From cbc981577094cb4a09a77cbf2ff83dcb7d8759d4 Mon Sep 17 00:00:00 2001 From: krishna2323 Date: Wed, 14 Aug 2024 05:49:48 +0530 Subject: [PATCH 001/121] fix: [P2P Distance] Split - Participants amount displayed 0.00 briefly on confirmation screen. Signed-off-by: krishna2323 --- .../MoneyRequestConfirmationList.tsx | 4 +- .../step/IOURequestStepParticipants.tsx | 50 ++++++++++++++++++- 2 files changed, 51 insertions(+), 3 deletions(-) diff --git a/src/components/MoneyRequestConfirmationList.tsx b/src/components/MoneyRequestConfirmationList.tsx index dcd3fa4c211..b9bae7243ab 100755 --- a/src/components/MoneyRequestConfirmationList.tsx +++ b/src/components/MoneyRequestConfirmationList.tsx @@ -322,7 +322,7 @@ function MoneyRequestConfirmationList({ const isFirstUpdatedDistanceAmount = useRef(false); useEffect(() => { - if (isFirstUpdatedDistanceAmount.current) { + if (isFirstUpdatedDistanceAmount.current || iouAmount) { return; } if (!isDistanceRequest) { @@ -331,7 +331,7 @@ function MoneyRequestConfirmationList({ const amount = DistanceRequestUtils.getDistanceRequestAmount(distance, unit ?? CONST.CUSTOM_UNITS.DISTANCE_UNIT_MILES, rate ?? 0); IOU.setMoneyRequestAmount(transactionID, amount, currency ?? ''); isFirstUpdatedDistanceAmount.current = true; - }, [distance, rate, unit, transactionID, currency, isDistanceRequest]); + }, [distance, rate, unit, transactionID, currency, isDistanceRequest, iouAmount]); useEffect(() => { if (!shouldCalculateDistanceAmount) { diff --git a/src/pages/iou/request/step/IOURequestStepParticipants.tsx b/src/pages/iou/request/step/IOURequestStepParticipants.tsx index 7fbc8d260f8..3598b32fdac 100644 --- a/src/pages/iou/request/step/IOURequestStepParticipants.tsx +++ b/src/pages/iou/request/step/IOURequestStepParticipants.tsx @@ -3,13 +3,17 @@ import React, {useCallback, useEffect, useMemo, useRef} from 'react'; import type {OnyxEntry} from 'react-native-onyx'; import {withOnyx} from 'react-native-onyx'; import FormHelpMessage from '@components/FormHelpMessage'; +import {usePersonalDetails} from '@components/OnyxProvider'; import useLocalize from '@hooks/useLocalize'; import useThemeStyles from '@hooks/useThemeStyles'; import {READ_COMMANDS} from '@libs/API/types'; import DistanceRequestUtils from '@libs/DistanceRequestUtils'; +import type {MileageRate} from '@libs/DistanceRequestUtils'; import HttpUtils from '@libs/HttpUtils'; import * as IOUUtils from '@libs/IOUUtils'; import Navigation from '@libs/Navigation/Navigation'; +import * as OptionsListUtils from '@libs/OptionsListUtils'; +import * as PolicyUtils from '@libs/PolicyUtils'; import * as ReportUtils from '@libs/ReportUtils'; import * as TransactionUtils from '@libs/TransactionUtils'; import MoneyRequestParticipantsSelector from '@pages/iou/request/MoneyRequestParticipantsSelector'; @@ -40,17 +44,20 @@ function IOURequestStepParticipants({ }, transaction, skipConfirmation, + report, }: IOURequestStepParticipantsProps) { const participants = transaction?.participants; const {translate} = useLocalize(); const styles = useThemeStyles(); const isFocused = useIsFocused(); + const personalDetails = usePersonalDetails(); // We need to set selectedReportID if user has navigated back from confirmation page and navigates to confirmation page with already selected participant const selectedReportID = useRef(participants?.length === 1 ? participants[0]?.reportID ?? reportID : reportID); const numberOfParticipants = useRef(participants?.length ?? 0); const iouRequestType = TransactionUtils.getRequestType(transaction); const isSplitRequest = iouType === CONST.IOU.TYPE.SPLIT; + const isDistanceRequest = iouRequestType === CONST.IOU.REQUEST_TYPE.DISTANCE; const headerTitle = useMemo(() => { if (action === CONST.IOU.ACTION.CATEGORIZE) { return translate('iou.categorize'); @@ -113,6 +120,43 @@ function IOURequestStepParticipants({ [iouType, reportID, transactionID], ); + // Sets `amount` and `split` share data before moving to the next step to avoid briefly showing `0.00` as the split share for participants + const setDistanceRequestData = useCallback(() => { + const isTypeSplit = iouType === CONST.IOU.TYPE.SPLIT; + + // Get policy report based on transaction participants + const policyReport = transaction?.participants?.[0] ? ReportUtils.getReport(transaction.participants[0].reportID ?? '') ?? report : report; + + const policyID = IOU.getIOURequestPolicyID(transaction, policyReport); + const policy = PolicyUtils.getPolicy(report?.policyID ?? policyID); + const policyCurrency = policy?.outputCurrency ?? PolicyUtils.getPersonalPolicy()?.outputCurrency ?? CONST.CURRENCY.USD; + + const customUnitRateID = TransactionUtils.getRateID(transaction) ?? '-1'; + const mileageRates = DistanceRequestUtils.getMileageRates(policy); + const defaultMileageRate = DistanceRequestUtils.getDefaultMileageRate(policy); + const mileageRate = TransactionUtils.isCustomUnitRateIDForP2P(transaction) + ? DistanceRequestUtils.getRateForP2P(policyCurrency) + : mileageRates?.[customUnitRateID] ?? defaultMileageRate; + + const {unit, rate} = mileageRate ?? {}; + const distance = TransactionUtils.getDistanceInMeters(transaction, unit); + const currency = (mileageRate as MileageRate)?.currency ?? policyCurrency; + const amount = DistanceRequestUtils.getDistanceRequestAmount(distance, unit ?? CONST.CUSTOM_UNITS.DISTANCE_UNIT_MILES, rate ?? 0); + IOU.setMoneyRequestAmount(transactionID, amount, currency ?? ''); + + const participantsMap = + transaction?.participants?.map((participant) => { + if (participant.isSender && iouType === CONST.IOU.TYPE.INVOICE) { + return participant; + } + return participant.accountID ? OptionsListUtils.getParticipantsOption(participant, personalDetails) : OptionsListUtils.getReportOption(participant); + }) ?? []; + const participantAccountIDs: number[] = participantsMap.map((participant) => participant.accountID ?? -1); + if (isTypeSplit && amount && transaction?.currency) { + IOU.setSplitShares(transaction, amount, currency, participantAccountIDs); + } + }, [iouType, personalDetails, report, transaction, transactionID]); + const goToNextStep = useCallback(() => { const isCategorizing = action === CONST.IOU.ACTION.CATEGORIZE; const isShareAction = action === CONST.IOU.ACTION.SHARE; @@ -123,6 +167,10 @@ function IOURequestStepParticipants({ IOU.setSplitShares(transaction, transaction.amount, transaction.currency, participantAccountIDs); } + if (isDistanceRequest) { + setDistanceRequestData(); + } + IOU.setMoneyRequestTag(transactionID, ''); IOU.setMoneyRequestCategory(transactionID, ''); if ((isCategorizing || isShareAction) && numberOfParticipants.current === 0) { @@ -136,7 +184,7 @@ function IOURequestStepParticipants({ } else { Navigation.navigate(iouConfirmationPageRoute); } - }, [iouType, transactionID, transaction, reportID, action, participants]); + }, [iouType, transactionID, transaction, reportID, action, participants, setDistanceRequestData, isDistanceRequest]); const navigateBack = useCallback(() => { IOUUtils.navigateToStartMoneyRequestStep(iouRequestType, iouType, transactionID, reportID, action); From 377be13ade1dd51ba2091028ae2589cfe6d964bf Mon Sep 17 00:00:00 2001 From: krishna2323 Date: Fri, 16 Aug 2024 15:53:17 +0530 Subject: [PATCH 002/121] minor fixes. Signed-off-by: krishna2323 --- .../MoneyRequestConfirmationList.tsx | 2 +- .../step/IOURequestStepParticipants.tsx | 75 ++++++++++--------- 2 files changed, 40 insertions(+), 37 deletions(-) diff --git a/src/components/MoneyRequestConfirmationList.tsx b/src/components/MoneyRequestConfirmationList.tsx index c26636cb4ae..9b00420294b 100755 --- a/src/components/MoneyRequestConfirmationList.tsx +++ b/src/components/MoneyRequestConfirmationList.tsx @@ -340,7 +340,7 @@ function MoneyRequestConfirmationList({ const isFirstUpdatedDistanceAmount = useRef(false); useEffect(() => { - if (isFirstUpdatedDistanceAmount.current || iouAmount) { + if (isFirstUpdatedDistanceAmount.current) { return; } if (!isDistanceRequest) { diff --git a/src/pages/iou/request/step/IOURequestStepParticipants.tsx b/src/pages/iou/request/step/IOURequestStepParticipants.tsx index 3598b32fdac..1bd74219603 100644 --- a/src/pages/iou/request/step/IOURequestStepParticipants.tsx +++ b/src/pages/iou/request/step/IOURequestStepParticipants.tsx @@ -121,41 +121,44 @@ function IOURequestStepParticipants({ ); // Sets `amount` and `split` share data before moving to the next step to avoid briefly showing `0.00` as the split share for participants - const setDistanceRequestData = useCallback(() => { - const isTypeSplit = iouType === CONST.IOU.TYPE.SPLIT; - - // Get policy report based on transaction participants - const policyReport = transaction?.participants?.[0] ? ReportUtils.getReport(transaction.participants[0].reportID ?? '') ?? report : report; - - const policyID = IOU.getIOURequestPolicyID(transaction, policyReport); - const policy = PolicyUtils.getPolicy(report?.policyID ?? policyID); - const policyCurrency = policy?.outputCurrency ?? PolicyUtils.getPersonalPolicy()?.outputCurrency ?? CONST.CURRENCY.USD; - - const customUnitRateID = TransactionUtils.getRateID(transaction) ?? '-1'; - const mileageRates = DistanceRequestUtils.getMileageRates(policy); - const defaultMileageRate = DistanceRequestUtils.getDefaultMileageRate(policy); - const mileageRate = TransactionUtils.isCustomUnitRateIDForP2P(transaction) - ? DistanceRequestUtils.getRateForP2P(policyCurrency) - : mileageRates?.[customUnitRateID] ?? defaultMileageRate; - - const {unit, rate} = mileageRate ?? {}; - const distance = TransactionUtils.getDistanceInMeters(transaction, unit); - const currency = (mileageRate as MileageRate)?.currency ?? policyCurrency; - const amount = DistanceRequestUtils.getDistanceRequestAmount(distance, unit ?? CONST.CUSTOM_UNITS.DISTANCE_UNIT_MILES, rate ?? 0); - IOU.setMoneyRequestAmount(transactionID, amount, currency ?? ''); - - const participantsMap = - transaction?.participants?.map((participant) => { - if (participant.isSender && iouType === CONST.IOU.TYPE.INVOICE) { - return participant; - } - return participant.accountID ? OptionsListUtils.getParticipantsOption(participant, personalDetails) : OptionsListUtils.getReportOption(participant); - }) ?? []; - const participantAccountIDs: number[] = participantsMap.map((participant) => participant.accountID ?? -1); - if (isTypeSplit && amount && transaction?.currency) { - IOU.setSplitShares(transaction, amount, currency, participantAccountIDs); - } - }, [iouType, personalDetails, report, transaction, transactionID]); + const setDistanceRequestData = useCallback( + (isPolicyExpenseChat: boolean) => { + const isTypeSplit = iouType === CONST.IOU.TYPE.SPLIT; + + // Get policy report based on transaction participants + const policyReport = transaction?.participants?.[0] ? ReportUtils.getReport(transaction.participants[0].reportID ?? '') ?? report : report; + + const policyID = IOU.getIOURequestPolicyID(transaction, policyReport); + const policy = PolicyUtils.getPolicy(report?.policyID ?? policyID); + const policyCurrency = policy?.outputCurrency ?? PolicyUtils.getPersonalPolicy()?.outputCurrency ?? CONST.CURRENCY.USD; + + const customUnitRateID = TransactionUtils.getRateID(transaction) ?? '-1'; + const mileageRates = DistanceRequestUtils.getMileageRates(policy); + const defaultMileageRate = DistanceRequestUtils.getDefaultMileageRate(policy); + const mileageRate = TransactionUtils.isCustomUnitRateIDForP2P(transaction) + ? DistanceRequestUtils.getRateForP2P(policyCurrency) + : mileageRates?.[customUnitRateID] ?? defaultMileageRate; + + const {unit, rate} = mileageRate ?? {}; + const distance = TransactionUtils.getDistanceInMeters(transaction, unit); + const currency = (mileageRate as MileageRate)?.currency ?? policyCurrency; + const amount = DistanceRequestUtils.getDistanceRequestAmount(distance, unit ?? CONST.CUSTOM_UNITS.DISTANCE_UNIT_MILES, rate ?? 0); + IOU.setMoneyRequestAmount(transactionID, amount, currency ?? ''); + + const participantsMap = + transaction?.participants?.map((participant) => { + if (participant.isSender && iouType === CONST.IOU.TYPE.INVOICE) { + return participant; + } + return participant.accountID ? OptionsListUtils.getParticipantsOption(participant, personalDetails) : OptionsListUtils.getReportOption(participant); + }) ?? []; + const participantAccountIDs: number[] = participantsMap.map((participant) => participant.accountID ?? -1); + if (isTypeSplit && amount && transaction?.currency && !isPolicyExpenseChat) { + IOU.setSplitShares(transaction, amount, currency ?? '', participantAccountIDs); + } + }, + [iouType, personalDetails, report, transaction, transactionID], + ); const goToNextStep = useCallback(() => { const isCategorizing = action === CONST.IOU.ACTION.CATEGORIZE; @@ -168,7 +171,7 @@ function IOURequestStepParticipants({ } if (isDistanceRequest) { - setDistanceRequestData(); + setDistanceRequestData(!!isPolicyExpenseChat); } IOU.setMoneyRequestTag(transactionID, ''); From 932a839ff5792945f34b468336d566c6f01c0c1d Mon Sep 17 00:00:00 2001 From: krishna2323 Date: Fri, 16 Aug 2024 15:55:23 +0530 Subject: [PATCH 003/121] minor update. Signed-off-by: krishna2323 --- src/pages/iou/request/step/IOURequestStepParticipants.tsx | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/src/pages/iou/request/step/IOURequestStepParticipants.tsx b/src/pages/iou/request/step/IOURequestStepParticipants.tsx index 1bd74219603..7a53d169d05 100644 --- a/src/pages/iou/request/step/IOURequestStepParticipants.tsx +++ b/src/pages/iou/request/step/IOURequestStepParticipants.tsx @@ -143,7 +143,7 @@ function IOURequestStepParticipants({ const distance = TransactionUtils.getDistanceInMeters(transaction, unit); const currency = (mileageRate as MileageRate)?.currency ?? policyCurrency; const amount = DistanceRequestUtils.getDistanceRequestAmount(distance, unit ?? CONST.CUSTOM_UNITS.DISTANCE_UNIT_MILES, rate ?? 0); - IOU.setMoneyRequestAmount(transactionID, amount, currency ?? ''); + IOU.setMoneyRequestAmount(transactionID, amount, currency); const participantsMap = transaction?.participants?.map((participant) => { @@ -153,7 +153,7 @@ function IOURequestStepParticipants({ return participant.accountID ? OptionsListUtils.getParticipantsOption(participant, personalDetails) : OptionsListUtils.getReportOption(participant); }) ?? []; const participantAccountIDs: number[] = participantsMap.map((participant) => participant.accountID ?? -1); - if (isTypeSplit && amount && transaction?.currency && !isPolicyExpenseChat) { + if (isTypeSplit && amount && currency && !isPolicyExpenseChat) { IOU.setSplitShares(transaction, amount, currency ?? '', participantAccountIDs); } }, From 99f37b36d66d54ec7dd48ff60c961e835071e898 Mon Sep 17 00:00:00 2001 From: krishna2323 Date: Fri, 16 Aug 2024 16:03:29 +0530 Subject: [PATCH 004/121] remove extra dependency. Signed-off-by: krishna2323 --- src/components/MoneyRequestConfirmationList.tsx | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/components/MoneyRequestConfirmationList.tsx b/src/components/MoneyRequestConfirmationList.tsx index 9b00420294b..80587998d9b 100755 --- a/src/components/MoneyRequestConfirmationList.tsx +++ b/src/components/MoneyRequestConfirmationList.tsx @@ -349,7 +349,7 @@ function MoneyRequestConfirmationList({ const amount = DistanceRequestUtils.getDistanceRequestAmount(distance, unit ?? CONST.CUSTOM_UNITS.DISTANCE_UNIT_MILES, rate ?? 0); IOU.setMoneyRequestAmount(transactionID, amount, currency ?? ''); isFirstUpdatedDistanceAmount.current = true; - }, [distance, rate, unit, transactionID, currency, isDistanceRequest, iouAmount]); + }, [distance, rate, unit, transactionID, currency, isDistanceRequest]); useEffect(() => { if (!shouldCalculateDistanceAmount) { From 5e894bd5e1d709008b4c91b51a76bfe292522048 Mon Sep 17 00:00:00 2001 From: krishna2323 Date: Sun, 18 Aug 2024 16:07:45 +0530 Subject: [PATCH 005/121] add suggested changes. Signed-off-by: krishna2323 --- .../step/IOURequestStepParticipants.tsx | 22 +++++-------------- 1 file changed, 5 insertions(+), 17 deletions(-) diff --git a/src/pages/iou/request/step/IOURequestStepParticipants.tsx b/src/pages/iou/request/step/IOURequestStepParticipants.tsx index 7a53d169d05..4e75fb7f706 100644 --- a/src/pages/iou/request/step/IOURequestStepParticipants.tsx +++ b/src/pages/iou/request/step/IOURequestStepParticipants.tsx @@ -3,7 +3,6 @@ import React, {useCallback, useEffect, useMemo, useRef} from 'react'; import type {OnyxEntry} from 'react-native-onyx'; import {withOnyx} from 'react-native-onyx'; import FormHelpMessage from '@components/FormHelpMessage'; -import {usePersonalDetails} from '@components/OnyxProvider'; import useLocalize from '@hooks/useLocalize'; import useThemeStyles from '@hooks/useThemeStyles'; import {READ_COMMANDS} from '@libs/API/types'; @@ -12,7 +11,6 @@ import type {MileageRate} from '@libs/DistanceRequestUtils'; import HttpUtils from '@libs/HttpUtils'; import * as IOUUtils from '@libs/IOUUtils'; import Navigation from '@libs/Navigation/Navigation'; -import * as OptionsListUtils from '@libs/OptionsListUtils'; import * as PolicyUtils from '@libs/PolicyUtils'; import * as ReportUtils from '@libs/ReportUtils'; import * as TransactionUtils from '@libs/TransactionUtils'; @@ -50,7 +48,6 @@ function IOURequestStepParticipants({ const {translate} = useLocalize(); const styles = useThemeStyles(); const isFocused = useIsFocused(); - const personalDetails = usePersonalDetails(); // We need to set selectedReportID if user has navigated back from confirmation page and navigates to confirmation page with already selected participant const selectedReportID = useRef(participants?.length === 1 ? participants[0]?.reportID ?? reportID : reportID); @@ -123,10 +120,8 @@ function IOURequestStepParticipants({ // Sets `amount` and `split` share data before moving to the next step to avoid briefly showing `0.00` as the split share for participants const setDistanceRequestData = useCallback( (isPolicyExpenseChat: boolean) => { - const isTypeSplit = iouType === CONST.IOU.TYPE.SPLIT; - // Get policy report based on transaction participants - const policyReport = transaction?.participants?.[0] ? ReportUtils.getReport(transaction.participants[0].reportID ?? '') ?? report : report; + const policyReport = transaction?.participants?.[0] ? ReportUtils.getReport(selectedReportID.current) : report; const policyID = IOU.getIOURequestPolicyID(transaction, policyReport); const policy = PolicyUtils.getPolicy(report?.policyID ?? policyID); @@ -145,19 +140,12 @@ function IOURequestStepParticipants({ const amount = DistanceRequestUtils.getDistanceRequestAmount(distance, unit ?? CONST.CUSTOM_UNITS.DISTANCE_UNIT_MILES, rate ?? 0); IOU.setMoneyRequestAmount(transactionID, amount, currency); - const participantsMap = - transaction?.participants?.map((participant) => { - if (participant.isSender && iouType === CONST.IOU.TYPE.INVOICE) { - return participant; - } - return participant.accountID ? OptionsListUtils.getParticipantsOption(participant, personalDetails) : OptionsListUtils.getReportOption(participant); - }) ?? []; - const participantAccountIDs: number[] = participantsMap.map((participant) => participant.accountID ?? -1); - if (isTypeSplit && amount && currency && !isPolicyExpenseChat) { - IOU.setSplitShares(transaction, amount, currency ?? '', participantAccountIDs); + const participantAccountIDs: number[] | undefined = transaction?.participants?.map((participant) => participant.accountID ?? -1); + if (isSplitRequest && amount && currency && !isPolicyExpenseChat) { + IOU.setSplitShares(transaction, amount, currency ?? '', participantAccountIDs ?? []); } }, - [iouType, personalDetails, report, transaction, transactionID], + [report, transaction, transactionID, isSplitRequest], ); const goToNextStep = useCallback(() => { From 30e13cc4244c5cb499d550bb2691d8199851b524 Mon Sep 17 00:00:00 2001 From: krishna2323 Date: Sun, 18 Aug 2024 16:48:13 +0530 Subject: [PATCH 006/121] define type for mileageRate. Signed-off-by: krishna2323 --- src/pages/iou/request/step/IOURequestStepParticipants.tsx | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/src/pages/iou/request/step/IOURequestStepParticipants.tsx b/src/pages/iou/request/step/IOURequestStepParticipants.tsx index 4e75fb7f706..c6010fa108a 100644 --- a/src/pages/iou/request/step/IOURequestStepParticipants.tsx +++ b/src/pages/iou/request/step/IOURequestStepParticipants.tsx @@ -130,13 +130,13 @@ function IOURequestStepParticipants({ const customUnitRateID = TransactionUtils.getRateID(transaction) ?? '-1'; const mileageRates = DistanceRequestUtils.getMileageRates(policy); const defaultMileageRate = DistanceRequestUtils.getDefaultMileageRate(policy); - const mileageRate = TransactionUtils.isCustomUnitRateIDForP2P(transaction) + const mileageRate: MileageRate = TransactionUtils.isCustomUnitRateIDForP2P(transaction) ? DistanceRequestUtils.getRateForP2P(policyCurrency) : mileageRates?.[customUnitRateID] ?? defaultMileageRate; const {unit, rate} = mileageRate ?? {}; const distance = TransactionUtils.getDistanceInMeters(transaction, unit); - const currency = (mileageRate as MileageRate)?.currency ?? policyCurrency; + const currency = mileageRate?.currency ?? policyCurrency; const amount = DistanceRequestUtils.getDistanceRequestAmount(distance, unit ?? CONST.CUSTOM_UNITS.DISTANCE_UNIT_MILES, rate ?? 0); IOU.setMoneyRequestAmount(transactionID, amount, currency); From 1d7dab0bb3725dc2f4a073ebb37392cc6c3a55e4 Mon Sep 17 00:00:00 2001 From: krishna2323 Date: Mon, 9 Sep 2024 19:23:56 +0530 Subject: [PATCH 007/121] remove redundant code. Signed-off-by: krishna2323 --- src/components/MoneyRequestConfirmationList.tsx | 14 -------------- 1 file changed, 14 deletions(-) diff --git a/src/components/MoneyRequestConfirmationList.tsx b/src/components/MoneyRequestConfirmationList.tsx index 20aa62c2a95..f86e5cf92ae 100755 --- a/src/components/MoneyRequestConfirmationList.tsx +++ b/src/components/MoneyRequestConfirmationList.tsx @@ -343,20 +343,6 @@ function MoneyRequestConfirmationList({ // eslint-disable-next-line react-compiler/react-compiler, react-hooks/exhaustive-deps -- we don't want this effect to run if it's just setFormError that changes }, [isFocused, transaction, shouldDisplayFieldError, hasSmartScanFailed, didConfirmSplit]); - const isFirstUpdatedDistanceAmount = useRef(false); - - useEffect(() => { - if (isFirstUpdatedDistanceAmount.current) { - return; - } - if (!isDistanceRequest) { - return; - } - const amount = DistanceRequestUtils.getDistanceRequestAmount(distance, unit ?? CONST.CUSTOM_UNITS.DISTANCE_UNIT_MILES, rate ?? 0); - IOU.setMoneyRequestAmount(transactionID, amount, currency ?? ''); - isFirstUpdatedDistanceAmount.current = true; - }, [distance, rate, unit, transactionID, currency, isDistanceRequest]); - useEffect(() => { if (!shouldCalculateDistanceAmount) { return; From cbdc5304a71b21f278ecbb8c24c0f438a6e32541 Mon Sep 17 00:00:00 2001 From: daledah Date: Thu, 12 Sep 2024 14:29:22 +0700 Subject: [PATCH 008/121] fix: Error when selecting distance rate that no longer has tax rate --- src/libs/actions/TaxRate.ts | 48 +++++++++++++++++++ .../PolicyDistanceRateDetailsPage.tsx | 2 +- 2 files changed, 49 insertions(+), 1 deletion(-) diff --git a/src/libs/actions/TaxRate.ts b/src/libs/actions/TaxRate.ts index 514b7391563..c8a2962d7dc 100644 --- a/src/libs/actions/TaxRate.ts +++ b/src/libs/actions/TaxRate.ts @@ -286,6 +286,10 @@ function deletePolicyTaxes(policyID: string, taxesToDelete: string[]) { const policyTaxRates = policy?.taxRates?.taxes; const foreignTaxDefault = policy?.taxRates?.foreignTaxDefault; const firstTaxID = Object.keys(policyTaxRates ?? {}).sort((a, b) => a.localeCompare(b))[0]; + const customUnits = policy?.customUnits ?? {}; + const ratesToUpdate = Object.values(customUnits?.[Object.keys(customUnits)[0]]?.rates).filter( + (rate) => !!rate.attributes?.taxRateExternalID && taxesToDelete.includes(rate.attributes?.taxRateExternalID), + ); if (!policyTaxRates) { console.debug('Policy or tax rates not found'); @@ -294,6 +298,35 @@ function deletePolicyTaxes(policyID: string, taxesToDelete: string[]) { const isForeignTaxRemoved = foreignTaxDefault && taxesToDelete.includes(foreignTaxDefault); + const optimisticRates: Record = {}; + const successRates: Record = {}; + const failureRates: Record = {}; + + ratesToUpdate.forEach((rate) => { + const rateID = rate.customUnitRateID ?? ''; + optimisticRates[rateID] = { + ...rate, + attributes: { + ...rate?.attributes, + taxRateExternalID: undefined, + taxClaimablePercentage: undefined, + }, + pendingFields: { + taxRateExternalID: CONST.RED_BRICK_ROAD_PENDING_ACTION.UPDATE, + taxClaimablePercentage: CONST.RED_BRICK_ROAD_PENDING_ACTION.UPDATE, + }, + }; + successRates[rateID] = {...rate, pendingFields: {taxRateExternalID: null}}; + failureRates[rateID] = { + ...rate, + pendingFields: {taxRateExternalID: null, taxClaimablePercentage: null}, + errorFields: { + taxRateExternalID: ErrorUtils.getMicroSecondOnyxErrorWithTranslationKey('common.genericErrorMessage'), + taxClaimablePercentage: ErrorUtils.getMicroSecondOnyxErrorWithTranslationKey('common.genericErrorMessage'), + }, + }; + }); + const onyxData: OnyxData = { optimisticData: [ { @@ -308,6 +341,11 @@ function deletePolicyTaxes(policyID: string, taxesToDelete: string[]) { return acc; }, {}), }, + customUnits: policy.customUnits && { + [Object.keys(policy.customUnits)[0]]: { + rates: optimisticRates, + }, + }, }, }, ], @@ -323,6 +361,11 @@ function deletePolicyTaxes(policyID: string, taxesToDelete: string[]) { return acc; }, {}), }, + customUnits: policy.customUnits && { + [Object.keys(policy.customUnits)[0]]: { + rates: successRates, + }, + }, }, }, ], @@ -341,6 +384,11 @@ function deletePolicyTaxes(policyID: string, taxesToDelete: string[]) { return acc; }, {}), }, + customUnits: policy.customUnits && { + [Object.keys(policy.customUnits)[0]]: { + rates: failureRates, + }, + }, }, }, ], diff --git a/src/pages/workspace/distanceRates/PolicyDistanceRateDetailsPage.tsx b/src/pages/workspace/distanceRates/PolicyDistanceRateDetailsPage.tsx index c4d033351b3..982e4799d78 100644 --- a/src/pages/workspace/distanceRates/PolicyDistanceRateDetailsPage.tsx +++ b/src/pages/workspace/distanceRates/PolicyDistanceRateDetailsPage.tsx @@ -155,7 +155,7 @@ function PolicyDistanceRateDetailsPage({policy, route}: PolicyDistanceRateDetail )} - {isDistanceTrackTaxEnabled && isPolicyTrackTaxEnabled && ( + {isDistanceTrackTaxEnabled && !!taxRate && isPolicyTrackTaxEnabled && ( Date: Thu, 12 Sep 2024 14:59:11 +0700 Subject: [PATCH 009/121] fix: undefined --- src/libs/actions/TaxRate.ts | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/libs/actions/TaxRate.ts b/src/libs/actions/TaxRate.ts index c8a2962d7dc..c9073bd4a66 100644 --- a/src/libs/actions/TaxRate.ts +++ b/src/libs/actions/TaxRate.ts @@ -287,7 +287,7 @@ function deletePolicyTaxes(policyID: string, taxesToDelete: string[]) { const foreignTaxDefault = policy?.taxRates?.foreignTaxDefault; const firstTaxID = Object.keys(policyTaxRates ?? {}).sort((a, b) => a.localeCompare(b))[0]; const customUnits = policy?.customUnits ?? {}; - const ratesToUpdate = Object.values(customUnits?.[Object.keys(customUnits)[0]]?.rates).filter( + const ratesToUpdate = Object.values(customUnits?.[Object.keys(customUnits)[0]]?.rates ?? {}).filter( (rate) => !!rate.attributes?.taxRateExternalID && taxesToDelete.includes(rate.attributes?.taxRateExternalID), ); From b2fcb63b1720a40573ef82de800a9b7ffd559569 Mon Sep 17 00:00:00 2001 From: krishna2323 Date: Sun, 15 Sep 2024 18:12:59 +0530 Subject: [PATCH 010/121] revert useEffect to update the amount. Signed-off-by: krishna2323 --- src/components/MoneyRequestConfirmationList.tsx | 14 ++++++++++++++ 1 file changed, 14 insertions(+) diff --git a/src/components/MoneyRequestConfirmationList.tsx b/src/components/MoneyRequestConfirmationList.tsx index 8672ae7dde2..743a5b276c9 100755 --- a/src/components/MoneyRequestConfirmationList.tsx +++ b/src/components/MoneyRequestConfirmationList.tsx @@ -347,6 +347,20 @@ function MoneyRequestConfirmationList({ // eslint-disable-next-line react-compiler/react-compiler, react-hooks/exhaustive-deps -- we don't want this effect to run if it's just setFormError that changes }, [isFocused, transaction, shouldDisplayFieldError, hasSmartScanFailed, didConfirmSplit]); + const isFirstUpdatedDistanceAmount = useRef(false); + + useEffect(() => { + if (isFirstUpdatedDistanceAmount.current) { + return; + } + if (!isDistanceRequest) { + return; + } + const amount = DistanceRequestUtils.getDistanceRequestAmount(distance, unit ?? CONST.CUSTOM_UNITS.DISTANCE_UNIT_MILES, rate ?? 0); + IOU.setMoneyRequestAmount(transactionID, amount, currency ?? ''); + isFirstUpdatedDistanceAmount.current = true; + }, [distance, rate, unit, transactionID, currency, isDistanceRequest]); + useEffect(() => { if (!shouldCalculateDistanceAmount) { return; From af0bbb283d1ef35d5a9aa73888b77da4f86da908 Mon Sep 17 00:00:00 2001 From: daledah Date: Wed, 18 Sep 2024 12:07:26 +0700 Subject: [PATCH 011/121] fix: change undefined to null in onyx data --- src/libs/actions/TaxRate.ts | 4 ++-- src/types/onyx/Policy.ts | 4 ++-- 2 files changed, 4 insertions(+), 4 deletions(-) diff --git a/src/libs/actions/TaxRate.ts b/src/libs/actions/TaxRate.ts index c9073bd4a66..bbc63b60367 100644 --- a/src/libs/actions/TaxRate.ts +++ b/src/libs/actions/TaxRate.ts @@ -308,8 +308,8 @@ function deletePolicyTaxes(policyID: string, taxesToDelete: string[]) { ...rate, attributes: { ...rate?.attributes, - taxRateExternalID: undefined, - taxClaimablePercentage: undefined, + taxRateExternalID: null, + taxClaimablePercentage: null, }, pendingFields: { taxRateExternalID: CONST.RED_BRICK_ROAD_PENDING_ACTION.UPDATE, diff --git a/src/types/onyx/Policy.ts b/src/types/onyx/Policy.ts index f2604d723f0..c1c5e78126a 100644 --- a/src/types/onyx/Policy.ts +++ b/src/types/onyx/Policy.ts @@ -11,10 +11,10 @@ type Unit = 'mi' | 'km'; /** Tax rate attributes of the policy distance rate */ type TaxRateAttributes = { /** Percentage of the tax that can be reclaimable */ - taxClaimablePercentage?: number; + taxClaimablePercentage?: number | null; /** External ID associated to this tax rate */ - taxRateExternalID?: string; + taxRateExternalID?: string | null; }; /** Model of policy distance rate */ From 6c5127e5fbd264f8dc9f10623ca2a72f2b2d7e17 Mon Sep 17 00:00:00 2001 From: daledah Date: Thu, 19 Sep 2024 14:19:12 +0700 Subject: [PATCH 012/121] fix: add missing key, refactor withOnyx --- src/libs/actions/TaxRate.ts | 2 +- .../PolicyDistanceRateDetailsPage.tsx | 20 +++++-------------- 2 files changed, 6 insertions(+), 16 deletions(-) diff --git a/src/libs/actions/TaxRate.ts b/src/libs/actions/TaxRate.ts index bbc63b60367..439e5131508 100644 --- a/src/libs/actions/TaxRate.ts +++ b/src/libs/actions/TaxRate.ts @@ -316,7 +316,7 @@ function deletePolicyTaxes(policyID: string, taxesToDelete: string[]) { taxClaimablePercentage: CONST.RED_BRICK_ROAD_PENDING_ACTION.UPDATE, }, }; - successRates[rateID] = {...rate, pendingFields: {taxRateExternalID: null}}; + successRates[rateID] = {...rate, pendingFields: {taxRateExternalID: null, taxClaimablePercentage: null}}; failureRates[rateID] = { ...rate, pendingFields: {taxRateExternalID: null, taxClaimablePercentage: null}, diff --git a/src/pages/workspace/distanceRates/PolicyDistanceRateDetailsPage.tsx b/src/pages/workspace/distanceRates/PolicyDistanceRateDetailsPage.tsx index 982e4799d78..0b7d925f2ee 100644 --- a/src/pages/workspace/distanceRates/PolicyDistanceRateDetailsPage.tsx +++ b/src/pages/workspace/distanceRates/PolicyDistanceRateDetailsPage.tsx @@ -1,8 +1,7 @@ import type {StackScreenProps} from '@react-navigation/stack'; import React, {useState} from 'react'; import {View} from 'react-native'; -import {withOnyx} from 'react-native-onyx'; -import type {OnyxEntry} from 'react-native-onyx'; +import {useOnyx} from 'react-native-onyx'; import ConfirmModal from '@components/ConfirmModal'; import HeaderWithBackButton from '@components/HeaderWithBackButton'; import * as Expensicons from '@components/Icon/Expensicons'; @@ -26,22 +25,17 @@ import CONST from '@src/CONST'; import ONYXKEYS from '@src/ONYXKEYS'; import ROUTES from '@src/ROUTES'; import type SCREENS from '@src/SCREENS'; -import type * as OnyxTypes from '@src/types/onyx'; import type {Rate, TaxRateAttributes} from '@src/types/onyx/Policy'; -type PolicyDistanceRateDetailsPageOnyxProps = { - /** Policy details */ - policy: OnyxEntry; -}; +type PolicyDistanceRateDetailsPageProps = StackScreenProps; -type PolicyDistanceRateDetailsPageProps = PolicyDistanceRateDetailsPageOnyxProps & StackScreenProps; - -function PolicyDistanceRateDetailsPage({policy, route}: PolicyDistanceRateDetailsPageProps) { +function PolicyDistanceRateDetailsPage({route}: PolicyDistanceRateDetailsPageProps) { const styles = useThemeStyles(); const {translate} = useLocalize(); const [isWarningModalVisible, setIsWarningModalVisible] = useState(false); const [isDeleteModalVisible, setIsDeleteModalVisible] = useState(false); const policyID = route.params.policyID; + const [policy] = useOnyx(`${ONYXKEYS.COLLECTION.POLICY}${route.params.policyID}`); const rateID = route.params.rateID; const customUnits = policy?.customUnits ?? {}; const customUnit = customUnits[Object.keys(customUnits)[0]]; @@ -209,8 +203,4 @@ function PolicyDistanceRateDetailsPage({policy, route}: PolicyDistanceRateDetail PolicyDistanceRateDetailsPage.displayName = 'PolicyDistanceRateDetailsPage'; -export default withOnyx({ - policy: { - key: ({route}) => `${ONYXKEYS.COLLECTION.POLICY}${route.params.policyID}`, - }, -})(PolicyDistanceRateDetailsPage); +export default PolicyDistanceRateDetailsPage; From a456b625745e23313b777d32bb37c18f9d12c49b Mon Sep 17 00:00:00 2001 From: daledah Date: Thu, 19 Sep 2024 23:47:28 +0700 Subject: [PATCH 013/121] refactor: onyx keys, types --- src/libs/actions/TaxRate.ts | 6 +++--- src/types/onyx/Policy.ts | 4 ++-- 2 files changed, 5 insertions(+), 5 deletions(-) diff --git a/src/libs/actions/TaxRate.ts b/src/libs/actions/TaxRate.ts index 439e5131508..bf0087d2673 100644 --- a/src/libs/actions/TaxRate.ts +++ b/src/libs/actions/TaxRate.ts @@ -1,4 +1,4 @@ -import type {OnyxCollection} from 'react-native-onyx'; +import type {NullishDeep, OnyxCollection} from 'react-native-onyx'; import Onyx from 'react-native-onyx'; import type {FormOnyxValues} from '@components/Form/types'; import * as API from '@libs/API'; @@ -298,7 +298,7 @@ function deletePolicyTaxes(policyID: string, taxesToDelete: string[]) { const isForeignTaxRemoved = foreignTaxDefault && taxesToDelete.includes(foreignTaxDefault); - const optimisticRates: Record = {}; + const optimisticRates: Record> = {}; const successRates: Record = {}; const failureRates: Record = {}; @@ -318,7 +318,7 @@ function deletePolicyTaxes(policyID: string, taxesToDelete: string[]) { }; successRates[rateID] = {...rate, pendingFields: {taxRateExternalID: null, taxClaimablePercentage: null}}; failureRates[rateID] = { - ...rate, + attributes: {...rate?.attributes}, pendingFields: {taxRateExternalID: null, taxClaimablePercentage: null}, errorFields: { taxRateExternalID: ErrorUtils.getMicroSecondOnyxErrorWithTranslationKey('common.genericErrorMessage'), diff --git a/src/types/onyx/Policy.ts b/src/types/onyx/Policy.ts index 26095aec818..95a9907526e 100644 --- a/src/types/onyx/Policy.ts +++ b/src/types/onyx/Policy.ts @@ -11,10 +11,10 @@ type Unit = 'mi' | 'km'; /** Tax rate attributes of the policy distance rate */ type TaxRateAttributes = { /** Percentage of the tax that can be reclaimable */ - taxClaimablePercentage?: number | null; + taxClaimablePercentage?: number; /** External ID associated to this tax rate */ - taxRateExternalID?: string | null; + taxRateExternalID?: string; }; /** Model of policy distance rate */ From 4d94b1113a3bdea0bb2f7b3f901574c5cb7acf74 Mon Sep 17 00:00:00 2001 From: daledah Date: Fri, 20 Sep 2024 16:34:55 +0700 Subject: [PATCH 014/121] fix: clean onyx data --- src/libs/actions/TaxRate.ts | 4 +--- 1 file changed, 1 insertion(+), 3 deletions(-) diff --git a/src/libs/actions/TaxRate.ts b/src/libs/actions/TaxRate.ts index bf0087d2673..81f3dbbb3ab 100644 --- a/src/libs/actions/TaxRate.ts +++ b/src/libs/actions/TaxRate.ts @@ -305,9 +305,7 @@ function deletePolicyTaxes(policyID: string, taxesToDelete: string[]) { ratesToUpdate.forEach((rate) => { const rateID = rate.customUnitRateID ?? ''; optimisticRates[rateID] = { - ...rate, attributes: { - ...rate?.attributes, taxRateExternalID: null, taxClaimablePercentage: null, }, @@ -316,7 +314,7 @@ function deletePolicyTaxes(policyID: string, taxesToDelete: string[]) { taxClaimablePercentage: CONST.RED_BRICK_ROAD_PENDING_ACTION.UPDATE, }, }; - successRates[rateID] = {...rate, pendingFields: {taxRateExternalID: null, taxClaimablePercentage: null}}; + successRates[rateID] = {pendingFields: {taxRateExternalID: null, taxClaimablePercentage: null}}; failureRates[rateID] = { attributes: {...rate?.attributes}, pendingFields: {taxRateExternalID: null, taxClaimablePercentage: null}, From 8da97f8f49acc09d34b202f64acbeb36c1d205ee Mon Sep 17 00:00:00 2001 From: HezekielT Date: Sun, 29 Sep 2024 12:48:56 +0300 Subject: [PATCH 015/121] add isUploading to language and use it in DefaultAttachmentView --- .../AttachmentView/DefaultAttachmentView/index.tsx | 7 +++++-- src/languages/en.ts | 1 + src/languages/es.ts | 1 + 3 files changed, 7 insertions(+), 2 deletions(-) diff --git a/src/components/Attachments/AttachmentView/DefaultAttachmentView/index.tsx b/src/components/Attachments/AttachmentView/DefaultAttachmentView/index.tsx index ee594f66aab..e6ac9f9f21c 100644 --- a/src/components/Attachments/AttachmentView/DefaultAttachmentView/index.tsx +++ b/src/components/Attachments/AttachmentView/DefaultAttachmentView/index.tsx @@ -24,9 +24,12 @@ type DefaultAttachmentViewProps = { containerStyles?: StyleProp; icon?: IconAsset; + + /** Flag indicating if the attachment is being uploaded. */ + isUploading?: boolean; }; -function DefaultAttachmentView({fileName = '', shouldShowLoadingSpinnerIcon = false, shouldShowDownloadIcon, containerStyles, icon}: DefaultAttachmentViewProps) { +function DefaultAttachmentView({fileName = '', shouldShowLoadingSpinnerIcon = false, shouldShowDownloadIcon, containerStyles, icon, isUploading}: DefaultAttachmentViewProps) { const theme = useTheme(); const styles = useThemeStyles(); const {translate} = useLocalize(); @@ -53,7 +56,7 @@ function DefaultAttachmentView({fileName = '', shouldShowLoadingSpinnerIcon = fa )} {shouldShowLoadingSpinnerIcon && ( - + Date: Sun, 29 Sep 2024 13:07:57 +0300 Subject: [PATCH 016/121] pass isUploading and use it show loading spinner. --- .../BaseAnchorForAttachmentsOnly.tsx | 3 ++- src/components/Attachments/AttachmentView/index.tsx | 8 +++++++- 2 files changed, 9 insertions(+), 2 deletions(-) diff --git a/src/components/AnchorForAttachmentsOnly/BaseAnchorForAttachmentsOnly.tsx b/src/components/AnchorForAttachmentsOnly/BaseAnchorForAttachmentsOnly.tsx index 1f1844bf20d..9ef0ab97f43 100644 --- a/src/components/AnchorForAttachmentsOnly/BaseAnchorForAttachmentsOnly.tsx +++ b/src/components/AnchorForAttachmentsOnly/BaseAnchorForAttachmentsOnly.tsx @@ -43,7 +43,7 @@ function BaseAnchorForAttachmentsOnly({style, source = '', displayName = '', dow {({anchor, report, reportNameValuePairs, action, checkIfContextMenuActive, isDisabled}) => ( { if (isDownloading || isOffline || !sourceID) { return; @@ -69,6 +69,7 @@ function BaseAnchorForAttachmentsOnly({style, source = '', displayName = '', dow shouldShowDownloadIcon={!!sourceID && !isOffline} shouldShowLoadingSpinnerIcon={isDownloading} isUsedAsChatAttachment + isUploading={!sourceID} /> )} diff --git a/src/components/Attachments/AttachmentView/index.tsx b/src/components/Attachments/AttachmentView/index.tsx index 6d14a41741a..03669d2fd9a 100644 --- a/src/components/Attachments/AttachmentView/index.tsx +++ b/src/components/Attachments/AttachmentView/index.tsx @@ -77,6 +77,10 @@ type AttachmentViewProps = AttachmentViewOnyxProps & /* Flag indicating whether the attachment has been uploaded. */ isUploaded?: boolean; + + /** Flag indicating if the attachment is being uploaded. */ + isUploading?: boolean; + }; function AttachmentView({ @@ -101,6 +105,7 @@ function AttachmentView({ duration, isUsedAsChatAttachment, isUploaded = true, + isUploading = false, }: AttachmentViewProps) { const {translate} = useLocalize(); const {updateCurrentlyPlayingURL} = usePlaybackContext(); @@ -288,8 +293,9 @@ function AttachmentView({ ); } From fcf13c9bae431e19745b6820d04225c9ffa3dbd1 Mon Sep 17 00:00:00 2001 From: HezekielT Date: Mon, 30 Sep 2024 03:58:30 +0300 Subject: [PATCH 017/121] replace withOnyx by useOnyx --- .../BaseAnchorForAttachmentsOnly.tsx | 23 +++++-------------- .../Attachments/AttachmentView/index.tsx | 22 ++++++------------ 2 files changed, 13 insertions(+), 32 deletions(-) diff --git a/src/components/AnchorForAttachmentsOnly/BaseAnchorForAttachmentsOnly.tsx b/src/components/AnchorForAttachmentsOnly/BaseAnchorForAttachmentsOnly.tsx index 9ef0ab97f43..f0e3305165d 100644 --- a/src/components/AnchorForAttachmentsOnly/BaseAnchorForAttachmentsOnly.tsx +++ b/src/components/AnchorForAttachmentsOnly/BaseAnchorForAttachmentsOnly.tsx @@ -1,6 +1,6 @@ import React from 'react'; import type {OnyxEntry} from 'react-native-onyx'; -import {withOnyx} from 'react-native-onyx'; +import {useOnyx} from 'react-native-onyx'; import AttachmentView from '@components/Attachments/AttachmentView'; import PressableWithoutFeedback from '@components/Pressable/PressableWithoutFeedback'; import {ShowContextMenuContext, showContextMenuForReport} from '@components/ShowContextMenuContext'; @@ -13,16 +13,10 @@ import * as ReportUtils from '@libs/ReportUtils'; import * as Download from '@userActions/Download'; import CONST from '@src/CONST'; import ONYXKEYS from '@src/ONYXKEYS'; -import type {Download as OnyxDownload} from '@src/types/onyx'; import type AnchorForAttachmentsOnlyProps from './types'; -type BaseAnchorForAttachmentsOnlyOnyxProps = { - /** If a file download is happening */ - download: OnyxEntry; -}; -type BaseAnchorForAttachmentsOnlyProps = AnchorForAttachmentsOnlyProps & - BaseAnchorForAttachmentsOnlyOnyxProps & { +type BaseAnchorForAttachmentsOnlyProps = AnchorForAttachmentsOnlyProps & { /** Press in handler for the link */ onPressIn?: () => void; @@ -30,10 +24,12 @@ type BaseAnchorForAttachmentsOnlyProps = AnchorForAttachmentsOnlyProps & onPressOut?: () => void; }; -function BaseAnchorForAttachmentsOnly({style, source = '', displayName = '', download, onPressIn, onPressOut}: BaseAnchorForAttachmentsOnlyProps) { +function BaseAnchorForAttachmentsOnly({style, source = '', displayName = '', onPressIn, onPressOut}: BaseAnchorForAttachmentsOnlyProps) { const sourceURLWithAuth = addEncryptedAuthTokenToURL(source); const sourceID = (source.match(CONST.REGEX.ATTACHMENT_ID) ?? [])[1]; + const [download] = useOnyx(`${ONYXKEYS.COLLECTION.DOWNLOAD}${sourceID}`) + const {isOffline} = useNetwork(); const styles = useThemeStyles(); @@ -79,11 +75,4 @@ function BaseAnchorForAttachmentsOnly({style, source = '', displayName = '', dow BaseAnchorForAttachmentsOnly.displayName = 'BaseAnchorForAttachmentsOnly'; -export default withOnyx({ - download: { - key: ({source}) => { - const sourceID = (source?.match(CONST.REGEX.ATTACHMENT_ID) ?? [])[1]; - return `${ONYXKEYS.COLLECTION.DOWNLOAD}${sourceID}`; - }, - }, -})(BaseAnchorForAttachmentsOnly); +export default BaseAnchorForAttachmentsOnly; diff --git a/src/components/Attachments/AttachmentView/index.tsx b/src/components/Attachments/AttachmentView/index.tsx index 03669d2fd9a..505bb812f65 100644 --- a/src/components/Attachments/AttachmentView/index.tsx +++ b/src/components/Attachments/AttachmentView/index.tsx @@ -3,7 +3,7 @@ import React, {memo, useContext, useEffect, useState} from 'react'; import type {GestureResponderEvent, StyleProp, ViewStyle} from 'react-native'; import {View} from 'react-native'; import type {OnyxEntry} from 'react-native-onyx'; -import {withOnyx} from 'react-native-onyx'; +import {useOnyx} from 'react-native-onyx'; import AttachmentCarouselPagerContext from '@components/Attachments/AttachmentCarousel/Pager/AttachmentCarouselPagerContext'; import type {Attachment, AttachmentSource} from '@components/Attachments/types'; import DistanceEReceipt from '@components/DistanceEReceipt'; @@ -31,12 +31,7 @@ import AttachmentViewVideo from './AttachmentViewVideo'; import DefaultAttachmentView from './DefaultAttachmentView'; import HighResolutionInfo from './HighResolutionInfo'; -type AttachmentViewOnyxProps = { - transaction: OnyxEntry; -}; - -type AttachmentViewProps = AttachmentViewOnyxProps & - Attachment & { +type AttachmentViewProps = Attachment & { /** Whether this view is the active screen */ isFocused?: boolean; @@ -99,7 +94,7 @@ function AttachmentView({ isWorkspaceAvatar, maybeIcon, fallbackSource, - transaction, + transactionID = '-1', reportActionID, isHovered, duration, @@ -110,6 +105,9 @@ function AttachmentView({ const {translate} = useLocalize(); const {updateCurrentlyPlayingURL} = usePlaybackContext(); const attachmentCarouselPagerContext = useContext(AttachmentCarouselPagerContext); + + const [transaction] = useOnyx(`${ONYXKEYS.COLLECTION.TRANSACTION}${transactionID}`); + const theme = useTheme(); const styles = useThemeStyles(); const StyleUtils = useStyleUtils(); @@ -302,12 +300,6 @@ function AttachmentView({ AttachmentView.displayName = 'AttachmentView'; -export default memo( - withOnyx({ - transaction: { - key: ({transactionID}) => `${ONYXKEYS.COLLECTION.TRANSACTION}${transactionID}`, - }, - })(AttachmentView), -); +export default memo(AttachmentView); export type {AttachmentViewProps}; From 87aa484d1449c0d92e27de932e68fb8065f65822 Mon Sep 17 00:00:00 2001 From: HezekielT Date: Mon, 30 Sep 2024 04:00:27 +0300 Subject: [PATCH 018/121] run prettier --- .../BaseAnchorForAttachmentsOnly.tsx | 13 ++-- .../Attachments/AttachmentView/index.tsx | 63 +++++++++---------- 2 files changed, 37 insertions(+), 39 deletions(-) diff --git a/src/components/AnchorForAttachmentsOnly/BaseAnchorForAttachmentsOnly.tsx b/src/components/AnchorForAttachmentsOnly/BaseAnchorForAttachmentsOnly.tsx index f0e3305165d..5f2ab6a761e 100644 --- a/src/components/AnchorForAttachmentsOnly/BaseAnchorForAttachmentsOnly.tsx +++ b/src/components/AnchorForAttachmentsOnly/BaseAnchorForAttachmentsOnly.tsx @@ -15,20 +15,19 @@ import CONST from '@src/CONST'; import ONYXKEYS from '@src/ONYXKEYS'; import type AnchorForAttachmentsOnlyProps from './types'; - type BaseAnchorForAttachmentsOnlyProps = AnchorForAttachmentsOnlyProps & { - /** Press in handler for the link */ - onPressIn?: () => void; + /** Press in handler for the link */ + onPressIn?: () => void; - /** Press out handler for the link */ - onPressOut?: () => void; - }; + /** Press out handler for the link */ + onPressOut?: () => void; +}; function BaseAnchorForAttachmentsOnly({style, source = '', displayName = '', onPressIn, onPressOut}: BaseAnchorForAttachmentsOnlyProps) { const sourceURLWithAuth = addEncryptedAuthTokenToURL(source); const sourceID = (source.match(CONST.REGEX.ATTACHMENT_ID) ?? [])[1]; - const [download] = useOnyx(`${ONYXKEYS.COLLECTION.DOWNLOAD}${sourceID}`) + const [download] = useOnyx(`${ONYXKEYS.COLLECTION.DOWNLOAD}${sourceID}`); const {isOffline} = useNetwork(); const styles = useThemeStyles(); diff --git a/src/components/Attachments/AttachmentView/index.tsx b/src/components/Attachments/AttachmentView/index.tsx index 505bb812f65..41294807397 100644 --- a/src/components/Attachments/AttachmentView/index.tsx +++ b/src/components/Attachments/AttachmentView/index.tsx @@ -32,51 +32,50 @@ import DefaultAttachmentView from './DefaultAttachmentView'; import HighResolutionInfo from './HighResolutionInfo'; type AttachmentViewProps = Attachment & { - /** Whether this view is the active screen */ - isFocused?: boolean; + /** Whether this view is the active screen */ + isFocused?: boolean; - /** Function for handle on press */ - onPress?: (e?: GestureResponderEvent | KeyboardEvent) => void; + /** Function for handle on press */ + onPress?: (e?: GestureResponderEvent | KeyboardEvent) => void; - isUsedInAttachmentModal?: boolean; + isUsedInAttachmentModal?: boolean; - /** Flag to show/hide download icon */ - shouldShowDownloadIcon?: boolean; + /** Flag to show/hide download icon */ + shouldShowDownloadIcon?: boolean; - /** Flag to show the loading indicator */ - shouldShowLoadingSpinnerIcon?: boolean; + /** Flag to show the loading indicator */ + shouldShowLoadingSpinnerIcon?: boolean; - /** Notify parent that the UI should be modified to accommodate keyboard */ - onToggleKeyboard?: (shouldFadeOut: boolean) => void; + /** Notify parent that the UI should be modified to accommodate keyboard */ + onToggleKeyboard?: (shouldFadeOut: boolean) => void; - /** A callback when the PDF fails to load */ - onPDFLoadError?: () => void; + /** A callback when the PDF fails to load */ + onPDFLoadError?: () => void; - /** Extra styles to pass to View wrapper */ - containerStyles?: StyleProp; + /** Extra styles to pass to View wrapper */ + containerStyles?: StyleProp; - /** Denotes whether it is a workspace avatar or not */ - isWorkspaceAvatar?: boolean; + /** Denotes whether it is a workspace avatar or not */ + isWorkspaceAvatar?: boolean; - /** Denotes whether it is an icon (ex: SVG) */ - maybeIcon?: boolean; + /** Denotes whether it is an icon (ex: SVG) */ + maybeIcon?: boolean; - /** Fallback source to use in case of error */ - fallbackSource?: AttachmentSource; + /** Fallback source to use in case of error */ + fallbackSource?: AttachmentSource; - /* Whether it is hovered or not */ - isHovered?: boolean; + /* Whether it is hovered or not */ + isHovered?: boolean; - /** Whether the attachment is used as a chat attachment */ - isUsedAsChatAttachment?: boolean; + /** Whether the attachment is used as a chat attachment */ + isUsedAsChatAttachment?: boolean; - /* Flag indicating whether the attachment has been uploaded. */ - isUploaded?: boolean; + /* Flag indicating whether the attachment has been uploaded. */ + isUploaded?: boolean; - /** Flag indicating if the attachment is being uploaded. */ - isUploading?: boolean; - - }; + /** Flag indicating if the attachment is being uploaded. */ + isUploading?: boolean; +}; function AttachmentView({ source, @@ -107,7 +106,7 @@ function AttachmentView({ const attachmentCarouselPagerContext = useContext(AttachmentCarouselPagerContext); const [transaction] = useOnyx(`${ONYXKEYS.COLLECTION.TRANSACTION}${transactionID}`); - + const theme = useTheme(); const styles = useThemeStyles(); const StyleUtils = useStyleUtils(); From 33360aa772daa1fc721dae875790c1c010ddd159 Mon Sep 17 00:00:00 2001 From: HezekielT Date: Mon, 30 Sep 2024 04:07:31 +0300 Subject: [PATCH 019/121] remove unused imports --- src/components/Attachments/AttachmentView/index.tsx | 2 -- 1 file changed, 2 deletions(-) diff --git a/src/components/Attachments/AttachmentView/index.tsx b/src/components/Attachments/AttachmentView/index.tsx index 41294807397..6c6d08f2c30 100644 --- a/src/components/Attachments/AttachmentView/index.tsx +++ b/src/components/Attachments/AttachmentView/index.tsx @@ -2,7 +2,6 @@ import {Str} from 'expensify-common'; import React, {memo, useContext, useEffect, useState} from 'react'; import type {GestureResponderEvent, StyleProp, ViewStyle} from 'react-native'; import {View} from 'react-native'; -import type {OnyxEntry} from 'react-native-onyx'; import {useOnyx} from 'react-native-onyx'; import AttachmentCarouselPagerContext from '@components/Attachments/AttachmentCarousel/Pager/AttachmentCarouselPagerContext'; import type {Attachment, AttachmentSource} from '@components/Attachments/types'; @@ -24,7 +23,6 @@ import * as TransactionUtils from '@libs/TransactionUtils'; import type {ColorValue} from '@styles/utils/types'; import variables from '@styles/variables'; import ONYXKEYS from '@src/ONYXKEYS'; -import type {Transaction} from '@src/types/onyx'; import AttachmentViewImage from './AttachmentViewImage'; import AttachmentViewPdf from './AttachmentViewPdf'; import AttachmentViewVideo from './AttachmentViewVideo'; From 86d532abfd12fc12f021524c906a5aeb51eab404 Mon Sep 17 00:00:00 2001 From: HezekielT Date: Mon, 30 Sep 2024 04:07:45 +0300 Subject: [PATCH 020/121] remove unused imports --- src/components/Attachments/AttachmentView/index.tsx | 1 + 1 file changed, 1 insertion(+) diff --git a/src/components/Attachments/AttachmentView/index.tsx b/src/components/Attachments/AttachmentView/index.tsx index 6c6d08f2c30..132f0affc23 100644 --- a/src/components/Attachments/AttachmentView/index.tsx +++ b/src/components/Attachments/AttachmentView/index.tsx @@ -288,6 +288,7 @@ function AttachmentView({ Date: Mon, 30 Sep 2024 04:09:55 +0300 Subject: [PATCH 021/121] remove unused imports --- .../AnchorForAttachmentsOnly/BaseAnchorForAttachmentsOnly.tsx | 1 - 1 file changed, 1 deletion(-) diff --git a/src/components/AnchorForAttachmentsOnly/BaseAnchorForAttachmentsOnly.tsx b/src/components/AnchorForAttachmentsOnly/BaseAnchorForAttachmentsOnly.tsx index 5f2ab6a761e..443a553d468 100644 --- a/src/components/AnchorForAttachmentsOnly/BaseAnchorForAttachmentsOnly.tsx +++ b/src/components/AnchorForAttachmentsOnly/BaseAnchorForAttachmentsOnly.tsx @@ -1,5 +1,4 @@ import React from 'react'; -import type {OnyxEntry} from 'react-native-onyx'; import {useOnyx} from 'react-native-onyx'; import AttachmentView from '@components/Attachments/AttachmentView'; import PressableWithoutFeedback from '@components/Pressable/PressableWithoutFeedback'; From b5293d1caaf219242a06fbc2516558c4d7296931 Mon Sep 17 00:00:00 2001 From: krishna2323 Date: Wed, 2 Oct 2024 06:17:45 +0530 Subject: [PATCH 022/121] move the logic to update amount for split in IOURequestStepDistance. Signed-off-by: krishna2323 --- .../request/step/IOURequestStepDistance.tsx | 37 ++++++++++++++++- .../step/IOURequestStepParticipants.tsx | 41 +------------------ 2 files changed, 37 insertions(+), 41 deletions(-) diff --git a/src/pages/iou/request/step/IOURequestStepDistance.tsx b/src/pages/iou/request/step/IOURequestStepDistance.tsx index 14597df8e31..4deac6e7ab1 100644 --- a/src/pages/iou/request/step/IOURequestStepDistance.tsx +++ b/src/pages/iou/request/step/IOURequestStepDistance.tsx @@ -20,10 +20,12 @@ import useNetwork from '@hooks/useNetwork'; import usePrevious from '@hooks/usePrevious'; import useThemeStyles from '@hooks/useThemeStyles'; import DistanceRequestUtils from '@libs/DistanceRequestUtils'; +import type {MileageRate} from '@libs/DistanceRequestUtils'; import * as ErrorUtils from '@libs/ErrorUtils'; import * as IOUUtils from '@libs/IOUUtils'; import Navigation from '@libs/Navigation/Navigation'; import * as OptionsListUtils from '@libs/OptionsListUtils'; +import * as PolicyUtils from '@libs/PolicyUtils'; import * as ReportUtils from '@libs/ReportUtils'; import * as TransactionUtils from '@libs/TransactionUtils'; import * as IOU from '@userActions/IOU'; @@ -98,6 +100,7 @@ function IOURequestStepDistance({ const scrollViewRef = useRef(null); const isLoadingRoute = transaction?.comment?.isLoading ?? false; const isLoading = transaction?.isLoading ?? false; + const isSplitRequest = iouType === CONST.IOU.TYPE.SPLIT; const hasRouteError = !!transaction?.errorFields?.route; const [shouldShowAtLeastTwoDifferentWaypointsError, setShouldShowAtLeastTwoDifferentWaypointsError] = useState(false); const isWaypointEmpty = (waypoint?: Waypoint) => { @@ -119,7 +122,37 @@ function IOURequestStepDistance({ const isCreatingNewRequest = !(backTo || isEditing); const [recentWaypoints, {status: recentWaypointsStatus}] = useOnyx(ONYXKEYS.NVP_RECENT_WAYPOINTS); const iouRequestType = TransactionUtils.getRequestType(transaction); - const customUnitRateID = TransactionUtils.getRateID(transaction); + const customUnitRateID = TransactionUtils.getRateID(transaction) ?? '-1'; + + // Sets `amount` and `split` share data before moving to the next step to avoid briefly showing `0.00` as the split share for participants + const setDistanceRequestData = useCallback(() => { + // Get policy report based on transaction participants + const participants = transaction?.participants; + const isPolicyExpenseChat = participants?.some((participant) => participant.isPolicyExpenseChat); + const selectedReportID = participants?.length === 1 ? participants[0]?.reportID ?? reportID : reportID; + const policyReport = transaction?.participants?.[0] ? ReportUtils.getReport(selectedReportID) : report; + + const policyID2 = IOU.getIOURequestPolicyID(transaction, policyReport); + const policy2 = PolicyUtils.getPolicy(report?.policyID ?? policyID2); + const policyCurrency = policy?.outputCurrency ?? PolicyUtils.getPersonalPolicy()?.outputCurrency ?? CONST.CURRENCY.USD; + + const mileageRates = DistanceRequestUtils.getMileageRates(policy2); + const defaultMileageRate = DistanceRequestUtils.getDefaultMileageRate(policy2); + const mileageRate: MileageRate = TransactionUtils.isCustomUnitRateIDForP2P(transaction) + ? DistanceRequestUtils.getRateForP2P(policyCurrency) + : mileageRates?.[customUnitRateID] ?? defaultMileageRate; + + const {unit, rate} = mileageRate ?? {}; + const distance = TransactionUtils.getDistanceInMeters(transaction, unit); + const currency = mileageRate?.currency ?? policyCurrency; + const amount = DistanceRequestUtils.getDistanceRequestAmount(distance, unit ?? CONST.CUSTOM_UNITS.DISTANCE_UNIT_MILES, rate ?? 0); + IOU.setMoneyRequestAmount(transactionID, amount, currency); + + const participantAccountIDs: number[] | undefined = transaction?.participants?.map((participant) => participant.accountID ?? -1); + if (isSplitRequest && amount && currency && !isPolicyExpenseChat) { + IOU.setSplitShares(transaction, amount, currency ?? '', participantAccountIDs ?? []); + } + }, [report, transaction, transactionID, isSplitRequest, policy?.outputCurrency, reportID, customUnitRateID]); // For quick button actions, we'll skip the confirmation page unless the report is archived or this is a workspace // request and the workspace requires a category or a tag @@ -404,6 +437,7 @@ function IOURequestStepDistance({ ); const submitWaypoints = useCallback(() => { + setDistanceRequestData(); // If there is any error or loading state, don't let user go to next page. if (duplicateWaypointsError || atLeastTwoDifferentWaypointsError || hasRouteError || isLoadingRoute || (!isEditing && isLoading)) { setShouldShowAtLeastTwoDifferentWaypointsError(true); @@ -451,6 +485,7 @@ function IOURequestStepDistance({ transaction?.routes, report?.reportID, policy, + setDistanceRequestData, ]); const renderItem = useCallback( diff --git a/src/pages/iou/request/step/IOURequestStepParticipants.tsx b/src/pages/iou/request/step/IOURequestStepParticipants.tsx index b37fb8c9388..552ad4d54e3 100644 --- a/src/pages/iou/request/step/IOURequestStepParticipants.tsx +++ b/src/pages/iou/request/step/IOURequestStepParticipants.tsx @@ -8,11 +8,9 @@ import usePermissions from '@hooks/usePermissions'; import useThemeStyles from '@hooks/useThemeStyles'; import {READ_COMMANDS} from '@libs/API/types'; import DistanceRequestUtils from '@libs/DistanceRequestUtils'; -import type {MileageRate} from '@libs/DistanceRequestUtils'; import HttpUtils from '@libs/HttpUtils'; import * as IOUUtils from '@libs/IOUUtils'; import Navigation from '@libs/Navigation/Navigation'; -import * as PolicyUtils from '@libs/PolicyUtils'; import * as ReportUtils from '@libs/ReportUtils'; import * as TransactionUtils from '@libs/TransactionUtils'; import MoneyRequestParticipantsSelector from '@pages/iou/request/MoneyRequestParticipantsSelector'; @@ -43,7 +41,6 @@ function IOURequestStepParticipants({ }, transaction, skipConfirmation, - report, }: IOURequestStepParticipantsProps) { const participants = transaction?.participants; const {translate} = useLocalize(); @@ -56,7 +53,6 @@ function IOURequestStepParticipants({ const numberOfParticipants = useRef(participants?.length ?? 0); const iouRequestType = TransactionUtils.getRequestType(transaction); const isSplitRequest = iouType === CONST.IOU.TYPE.SPLIT; - const isDistanceRequest = iouRequestType === CONST.IOU.REQUEST_TYPE.DISTANCE; const headerTitle = useMemo(() => { if (action === CONST.IOU.ACTION.CATEGORIZE) { return translate('iou.categorize'); @@ -119,37 +115,6 @@ function IOURequestStepParticipants({ [iouType, reportID, transactionID, canUseP2PDistanceRequests], ); - // Sets `amount` and `split` share data before moving to the next step to avoid briefly showing `0.00` as the split share for participants - const setDistanceRequestData = useCallback( - (isPolicyExpenseChat: boolean) => { - // Get policy report based on transaction participants - const policyReport = transaction?.participants?.[0] ? ReportUtils.getReport(selectedReportID.current) : report; - - const policyID = IOU.getIOURequestPolicyID(transaction, policyReport); - const policy = PolicyUtils.getPolicy(report?.policyID ?? policyID); - const policyCurrency = policy?.outputCurrency ?? PolicyUtils.getPersonalPolicy()?.outputCurrency ?? CONST.CURRENCY.USD; - - const customUnitRateID = TransactionUtils.getRateID(transaction) ?? '-1'; - const mileageRates = DistanceRequestUtils.getMileageRates(policy); - const defaultMileageRate = DistanceRequestUtils.getDefaultMileageRate(policy); - const mileageRate: MileageRate = TransactionUtils.isCustomUnitRateIDForP2P(transaction) - ? DistanceRequestUtils.getRateForP2P(policyCurrency) - : mileageRates?.[customUnitRateID] ?? defaultMileageRate; - - const {unit, rate} = mileageRate ?? {}; - const distance = TransactionUtils.getDistanceInMeters(transaction, unit); - const currency = mileageRate?.currency ?? policyCurrency; - const amount = DistanceRequestUtils.getDistanceRequestAmount(distance, unit ?? CONST.CUSTOM_UNITS.DISTANCE_UNIT_MILES, rate ?? 0); - IOU.setMoneyRequestAmount(transactionID, amount, currency); - - const participantAccountIDs: number[] | undefined = transaction?.participants?.map((participant) => participant.accountID ?? -1); - if (isSplitRequest && amount && currency && !isPolicyExpenseChat) { - IOU.setSplitShares(transaction, amount, currency ?? '', participantAccountIDs ?? []); - } - }, - [report, transaction, transactionID, isSplitRequest], - ); - const goToNextStep = useCallback(() => { const isCategorizing = action === CONST.IOU.ACTION.CATEGORIZE; const isShareAction = action === CONST.IOU.ACTION.SHARE; @@ -160,10 +125,6 @@ function IOURequestStepParticipants({ IOU.setSplitShares(transaction, transaction.amount, transaction.currency, participantAccountIDs); } - if (isDistanceRequest) { - setDistanceRequestData(!!isPolicyExpenseChat); - } - IOU.setMoneyRequestTag(transactionID, ''); IOU.setMoneyRequestCategory(transactionID, ''); if ((isCategorizing || isShareAction) && numberOfParticipants.current === 0) { @@ -177,7 +138,7 @@ function IOURequestStepParticipants({ } else { Navigation.navigate(iouConfirmationPageRoute); } - }, [iouType, transactionID, transaction, reportID, action, participants, setDistanceRequestData, isDistanceRequest]); + }, [iouType, transactionID, transaction, reportID, action, participants]); const navigateBack = useCallback(() => { IOUUtils.navigateToStartMoneyRequestStep(iouRequestType, iouType, transactionID, reportID, action); From 35af9ce56471e5dbc21c4916010067230525e9a9 Mon Sep 17 00:00:00 2001 From: krishna2323 Date: Wed, 2 Oct 2024 06:44:42 +0530 Subject: [PATCH 023/121] fix: empty participants issue. Signed-off-by: krishna2323 --- .../request/step/IOURequestStepDistance.tsx | 64 ++++++++++--------- 1 file changed, 34 insertions(+), 30 deletions(-) diff --git a/src/pages/iou/request/step/IOURequestStepDistance.tsx b/src/pages/iou/request/step/IOURequestStepDistance.tsx index 4deac6e7ab1..ee6a0cd2c52 100644 --- a/src/pages/iou/request/step/IOURequestStepDistance.tsx +++ b/src/pages/iou/request/step/IOURequestStepDistance.tsx @@ -37,6 +37,7 @@ import ONYXKEYS from '@src/ONYXKEYS'; import ROUTES from '@src/ROUTES'; import type SCREENS from '@src/SCREENS'; import type * as OnyxTypes from '@src/types/onyx'; +import type {Participant} from '@src/types/onyx/IOU'; import type {Errors} from '@src/types/onyx/OnyxCommon'; import type {Waypoint, WaypointCollection} from '@src/types/onyx/Transaction'; import StepScreenWrapper from './StepScreenWrapper'; @@ -125,34 +126,37 @@ function IOURequestStepDistance({ const customUnitRateID = TransactionUtils.getRateID(transaction) ?? '-1'; // Sets `amount` and `split` share data before moving to the next step to avoid briefly showing `0.00` as the split share for participants - const setDistanceRequestData = useCallback(() => { - // Get policy report based on transaction participants - const participants = transaction?.participants; - const isPolicyExpenseChat = participants?.some((participant) => participant.isPolicyExpenseChat); - const selectedReportID = participants?.length === 1 ? participants[0]?.reportID ?? reportID : reportID; - const policyReport = transaction?.participants?.[0] ? ReportUtils.getReport(selectedReportID) : report; - - const policyID2 = IOU.getIOURequestPolicyID(transaction, policyReport); - const policy2 = PolicyUtils.getPolicy(report?.policyID ?? policyID2); - const policyCurrency = policy?.outputCurrency ?? PolicyUtils.getPersonalPolicy()?.outputCurrency ?? CONST.CURRENCY.USD; - - const mileageRates = DistanceRequestUtils.getMileageRates(policy2); - const defaultMileageRate = DistanceRequestUtils.getDefaultMileageRate(policy2); - const mileageRate: MileageRate = TransactionUtils.isCustomUnitRateIDForP2P(transaction) - ? DistanceRequestUtils.getRateForP2P(policyCurrency) - : mileageRates?.[customUnitRateID] ?? defaultMileageRate; - - const {unit, rate} = mileageRate ?? {}; - const distance = TransactionUtils.getDistanceInMeters(transaction, unit); - const currency = mileageRate?.currency ?? policyCurrency; - const amount = DistanceRequestUtils.getDistanceRequestAmount(distance, unit ?? CONST.CUSTOM_UNITS.DISTANCE_UNIT_MILES, rate ?? 0); - IOU.setMoneyRequestAmount(transactionID, amount, currency); - - const participantAccountIDs: number[] | undefined = transaction?.participants?.map((participant) => participant.accountID ?? -1); - if (isSplitRequest && amount && currency && !isPolicyExpenseChat) { - IOU.setSplitShares(transaction, amount, currency ?? '', participantAccountIDs ?? []); - } - }, [report, transaction, transactionID, isSplitRequest, policy?.outputCurrency, reportID, customUnitRateID]); + const setDistanceRequestData = useCallback( + (participants: Participant[]) => { + // Get policy report based on transaction participants + const isPolicyExpenseChat = participants?.some((participant) => participant.isPolicyExpenseChat); + const selectedReportID = participants?.length === 1 ? participants[0]?.reportID ?? reportID : reportID; + const policyReport = participants?.[0] ? ReportUtils.getReport(selectedReportID) : report; + + const policyID2 = IOU.getIOURequestPolicyID(transaction, policyReport); + const policy2 = PolicyUtils.getPolicy(report?.policyID ?? policyID2); + const policyCurrency = policy?.outputCurrency ?? PolicyUtils.getPersonalPolicy()?.outputCurrency ?? CONST.CURRENCY.USD; + + const mileageRates = DistanceRequestUtils.getMileageRates(policy2); + const defaultMileageRate = DistanceRequestUtils.getDefaultMileageRate(policy2); + const mileageRate: MileageRate = TransactionUtils.isCustomUnitRateIDForP2P(transaction) + ? DistanceRequestUtils.getRateForP2P(policyCurrency) + : mileageRates?.[customUnitRateID] ?? defaultMileageRate; + + const {unit, rate} = mileageRate ?? {}; + const distance = TransactionUtils.getDistanceInMeters(transaction, unit); + const currency = mileageRate?.currency ?? policyCurrency; + const amount = DistanceRequestUtils.getDistanceRequestAmount(distance, unit ?? CONST.CUSTOM_UNITS.DISTANCE_UNIT_MILES, rate ?? 0); + IOU.setMoneyRequestAmount(transactionID, amount, currency); + + const participantAccountIDs: number[] | undefined = participants?.map((participant) => Number(participant.accountID ?? -1)); + if (isSplitRequest && amount && currency && !isPolicyExpenseChat) { + console.log('TRUE', transaction, amount, currency ?? '', participantAccountIDs ?? []); + IOU.setSplitShares(transaction, amount, currency ?? '', participantAccountIDs ?? []); + } + }, + [report, transaction, transactionID, isSplitRequest, policy?.outputCurrency, reportID, customUnitRateID], + ); // For quick button actions, we'll skip the confirmation page unless the report is archived or this is a workspace // request and the workspace requires a category or a tag @@ -287,6 +291,7 @@ function IOURequestStepDistance({ const participantAccountID = participant?.accountID ?? -1; return participantAccountID ? OptionsListUtils.getParticipantsOption(participant, personalDetails) : OptionsListUtils.getReportOption(participant); }); + setDistanceRequestData(participants); if (shouldSkipConfirmation) { if (iouType === CONST.IOU.TYPE.SPLIT) { IOU.splitBill({ @@ -389,6 +394,7 @@ function IOURequestStepDistance({ iouRequestType, reportNameValuePairs, customUnitRateID, + setDistanceRequestData, ]); const getError = () => { @@ -437,7 +443,6 @@ function IOURequestStepDistance({ ); const submitWaypoints = useCallback(() => { - setDistanceRequestData(); // If there is any error or loading state, don't let user go to next page. if (duplicateWaypointsError || atLeastTwoDifferentWaypointsError || hasRouteError || isLoadingRoute || (!isEditing && isLoading)) { setShouldShowAtLeastTwoDifferentWaypointsError(true); @@ -485,7 +490,6 @@ function IOURequestStepDistance({ transaction?.routes, report?.reportID, policy, - setDistanceRequestData, ]); const renderItem = useCallback( From 98ca6ac74b02b7217ea1f6b316dfe6a868a690fd Mon Sep 17 00:00:00 2001 From: krishna2323 Date: Wed, 2 Oct 2024 06:46:34 +0530 Subject: [PATCH 024/121] remove console log. Signed-off-by: krishna2323 --- src/pages/iou/request/step/IOURequestStepDistance.tsx | 1 - 1 file changed, 1 deletion(-) diff --git a/src/pages/iou/request/step/IOURequestStepDistance.tsx b/src/pages/iou/request/step/IOURequestStepDistance.tsx index ee6a0cd2c52..63af4a7fcfe 100644 --- a/src/pages/iou/request/step/IOURequestStepDistance.tsx +++ b/src/pages/iou/request/step/IOURequestStepDistance.tsx @@ -151,7 +151,6 @@ function IOURequestStepDistance({ const participantAccountIDs: number[] | undefined = participants?.map((participant) => Number(participant.accountID ?? -1)); if (isSplitRequest && amount && currency && !isPolicyExpenseChat) { - console.log('TRUE', transaction, amount, currency ?? '', participantAccountIDs ?? []); IOU.setSplitShares(transaction, amount, currency ?? '', participantAccountIDs ?? []); } }, From 3caf9791a2ccc50beecb52160a8da7ee3bebeb0f Mon Sep 17 00:00:00 2001 From: krishna2323 Date: Wed, 2 Oct 2024 06:52:57 +0530 Subject: [PATCH 025/121] fix: lint issues. Signed-off-by: krishna2323 --- src/pages/iou/request/step/IOURequestStepConfirmation.tsx | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/src/pages/iou/request/step/IOURequestStepConfirmation.tsx b/src/pages/iou/request/step/IOURequestStepConfirmation.tsx index b08f9a6ced5..8555c99d1bf 100644 --- a/src/pages/iou/request/step/IOURequestStepConfirmation.tsx +++ b/src/pages/iou/request/step/IOURequestStepConfirmation.tsx @@ -87,11 +87,11 @@ function IOURequestStepConfirmation({ const isSubmittingFromTrackExpense = action === CONST.IOU.ACTION.SUBMIT; const isMovingTransactionFromTrackExpense = IOUUtils.isMovingTransactionFromTrackExpense(action); const payeePersonalDetails = useMemo(() => { - if (personalDetails?.[transaction?.splitPayerAccountIDs?.[0] ?? -1]) { - return personalDetails?.[transaction?.splitPayerAccountIDs?.[0] ?? -1]; + if (personalDetails?.[transaction?.splitPayerAccountIDs?.at(0) ?? -1]) { + return personalDetails?.[transaction?.splitPayerAccountIDs?.at(0) ?? -1]; } - const participant = transaction?.participants?.find((val) => val.accountID === (transaction?.splitPayerAccountIDs?.[0] ?? -1)); + const participant = transaction?.participants?.find((val) => val.accountID === (transaction?.splitPayerAccountIDs?.at(0) ?? -1)); return { login: participant?.login ?? '', From 5e4934a1fbebef3567ad489d99ee49d51a64dd66 Mon Sep 17 00:00:00 2001 From: Wojciech Lewicki Date: Wed, 2 Oct 2024 15:10:10 +0200 Subject: [PATCH 026/121] fix: update library and patch for react-native-config --- ios/NewExpensify.xcodeproj/project.pbxproj | 6 +- ios/Podfile.lock | 27 +- package-lock.json | 9 +- package.json | 2 +- ....patch => react-native-config+1.5.3.patch} | 257 ++++++++---------- 5 files changed, 135 insertions(+), 166 deletions(-) rename patches/{react-native-config+1.5.0.patch => react-native-config+1.5.3.patch} (71%) diff --git a/ios/NewExpensify.xcodeproj/project.pbxproj b/ios/NewExpensify.xcodeproj/project.pbxproj index 1a29a275b95..65a18af19a7 100644 --- a/ios/NewExpensify.xcodeproj/project.pbxproj +++ b/ios/NewExpensify.xcodeproj/project.pbxproj @@ -43,7 +43,7 @@ D27CE6B77196EF3EF450EEAC /* PrivacyInfo.xcprivacy in Resources */ = {isa = PBXBuildFile; fileRef = 0D3F9E814828D91464DF9D35 /* PrivacyInfo.xcprivacy */; }; DD79042B2792E76D004484B4 /* RCTBootSplash.mm in Sources */ = {isa = PBXBuildFile; fileRef = DD79042A2792E76D004484B4 /* RCTBootSplash.mm */; }; DDCB2E57F334C143AC462B43 /* ExpoModulesProvider.swift in Sources */ = {isa = PBXBuildFile; fileRef = 4D20D83B0E39BA6D21761E72 /* ExpoModulesProvider.swift */; }; - E51DC681C7DEE40AEBDDFBFE /* (null) in Frameworks */ = {isa = PBXBuildFile; }; + E51DC681C7DEE40AEBDDFBFE /* BuildFile in Frameworks */ = {isa = PBXBuildFile; }; E9DF872D2525201700607FDC /* AirshipConfig.plist in Resources */ = {isa = PBXBuildFile; fileRef = E9DF872C2525201700607FDC /* AirshipConfig.plist */; }; ED222ED90E074A5481A854FA /* ExpensifyNeue-BoldItalic.otf in Resources */ = {isa = PBXBuildFile; fileRef = 8B28D84EF339436DBD42A203 /* ExpensifyNeue-BoldItalic.otf */; }; F0C450EA2705020500FD2970 /* colors.json in Resources */ = {isa = PBXBuildFile; fileRef = F0C450E92705020500FD2970 /* colors.json */; }; @@ -176,8 +176,8 @@ buildActionMask = 2147483647; files = ( 383643682B6D4AE2005BB9AE /* DeviceCheck.framework in Frameworks */, - E51DC681C7DEE40AEBDDFBFE /* (null) in Frameworks */, - E51DC681C7DEE40AEBDDFBFE /* (null) in Frameworks */, + E51DC681C7DEE40AEBDDFBFE /* BuildFile in Frameworks */, + E51DC681C7DEE40AEBDDFBFE /* BuildFile in Frameworks */, 8744C5400E24E379441C04A4 /* libPods-NewExpensify.a in Frameworks */, ); runOnlyForDeploymentPostprocessing = 0; diff --git a/ios/Podfile.lock b/ios/Podfile.lock index 69989797ecb..1d6896f57af 100644 --- a/ios/Podfile.lock +++ b/ios/Podfile.lock @@ -1606,16 +1606,29 @@ PODS: - ReactCommon/turbomodule/bridging - ReactCommon/turbomodule/core - Yoga - - react-native-config (1.5.0): - - react-native-config/App (= 1.5.0) - - react-native-config/App (1.5.0): - - RCT-Folly + - react-native-config (1.5.3): + - react-native-config/App (= 1.5.3) + - react-native-config/App (1.5.3): + - DoubleConversion + - glog + - hermes-engine + - RCT-Folly (= 2024.01.01.00) - RCTRequired - RCTTypeSafety - - React - - React-Codegen + - React-Core + - React-debug + - React-Fabric + - React-featureflags + - React-graphics + - React-ImageManager + - React-NativeModulesApple - React-RCTFabric + - React-rendererdebug + - React-utils + - ReactCodegen + - ReactCommon/turbomodule/bridging - ReactCommon/turbomodule/core + - Yoga - react-native-document-picker (9.3.1): - DoubleConversion - glog @@ -3173,7 +3186,7 @@ SPEC CHECKSUMS: react-native-airship: e10f6823d8da49bbcb2db4bdb16ff954188afccc react-native-blob-util: 221c61c98ae507b758472ac4d2d489119d1a6c44 react-native-cameraroll: 478a0c1fcdd39f08f6ac272b7ed06e92b2c7c129 - react-native-config: 5ce986133b07fc258828b20b9506de0e683efc1c + react-native-config: 742a9e0a378a78d0eaff1fb3477d8c0ae222eb51 react-native-document-picker: e9d83c149bdd72dc01cf8dcb8df0389c6bd5fddb react-native-geolocation: b9bd12beaf0ebca61a01514517ca8455bd26fa06 react-native-image-picker: f8a13ff106bcc7eb00c71ce11fdc36aac2a44440 diff --git a/package-lock.json b/package-lock.json index 323a78bed2f..b970fe377c1 100644 --- a/package-lock.json +++ b/package-lock.json @@ -77,7 +77,7 @@ "react-native-android-location-enabler": "^2.0.1", "react-native-blob-util": "0.19.4", "react-native-collapsible": "^1.6.2", - "react-native-config": "1.5.0", + "react-native-config": "1.5.3", "react-native-dev-menu": "^4.1.1", "react-native-device-info": "10.3.1", "react-native-document-picker": "^9.3.1", @@ -34432,11 +34432,10 @@ } }, "node_modules/react-native-config": { - "version": "1.5.0", - "license": "MIT", + "version": "1.5.3", + "resolved": "https://registry.npmjs.org/react-native-config/-/react-native-config-1.5.3.tgz", + "integrity": "sha512-3D05Abgk5DfDw9w258EzXvX5AkU7eqj3u9H0H0L4gUga4nYg/zuupcrpGbpF4QeXBcJ84jjs6g8JaEP6VBT7Pg==", "peerDependencies": { - "react": "*", - "react-native": "*", "react-native-windows": ">=0.61" }, "peerDependenciesMeta": { diff --git a/package.json b/package.json index f4ac5dbe8c3..0d2fae17a59 100644 --- a/package.json +++ b/package.json @@ -134,7 +134,7 @@ "react-native-android-location-enabler": "^2.0.1", "react-native-blob-util": "0.19.4", "react-native-collapsible": "^1.6.2", - "react-native-config": "1.5.0", + "react-native-config": "1.5.3", "react-native-dev-menu": "^4.1.1", "react-native-device-info": "10.3.1", "react-native-document-picker": "^9.3.1", diff --git a/patches/react-native-config+1.5.0.patch b/patches/react-native-config+1.5.3.patch similarity index 71% rename from patches/react-native-config+1.5.0.patch rename to patches/react-native-config+1.5.3.patch index 4b5a597de4d..d2c09370503 100644 --- a/patches/react-native-config+1.5.0.patch +++ b/patches/react-native-config+1.5.3.patch @@ -1,68 +1,11 @@ -diff --git a/node_modules/react-native-config/README.md b/node_modules/react-native-config/README.md -index 8424402..ca29e39 100644 ---- a/node_modules/react-native-config/README.md -+++ b/node_modules/react-native-config/README.md -@@ -78,13 +78,13 @@ if cocoapods are used in the project then pod has to be installed as well: - **MainApplication.java** - - ```diff -- + import com.lugg.ReactNativeConfig.ReactNativeConfigPackage; -+ + import com.lugg.RNCConfig.RNCConfigPackage; - - @Override - protected List getPackages() { - return Arrays.asList( - new MainReactPackage() -- + new ReactNativeConfigPackage() -+ + new RNCConfigPackage() - ); - } - ``` diff --git a/node_modules/react-native-config/android/build.gradle b/node_modules/react-native-config/android/build.gradle -index c8f7fd4..86b3e1a 100644 +index d3bdb07..1629423 100644 --- a/node_modules/react-native-config/android/build.gradle +++ b/node_modules/react-native-config/android/build.gradle -@@ -15,6 +15,55 @@ def safeExtGet(prop, fallback) { +@@ -15,6 +15,18 @@ def safeExtGet(prop, fallback) { rootProject.ext.has(prop) ? rootProject.ext.get(prop) : fallback } -+def resolveReactNativeDirectory() { -+ def reactNativeLocation = safeExtGet("REACT_NATIVE_NODE_MODULES_DIR", null) -+ if (reactNativeLocation != null) { -+ return file(reactNativeLocation) -+ } -+ -+ // monorepo workaround -+ // react-native can be hoisted or in project's own node_modules -+ def reactNativeFromProjectNodeModules = file("${rootProject.projectDir}/../node_modules/react-native") -+ if (reactNativeFromProjectNodeModules.exists()) { -+ return reactNativeFromProjectNodeModules -+ } -+ -+ def reactNativeFromNodeModulesWithRNCConfig = file("${projectDir}/../../react-native") -+ if (reactNativeFromNodeModulesWithRNCConfig.exists()) { -+ return reactNativeFromNodeModulesWithRNCConfig -+ } -+ -+ throw new Exception( -+ "[react-native-config] Unable to resolve react-native location in " + -+ "node_modules. You should add project extension property (in app/build.gradle) " + -+ "`REACT_NATIVE_NODE_MODULES_DIR` with path to react-native." -+ ) -+} -+ -+def getReactNativeMinorVersion() { -+ def REACT_NATIVE_DIR = resolveReactNativeDirectory() -+ -+ def reactProperties = new Properties() -+ file("$REACT_NATIVE_DIR/ReactAndroid/gradle.properties").withInputStream { reactProperties.load(it) } -+ -+ def REACT_NATIVE_VERSION = reactProperties.getProperty("VERSION_NAME") -+ def REACT_NATIVE_MINOR_VERSION = REACT_NATIVE_VERSION.startsWith("0.0.0-") ? 1000 : REACT_NATIVE_VERSION.split("\\.")[1].toInteger() -+ -+ return REACT_NATIVE_MINOR_VERSION -+} -+ +def isNewArchitectureEnabled() { + // To opt-in for the New Architecture, you can either: + // - Set `newArchEnabled` to true inside the `gradle.properties` file @@ -75,10 +18,10 @@ index c8f7fd4..86b3e1a 100644 + apply plugin: "com.facebook.react" +} + - android { - compileSdkVersion rootProject.ext.compileSdkVersion - -@@ -23,10 +72,23 @@ android { + def supportsNamespace() { + def parsed = com.android.Version.ANDROID_GRADLE_PLUGIN_VERSION.tokenize('.'); + def major = parsed[0].toInteger(); +@@ -44,10 +56,23 @@ android { targetSdkVersion rootProject.ext.targetSdkVersion versionCode 1 versionName "1.0" @@ -102,44 +45,45 @@ index c8f7fd4..86b3e1a 100644 } repositories { -@@ -34,5 +96,9 @@ repositories { +@@ -55,5 +80,5 @@ repositories { } dependencies { - implementation "com.facebook.react:react-native:${safeExtGet("reactNative", "+")}" // from node_modules -+ if (isNewArchitectureEnabled() && getReactNativeMinorVersion() < 71) { -+ implementation project(":ReactAndroid") -+ } else { -+ implementation 'com.facebook.react:react-native:+' -+ } ++ implementation 'com.facebook.react:react-native:+' } diff --git a/node_modules/react-native-config/android/src/main/java/com/lugg/RNCConfig/RNCConfigModule.java b/node_modules/react-native-config/android/src/main/java/com/lugg/RNCConfig/RNCConfigModule.java -index 0b52515..bef2834 100644 +index 55b853b..2784795 100644 --- a/node_modules/react-native-config/android/src/main/java/com/lugg/RNCConfig/RNCConfigModule.java +++ b/node_modules/react-native-config/android/src/main/java/com/lugg/RNCConfig/RNCConfigModule.java -@@ -13,20 +13,32 @@ import java.lang.reflect.Field; +@@ -11,41 +11,58 @@ import java.lang.reflect.Field; import java.util.Map; import java.util.HashMap; -public class RNCConfigModule extends ReactContextBaseJavaModule { +public class RNCConfigModule extends NativeConfigModuleSpec { + public static final String NAME = "RNCConfigModule"; -+ - public RNCConfigModule(ReactApplicationContext reactContext) { - super(reactContext); - } - @Override - public String getName() { -- return "RNCConfigModule"; -+ return NAME; - } +- public RNCConfigModule(ReactApplicationContext reactContext) { +- super(reactContext); +- } ++ public RNCConfigModule(ReactApplicationContext reactContext) { ++ super(reactContext); ++ } - @Override -- public Map getConstants() { +- @Override +- public String getName() { +- return "RNCConfigModule"; +- } ++ @Override ++ public String getName() { ++ return NAME; ++ } ++ ++ @Override + public Map getTypedExportedConstants() { - final Map constants = new HashMap<>(); - ++ final Map constants = new HashMap<>(); ++ + // Codegen ensures that the constants defined in the module spec and in the native module implementation + // are consistent, which is tad problematic in this case, as the constants are dependant on the `.env` + // file. The simple workaround is to define a `constants` object that will contain actual constants. @@ -149,33 +93,64 @@ index 0b52515..bef2834 100644 + // we export { constants: { constant1: "value1", constant2: "value2" } } + // because of type safety on the new arch + final Map realConstants = new HashMap<>(); -+ - try { - Context context = getReactApplicationContext(); - int resId = context.getResources().getIdentifier("build_config_package", "string", context.getPackageName()); -@@ -40,7 +52,7 @@ public class RNCConfigModule extends ReactContextBaseJavaModule { - Field[] fields = clazz.getDeclaredFields(); - for(Field f: fields) { + +- @Override +- public Map getConstants() { +- final Map constants = new HashMap<>(); ++ try { ++ Context context = getReactApplicationContext(); ++ int resId = context.getResources().getIdentifier("build_config_package", "string", context.getPackageName()); ++ String className; ++ try { ++ className = context.getString(resId); ++ } catch (Resources.NotFoundException e) { ++ className = getReactApplicationContext().getApplicationContext().getPackageName(); ++ } ++ Class clazz = Class.forName(className + ".BuildConfig"); ++ Field[] fields = clazz.getDeclaredFields(); ++ for(Field f: fields) { try { -- constants.put(f.getName(), f.get(null)); +- Context context = getReactApplicationContext(); +- int resId = context.getResources().getIdentifier("build_config_package", "string", context.getPackageName()); +- String className; +- try { +- className = context.getString(resId); +- } catch (Resources.NotFoundException e) { +- className = getReactApplicationContext().getApplicationContext().getPackageName(); +- } +- Class clazz = Class.forName(className + ".BuildConfig"); +- Field[] fields = clazz.getDeclaredFields(); +- for (Field f : fields) { +- try { +- constants.put(f.getName(), f.get(null)); +- } catch (IllegalAccessException e) { +- Log.d("ReactNative", "ReactConfig: Could not access BuildConfig field " + f.getName()); +- } +- } +- } catch (ClassNotFoundException e) { +- Log.d("ReactNative", "ReactConfig: Could not find BuildConfig class"); + realConstants.put(f.getName(), f.get(null)); } - catch (IllegalAccessException e) { - Log.d("ReactNative", "ReactConfig: Could not access BuildConfig field " + f.getName()); -@@ -51,6 +63,8 @@ public class RNCConfigModule extends ReactContextBaseJavaModule { - Log.d("ReactNative", "ReactConfig: Could not find BuildConfig class"); +- return constants; ++ catch (IllegalAccessException e) { ++ Log.d("ReactNative", "ReactConfig: Could not access BuildConfig field " + f.getName()); ++ } ++ } ++ } ++ catch (ClassNotFoundException e) { ++ Log.d("ReactNative", "ReactConfig: Could not find BuildConfig class"); } - ++ + constants.put("constants", realConstants); + - return constants; - } ++ return constants; ++ } } diff --git a/node_modules/react-native-config/android/src/main/java/com/lugg/RNCConfig/RNCConfigPackage.java b/node_modules/react-native-config/android/src/main/java/com/lugg/RNCConfig/RNCConfigPackage.java -index 9251c09..2edd797 100644 +index 599a81a..2edd797 100644 --- a/node_modules/react-native-config/android/src/main/java/com/lugg/RNCConfig/RNCConfigPackage.java +++ b/node_modules/react-native-config/android/src/main/java/com/lugg/RNCConfig/RNCConfigPackage.java -@@ -1,29 +1,42 @@ +@@ -1,27 +1,42 @@ package com.lugg.RNCConfig; -import com.facebook.react.ReactPackage; @@ -184,26 +159,24 @@ index 9251c09..2edd797 100644 import com.facebook.react.bridge.NativeModule; import com.facebook.react.bridge.ReactApplicationContext; -import com.facebook.react.uimanager.ViewManager; -+import com.facebook.react.module.model.ReactModuleInfo; -+import com.facebook.react.module.model.ReactModuleInfoProvider; - -import java.util.Arrays; -import java.util.Collections; -import java.util.List; ++import com.facebook.react.module.model.ReactModuleInfo; ++import com.facebook.react.module.model.ReactModuleInfoProvider; + +-public class RNCConfigPackage implements ReactPackage { +import java.util.HashMap; +import java.util.Map; --public class RNCConfigPackage implements ReactPackage { - @Override - public List createNativeModules(ReactApplicationContext reactContext) { -- return Arrays.asList( -- new RNCConfigModule(reactContext) -- ); +- return Arrays.asList(new RNCConfigModule(reactContext)); - } +public class RNCConfigPackage extends TurboReactPackage { -- public List> createJSModules() { -- return Collections.emptyList(); +- public List> createJSModules() { +- return Collections.emptyList(); + @Override + public NativeModule getModule(String name, ReactApplicationContext reactContext) { + if (name.equals(RNCConfigModule.NAME)) { @@ -336,31 +309,29 @@ index 70866c4..a8f3624 100644 -export const Config = NativeModules.RNCConfigModule || {} export default Config; -diff --git a/node_modules/react-native-config/ios/ReactNativeConfig/GeneratedDotEnv.m b/node_modules/react-native-config/ios/ReactNativeConfig/GeneratedDotEnv.m -index 04a2f3d..59df625 100644 ---- a/node_modules/react-native-config/ios/ReactNativeConfig/GeneratedDotEnv.m -+++ b/node_modules/react-native-config/ios/ReactNativeConfig/GeneratedDotEnv.m -@@ -1 +1 @@ -- #define DOT_ENV @{ }; -+ #define DOT_ENV @{ @"ENV":@"dev",@"API_URL":@"http://localhost" }; diff --git a/node_modules/react-native-config/ios/ReactNativeConfig/RNCConfigModule.h b/node_modules/react-native-config/ios/ReactNativeConfig/RNCConfigModule.h -index 755d103..5341aca 100644 +index 755d103..4e4c564 100644 --- a/node_modules/react-native-config/ios/ReactNativeConfig/RNCConfigModule.h +++ b/node_modules/react-native-config/ios/ReactNativeConfig/RNCConfigModule.h -@@ -1,3 +1,9 @@ +@@ -1,12 +1,15 @@ +-#if __has_include() +-#import +-#elif __has_include("React/RCTBridgeModule.h") +-#import "React/RCTBridgeModule.h" +#ifdef RCT_NEW_ARCH_ENABLED +#import "RNCConfigSpec.h" -+ -+@interface RNCConfigModule : NSObject -+#else -+ - #if __has_include() - #import - #elif __has_include("React/RCTBridgeModule.h") -@@ -7,6 +13,7 @@ - #endif + #else +-#import "RCTBridgeModule.h" +-#endif ++#import ++#endif // RCT_NEW_ARCH_ENABLED - @interface RNCConfigModule : NSObject +-@interface RNCConfigModule : NSObject ++@interface RNCConfigModule : NSObject ++#ifdef RCT_NEW_ARCH_ENABLED ++ ++#else ++ +#endif // RCT_NEW_ARCH_ENABLED + (NSDictionary *)env; @@ -440,10 +411,10 @@ index 0000000..1cacb65 + +@end diff --git a/node_modules/react-native-config/package.json b/node_modules/react-native-config/package.json -index b4d1fba..0a018a7 100644 +index f758725..f338b41 100644 --- a/node_modules/react-native-config/package.json +++ b/node_modules/react-native-config/package.json -@@ -26,6 +26,7 @@ +@@ -27,6 +27,7 @@ "android/", "ios/", "windows/", @@ -451,8 +422,8 @@ index b4d1fba..0a018a7 100644 "index.js", "index.d.ts", "react-native-config.podspec", -@@ -38,11 +39,21 @@ - "semantic-release": "^17.0.4" +@@ -39,11 +40,21 @@ + "semantic-release": "^19.0.5" }, "peerDependencies": { + "react": "*", @@ -474,7 +445,7 @@ index b4d1fba..0a018a7 100644 } } diff --git a/node_modules/react-native-config/react-native-config.podspec b/node_modules/react-native-config/react-native-config.podspec -index 35313d4..56bce4a 100644 +index 449b970..88b14c5 100644 --- a/node_modules/react-native-config/react-native-config.podspec +++ b/node_modules/react-native-config/react-native-config.podspec @@ -4,6 +4,8 @@ require 'json' @@ -486,7 +457,7 @@ index 35313d4..56bce4a 100644 Pod::Spec.new do |s| s.name = 'react-native-config' s.version = package['version'] -@@ -33,8 +35,27 @@ HOST_PATH="$SRCROOT/../.." +@@ -35,8 +37,13 @@ HOST_PATH="$SRCROOT/../.." s.default_subspec = 'App' s.subspec 'App' do |app| @@ -495,21 +466,7 @@ index 35313d4..56bce4a 100644 + app.source_files = 'ios/**/*.{h,m,mm}' + + if fabric_enabled -+ folly_compiler_flags = '-DFOLLY_NO_CONFIG -DFOLLY_MOBILE=1 -DFOLLY_USE_LIBCPP=1 -Wno-comma -Wno-shorten-64-to-32' -+ -+ app.pod_target_xcconfig = { -+ 'HEADER_SEARCH_PATHS' => '"$(PODS_ROOT)/boost" "$(PODS_ROOT)/boost-for-react-native" "$(PODS_ROOT)/RCT-Folly"', -+ 'CLANG_CXX_LANGUAGE_STANDARD' => 'c++17', -+ } -+ app.compiler_flags = folly_compiler_flags + ' -DRCT_NEW_ARCH_ENABLED' -+ -+ app.dependency "React" -+ app.dependency "React-RCTFabric" # This is for fabric component -+ app.dependency "React-Codegen" -+ app.dependency "RCT-Folly" -+ app.dependency "RCTRequired" -+ app.dependency "RCTTypeSafety" -+ app.dependency "ReactCommon/turbomodule/core" ++ install_modules_dependencies(app) + else + app.dependency 'React-Core' + end From 9fc08a5736fb423b6c78dbd8f462cedbe1322f20 Mon Sep 17 00:00:00 2001 From: Carlos Martins Date: Wed, 2 Oct 2024 17:58:36 -0600 Subject: [PATCH 027/121] add no operator --- src/libs/SearchParser/searchParser.js | 107 +++++++++++++---------- src/libs/SearchParser/searchParser.peggy | 1 + 2 files changed, 60 insertions(+), 48 deletions(-) diff --git a/src/libs/SearchParser/searchParser.js b/src/libs/SearchParser/searchParser.js index 622c3f5f3c4..f461394076b 100644 --- a/src/libs/SearchParser/searchParser.js +++ b/src/libs/SearchParser/searchParser.js @@ -200,12 +200,13 @@ function peg$parse(input, options) { var peg$c17 = "cardID"; var peg$c18 = "from"; var peg$c19 = "expenseType"; - var peg$c20 = "type"; - var peg$c21 = "status"; - var peg$c22 = "sortBy"; - var peg$c23 = "sortOrder"; - var peg$c24 = "policyID"; - var peg$c25 = "\""; + var peg$c20 = "no"; + var peg$c21 = "type"; + var peg$c22 = "status"; + var peg$c23 = "sortBy"; + var peg$c24 = "sortOrder"; + var peg$c25 = "policyID"; + var peg$c26 = "\""; var peg$r0 = /^[:=]/; var peg$r1 = /^[^"\r\n]/; @@ -235,19 +236,20 @@ function peg$parse(input, options) { var peg$e20 = peg$literalExpectation("cardID", false); var peg$e21 = peg$literalExpectation("from", false); var peg$e22 = peg$literalExpectation("expenseType", false); - var peg$e23 = peg$otherExpectation("default key"); - var peg$e24 = peg$literalExpectation("type", false); - var peg$e25 = peg$literalExpectation("status", false); - var peg$e26 = peg$literalExpectation("sortBy", false); - var peg$e27 = peg$literalExpectation("sortOrder", false); - var peg$e28 = peg$literalExpectation("policyID", false); - var peg$e29 = peg$otherExpectation("quote"); - var peg$e30 = peg$literalExpectation("\"", false); - var peg$e31 = peg$classExpectation(["\"", "\r", "\n"], true, false); - var peg$e32 = peg$otherExpectation("word"); - var peg$e33 = peg$classExpectation([["A", "Z"], ["a", "z"], ["0", "9"], "_", "@", ".", "/", "#", "&", "+", "-", "\\", "'", ",", ";"], false, false); - var peg$e34 = peg$otherExpectation("whitespace"); - var peg$e35 = peg$classExpectation([" ", "\t", "\r", "\n"], false, false); + var peg$e23 = peg$literalExpectation("no", false); + var peg$e24 = peg$otherExpectation("default key"); + var peg$e25 = peg$literalExpectation("type", false); + var peg$e26 = peg$literalExpectation("status", false); + var peg$e27 = peg$literalExpectation("sortBy", false); + var peg$e28 = peg$literalExpectation("sortOrder", false); + var peg$e29 = peg$literalExpectation("policyID", false); + var peg$e30 = peg$otherExpectation("quote"); + var peg$e31 = peg$literalExpectation("\"", false); + var peg$e32 = peg$classExpectation(["\"", "\r", "\n"], true, false); + var peg$e33 = peg$otherExpectation("word"); + var peg$e34 = peg$classExpectation([["A", "Z"], ["a", "z"], ["0", "9"], "_", "@", ".", "/", "#", "&", "+", "-", "\\", "'", ",", ";"], false, false); + var peg$e35 = peg$otherExpectation("whitespace"); + var peg$e36 = peg$classExpectation([" ", "\t", "\r", "\n"], false, false); var peg$f0 = function(filters) { return applyDefaults(filters); }; var peg$f1 = function(head, tail) { @@ -856,6 +858,15 @@ function peg$parse(input, options) { s1 = peg$FAILED; if (peg$silentFails === 0) { peg$fail(peg$e14); } } + if (s1 === peg$FAILED) { + if (input.substr(peg$currPos, 2) === peg$c20) { + s1 = peg$c20; + peg$currPos += 2; + } else { + s1 = peg$FAILED; + if (peg$silentFails === 0) { peg$fail(peg$e23); } + } + } } } } @@ -891,44 +902,44 @@ function peg$parse(input, options) { peg$silentFails++; s0 = peg$currPos; - if (input.substr(peg$currPos, 4) === peg$c20) { - s1 = peg$c20; + if (input.substr(peg$currPos, 4) === peg$c21) { + s1 = peg$c21; peg$currPos += 4; } else { s1 = peg$FAILED; - if (peg$silentFails === 0) { peg$fail(peg$e24); } + if (peg$silentFails === 0) { peg$fail(peg$e25); } } if (s1 === peg$FAILED) { - if (input.substr(peg$currPos, 6) === peg$c21) { - s1 = peg$c21; + if (input.substr(peg$currPos, 6) === peg$c22) { + s1 = peg$c22; peg$currPos += 6; } else { s1 = peg$FAILED; - if (peg$silentFails === 0) { peg$fail(peg$e25); } + if (peg$silentFails === 0) { peg$fail(peg$e26); } } if (s1 === peg$FAILED) { - if (input.substr(peg$currPos, 6) === peg$c22) { - s1 = peg$c22; + if (input.substr(peg$currPos, 6) === peg$c23) { + s1 = peg$c23; peg$currPos += 6; } else { s1 = peg$FAILED; - if (peg$silentFails === 0) { peg$fail(peg$e26); } + if (peg$silentFails === 0) { peg$fail(peg$e27); } } if (s1 === peg$FAILED) { - if (input.substr(peg$currPos, 9) === peg$c23) { - s1 = peg$c23; + if (input.substr(peg$currPos, 9) === peg$c24) { + s1 = peg$c24; peg$currPos += 9; } else { s1 = peg$FAILED; - if (peg$silentFails === 0) { peg$fail(peg$e27); } + if (peg$silentFails === 0) { peg$fail(peg$e28); } } if (s1 === peg$FAILED) { - if (input.substr(peg$currPos, 8) === peg$c24) { - s1 = peg$c24; + if (input.substr(peg$currPos, 8) === peg$c25) { + s1 = peg$c25; peg$currPos += 8; } else { s1 = peg$FAILED; - if (peg$silentFails === 0) { peg$fail(peg$e28); } + if (peg$silentFails === 0) { peg$fail(peg$e29); } } } } @@ -943,7 +954,7 @@ function peg$parse(input, options) { peg$silentFails--; if (s0 === peg$FAILED) { s1 = peg$FAILED; - if (peg$silentFails === 0) { peg$fail(peg$e23); } + if (peg$silentFails === 0) { peg$fail(peg$e24); } } return s0; @@ -984,11 +995,11 @@ function peg$parse(input, options) { peg$silentFails++; s0 = peg$currPos; if (input.charCodeAt(peg$currPos) === 34) { - s1 = peg$c25; + s1 = peg$c26; peg$currPos++; } else { s1 = peg$FAILED; - if (peg$silentFails === 0) { peg$fail(peg$e30); } + if (peg$silentFails === 0) { peg$fail(peg$e31); } } if (s1 !== peg$FAILED) { s2 = []; @@ -997,7 +1008,7 @@ function peg$parse(input, options) { peg$currPos++; } else { s3 = peg$FAILED; - if (peg$silentFails === 0) { peg$fail(peg$e31); } + if (peg$silentFails === 0) { peg$fail(peg$e32); } } while (s3 !== peg$FAILED) { s2.push(s3); @@ -1006,15 +1017,15 @@ function peg$parse(input, options) { peg$currPos++; } else { s3 = peg$FAILED; - if (peg$silentFails === 0) { peg$fail(peg$e31); } + if (peg$silentFails === 0) { peg$fail(peg$e32); } } } if (input.charCodeAt(peg$currPos) === 34) { - s3 = peg$c25; + s3 = peg$c26; peg$currPos++; } else { s3 = peg$FAILED; - if (peg$silentFails === 0) { peg$fail(peg$e30); } + if (peg$silentFails === 0) { peg$fail(peg$e31); } } if (s3 !== peg$FAILED) { peg$savedPos = s0; @@ -1030,7 +1041,7 @@ function peg$parse(input, options) { peg$silentFails--; if (s0 === peg$FAILED) { s1 = peg$FAILED; - if (peg$silentFails === 0) { peg$fail(peg$e29); } + if (peg$silentFails === 0) { peg$fail(peg$e30); } } return s0; @@ -1047,7 +1058,7 @@ function peg$parse(input, options) { peg$currPos++; } else { s2 = peg$FAILED; - if (peg$silentFails === 0) { peg$fail(peg$e33); } + if (peg$silentFails === 0) { peg$fail(peg$e34); } } if (s2 !== peg$FAILED) { while (s2 !== peg$FAILED) { @@ -1057,7 +1068,7 @@ function peg$parse(input, options) { peg$currPos++; } else { s2 = peg$FAILED; - if (peg$silentFails === 0) { peg$fail(peg$e33); } + if (peg$silentFails === 0) { peg$fail(peg$e34); } } } } else { @@ -1071,7 +1082,7 @@ function peg$parse(input, options) { peg$silentFails--; if (s0 === peg$FAILED) { s1 = peg$FAILED; - if (peg$silentFails === 0) { peg$fail(peg$e32); } + if (peg$silentFails === 0) { peg$fail(peg$e33); } } return s0; @@ -1099,7 +1110,7 @@ function peg$parse(input, options) { peg$currPos++; } else { s1 = peg$FAILED; - if (peg$silentFails === 0) { peg$fail(peg$e35); } + if (peg$silentFails === 0) { peg$fail(peg$e36); } } while (s1 !== peg$FAILED) { s0.push(s1); @@ -1108,12 +1119,12 @@ function peg$parse(input, options) { peg$currPos++; } else { s1 = peg$FAILED; - if (peg$silentFails === 0) { peg$fail(peg$e35); } + if (peg$silentFails === 0) { peg$fail(peg$e36); } } } peg$silentFails--; s1 = peg$FAILED; - if (peg$silentFails === 0) { peg$fail(peg$e34); } + if (peg$silentFails === 0) { peg$fail(peg$e35); } return s0; } diff --git a/src/libs/SearchParser/searchParser.peggy b/src/libs/SearchParser/searchParser.peggy index f9f681736c6..028f2aad8bd 100644 --- a/src/libs/SearchParser/searchParser.peggy +++ b/src/libs/SearchParser/searchParser.peggy @@ -120,6 +120,7 @@ key "key" / "from" / "expenseType" / "in" + / "no" ) defaultKey "default key" From 81846715da04431e1b1095994fe402dc8acf8fec Mon Sep 17 00:00:00 2001 From: nkdengineer Date: Thu, 3 Oct 2024 11:37:16 +0700 Subject: [PATCH 028/121] fix: Tapping thread reply of expense hold offline, hmm not here displayed briefly --- src/libs/ReportActionsUtils.ts | 2 +- src/libs/actions/IOU.ts | 8 ++++++++ 2 files changed, 9 insertions(+), 1 deletion(-) diff --git a/src/libs/ReportActionsUtils.ts b/src/libs/ReportActionsUtils.ts index 48694349485..da4414ffcb2 100644 --- a/src/libs/ReportActionsUtils.ts +++ b/src/libs/ReportActionsUtils.ts @@ -1068,7 +1068,7 @@ function getOneTransactionThreadReportID(reportID: string, reportActions: OnyxEn // If there's only one IOU request action associated with the report but it's been deleted, then we don't consider this a oneTransaction report // and want to display it using the standard view - if ((originalMessage?.deleted ?? '') !== '' && isMoneyRequestAction(singleAction)) { + if (((originalMessage?.deleted ?? '') !== '' || isDeletedAction(singleAction)) && isMoneyRequestAction(singleAction)) { return; } diff --git a/src/libs/actions/IOU.ts b/src/libs/actions/IOU.ts index 9c03dd2af8f..8adc13b493a 100644 --- a/src/libs/actions/IOU.ts +++ b/src/libs/actions/IOU.ts @@ -7889,6 +7889,7 @@ function putOnHold(transactionID: string, comment: string, reportID: string, sea const newViolation = {name: CONST.VIOLATIONS.HOLD, type: CONST.VIOLATION_TYPES.VIOLATION}; const transactionViolations = allTransactionViolations[`${ONYXKEYS.COLLECTION.TRANSACTION_VIOLATIONS}${transactionID}`] ?? []; const updatedViolations = [...transactionViolations, newViolation]; + const optimisticData: OnyxUpdate[] = [ { onyxMethod: Onyx.METHOD.MERGE, @@ -7915,6 +7916,13 @@ function putOnHold(transactionID: string, comment: string, reportID: string, sea }, ]; + const optimisticParentReportActions = ReportUtils.getOptimisticDataForParentReportAction(reportID, currentTime, CONST.RED_BRICK_ROAD_PENDING_ACTION.ADD); + optimisticParentReportActions.forEach((optimisticParentReportAction) => { + if (!optimisticParentReportAction) { + return; + } + optimisticData.push(optimisticParentReportAction); + }); const successData: OnyxUpdate[] = [ { onyxMethod: Onyx.METHOD.MERGE, From 74fa5107a2f9d24daecc9b41c30c38aeb62ee3ca Mon Sep 17 00:00:00 2001 From: nkdengineer Date: Thu, 3 Oct 2024 11:38:05 +0700 Subject: [PATCH 029/121] fix lint --- src/libs/actions/IOU.ts | 1 - 1 file changed, 1 deletion(-) diff --git a/src/libs/actions/IOU.ts b/src/libs/actions/IOU.ts index 8adc13b493a..04be8dffdd3 100644 --- a/src/libs/actions/IOU.ts +++ b/src/libs/actions/IOU.ts @@ -7889,7 +7889,6 @@ function putOnHold(transactionID: string, comment: string, reportID: string, sea const newViolation = {name: CONST.VIOLATIONS.HOLD, type: CONST.VIOLATION_TYPES.VIOLATION}; const transactionViolations = allTransactionViolations[`${ONYXKEYS.COLLECTION.TRANSACTION_VIOLATIONS}${transactionID}`] ?? []; const updatedViolations = [...transactionViolations, newViolation]; - const optimisticData: OnyxUpdate[] = [ { onyxMethod: Onyx.METHOD.MERGE, From be73b227389e18af0cedb69a336ce553889c3567 Mon Sep 17 00:00:00 2001 From: Carlos Martins Date: Thu, 3 Oct 2024 10:15:30 -0600 Subject: [PATCH 030/121] add no operator --- src/libs/SearchUtils.ts | 3 ++- .../SearchFiltersCategoryPage.tsx | 12 +++++++++--- src/types/form/SearchAdvancedFiltersForm.ts | 2 ++ 3 files changed, 13 insertions(+), 4 deletions(-) diff --git a/src/libs/SearchUtils.ts b/src/libs/SearchUtils.ts index 47cee2c7c2b..6523423cff2 100644 --- a/src/libs/SearchUtils.ts +++ b/src/libs/SearchUtils.ts @@ -602,7 +602,7 @@ function buildQueryStringFromFilterFormValues(filterValues: Partial { if ((filterKey === FILTER_KEYS.MERCHANT || filterKey === FILTER_KEYS.DESCRIPTION || filterKey === FILTER_KEYS.REPORT_ID) && filterValue) { @@ -626,6 +626,7 @@ function buildQueryStringFromFilterFormValues(filterValues: Partial 0 diff --git a/src/pages/Search/SearchAdvancedFiltersPage/SearchFiltersCategoryPage.tsx b/src/pages/Search/SearchAdvancedFiltersPage/SearchFiltersCategoryPage.tsx index 1e37cdc6029..315e336be74 100644 --- a/src/pages/Search/SearchAdvancedFiltersPage/SearchFiltersCategoryPage.tsx +++ b/src/pages/Search/SearchAdvancedFiltersPage/SearchFiltersCategoryPage.tsx @@ -26,12 +26,18 @@ function SearchFiltersCategoryPage() { if (!singlePolicyCategories) { const uniqueCategoryNames = new Set(); Object.values(allPolicyIDCategories ?? {}).map((policyCategories) => Object.values(policyCategories ?? {}).forEach((category) => uniqueCategoryNames.add(category.name))); - return Array.from(uniqueCategoryNames).map((categoryName) => ({name: categoryName, value: categoryName})); + return [{name: 'No category', value: 'no:category'}, ...Array.from(uniqueCategoryNames).map((categoryName) => ({name: categoryName, value: categoryName}))]; } - return Object.values(singlePolicyCategories ?? {}).map((category) => ({name: category.name, value: category.name})); + return [{name: 'No category', value: 'no:category'}, ...Object.values(singlePolicyCategories ?? {}).map((category) => ({name: category.name, value: category.name}))]; }, [allPolicyIDCategories, singlePolicyCategories]); - const onSaveSelection = useCallback((values: string[]) => SearchActions.updateAdvancedFilters({category: values}), []); + const onSaveSelection = useCallback((values: string[]) => { + if (values.at(0) === 'no:category') { + SearchActions.updateAdvancedFilters({no: ['category']}); + return; + } + SearchActions.updateAdvancedFilters({category: values}); + }, []); const safePaddingBottomStyle = useSafePaddingBottomStyle(); return ( diff --git a/src/types/form/SearchAdvancedFiltersForm.ts b/src/types/form/SearchAdvancedFiltersForm.ts index a70e58d48a1..c5c55fd4c40 100644 --- a/src/types/form/SearchAdvancedFiltersForm.ts +++ b/src/types/form/SearchAdvancedFiltersForm.ts @@ -22,6 +22,7 @@ const FILTER_KEYS = { FROM: 'from', TO: 'to', IN: 'in', + NO: 'no', } as const; type InputID = ValueOf; @@ -49,6 +50,7 @@ type SearchAdvancedFiltersForm = Form< [FILTER_KEYS.FROM]: string[]; [FILTER_KEYS.TO]: string[]; [FILTER_KEYS.IN]: string[]; + [FILTER_KEYS.NO]: string[]; } >; From d1a83388fa4259497e15f08c201b55c3f39dc54b Mon Sep 17 00:00:00 2001 From: Carlos Martins Date: Thu, 3 Oct 2024 10:57:41 -0600 Subject: [PATCH 031/121] add no as a valid filter --- src/CONST.ts | 1 + src/libs/SearchUtils.ts | 2 +- src/pages/Search/AdvancedSearchFilters.tsx | 1 - 3 files changed, 2 insertions(+), 2 deletions(-) diff --git a/src/CONST.ts b/src/CONST.ts index 2af183d9d75..1d8e3382643 100755 --- a/src/CONST.ts +++ b/src/CONST.ts @@ -5586,6 +5586,7 @@ const CONST = { REPORT_ID: 'reportID', KEYWORD: 'keyword', IN: 'in', + NO: 'no', }, }, diff --git a/src/libs/SearchUtils.ts b/src/libs/SearchUtils.ts index 6523423cff2..f372ae71c81 100644 --- a/src/libs/SearchUtils.ts +++ b/src/libs/SearchUtils.ts @@ -602,7 +602,7 @@ function buildQueryStringFromFilterFormValues(filterValues: Partial { if ((filterKey === FILTER_KEYS.MERCHANT || filterKey === FILTER_KEYS.DESCRIPTION || filterKey === FILTER_KEYS.REPORT_ID) && filterValue) { diff --git a/src/pages/Search/AdvancedSearchFilters.tsx b/src/pages/Search/AdvancedSearchFilters.tsx index 7009764629b..b3e43a050d9 100644 --- a/src/pages/Search/AdvancedSearchFilters.tsx +++ b/src/pages/Search/AdvancedSearchFilters.tsx @@ -235,7 +235,6 @@ function AdvancedSearchFilters() { const queryString = useMemo(() => SearchUtils.buildQueryStringFromFilterFormValues(searchAdvancedFilters), [searchAdvancedFilters]); const queryJSON = useMemo(() => SearchUtils.buildSearchQueryJSON(queryString || SearchUtils.buildCannedSearchQuery()), [queryString]); - const applyFiltersAndNavigate = () => { SearchActions.clearAllFilters(); Navigation.dismissModal(); From f353d36450b6c5ee8005b25496afa158f03ee322 Mon Sep 17 00:00:00 2001 From: Carlos Martins Date: Thu, 3 Oct 2024 11:05:41 -0600 Subject: [PATCH 032/121] add comments --- src/ROUTES.ts | 3 +++ src/pages/Search/AdvancedSearchFilters.tsx | 7 +++++++ 2 files changed, 10 insertions(+) diff --git a/src/ROUTES.ts b/src/ROUTES.ts index 9c429dd3e90..56815acd0b8 100644 --- a/src/ROUTES.ts +++ b/src/ROUTES.ts @@ -57,6 +57,9 @@ const ROUTES = { SEARCH_ADVANCED_FILTERS_FROM: 'search/filters/from', SEARCH_ADVANCED_FILTERS_TO: 'search/filters/to', SEARCH_ADVANCED_FILTERS_IN: 'search/filters/in', + + // The 'no' filter is a special case because it's a modifier built into other filters, e.g. categories and tags so it doesn't have it's own page + SEARCH_ADVANCED_FILTERS_NO: 'search/filters/', SEARCH_REPORT: { route: 'search/view/:reportID/:reportActionID?', getRoute: ({reportID, reportActionID, backTo}: {reportID: string; reportActionID?: string; backTo?: string}) => { diff --git a/src/pages/Search/AdvancedSearchFilters.tsx b/src/pages/Search/AdvancedSearchFilters.tsx index b3e43a050d9..fb8f7364598 100644 --- a/src/pages/Search/AdvancedSearchFilters.tsx +++ b/src/pages/Search/AdvancedSearchFilters.tsx @@ -105,6 +105,13 @@ const baseFilterConfig = { description: 'common.in' as const, route: ROUTES.SEARCH_ADVANCED_FILTERS_IN, }, + + // The no filter is just a modifier for other filters, e.g. categories and tags so it does't have its own page + no: { + getTitle: () => {}, + description: 'common.no' as const, + route: ROUTES.SEARCH_ADVANCED_FILTERS_NO, + }, }; const typeFiltersKeys: Record>> = { From 524a0ee2a337eff66b6e4e60ea4c5ee9fabf4b17 Mon Sep 17 00:00:00 2001 From: Carlos Martins Date: Thu, 3 Oct 2024 11:28:09 -0600 Subject: [PATCH 033/121] display selection on filters page --- src/pages/Search/AdvancedSearchFilters.tsx | 7 ++++++- 1 file changed, 6 insertions(+), 1 deletion(-) diff --git a/src/pages/Search/AdvancedSearchFilters.tsx b/src/pages/Search/AdvancedSearchFilters.tsx index fb8f7364598..569987ddec1 100644 --- a/src/pages/Search/AdvancedSearchFilters.tsx +++ b/src/pages/Search/AdvancedSearchFilters.tsx @@ -108,7 +108,6 @@ const baseFilterConfig = { // The no filter is just a modifier for other filters, e.g. categories and tags so it does't have its own page no: { - getTitle: () => {}, description: 'common.no' as const, route: ROUTES.SEARCH_ADVANCED_FILTERS_NO, }, @@ -181,6 +180,12 @@ function getFilterDisplayTitle(filters: Partial, fiel return; } + if (fieldName === CONST.SEARCH.SYNTAX_FILTER_KEYS.CATEGORY && filters['no']) { + return 'No category'; + } + if (fieldName === CONST.SEARCH.SYNTAX_FILTER_KEYS.TAG && filters['no']) { + return 'No tag'; + } if ( (fieldName === CONST.SEARCH.SYNTAX_FILTER_KEYS.CATEGORY || fieldName === CONST.SEARCH.SYNTAX_FILTER_KEYS.CURRENCY || fieldName === CONST.SEARCH.SYNTAX_FILTER_KEYS.TAG) && filters[fieldName] From c9a845173d0c57d93539acc83cc2c24352a35681 Mon Sep 17 00:00:00 2001 From: Carlos Martins Date: Thu, 3 Oct 2024 11:39:48 -0600 Subject: [PATCH 034/121] use consts --- src/CONST.ts | 4 ++++ src/languages/en.ts | 2 ++ src/languages/es.ts | 2 ++ src/pages/Search/AdvancedSearchFilters.tsx | 10 ++++++---- .../SearchFiltersCategoryPage.tsx | 17 ++++++++++++----- 5 files changed, 26 insertions(+), 9 deletions(-) diff --git a/src/CONST.ts b/src/CONST.ts index 1d8e3382643..db6e0391094 100755 --- a/src/CONST.ts +++ b/src/CONST.ts @@ -5588,6 +5588,10 @@ const CONST = { IN: 'in', NO: 'no', }, + FILTER_NO: { + CATEGORY: 'no:category', + TAG: 'no:tag', + }, }, REFERRER: { diff --git a/src/languages/en.ts b/src/languages/en.ts index f15e5c97d03..58ab6a4e9e4 100755 --- a/src/languages/en.ts +++ b/src/languages/en.ts @@ -4146,6 +4146,8 @@ const translations = { }, current: 'Current', past: 'Past', + noCategory: 'No category', + noTag: 'No tag', }, expenseType: 'Expense type', recentSearches: 'Recent searches', diff --git a/src/languages/es.ts b/src/languages/es.ts index 413084aa828..16af936180d 100644 --- a/src/languages/es.ts +++ b/src/languages/es.ts @@ -4190,6 +4190,8 @@ const translations = { }, current: 'Actual', past: 'Anterior', + noCategory: 'Sin categoría', + noTag: 'Sin etiqueta', }, expenseType: 'Tipo de gasto', recentSearches: 'Búsquedas recientes', diff --git a/src/pages/Search/AdvancedSearchFilters.tsx b/src/pages/Search/AdvancedSearchFilters.tsx index 569987ddec1..b1e3e389b79 100644 --- a/src/pages/Search/AdvancedSearchFilters.tsx +++ b/src/pages/Search/AdvancedSearchFilters.tsx @@ -180,12 +180,14 @@ function getFilterDisplayTitle(filters: Partial, fiel return; } - if (fieldName === CONST.SEARCH.SYNTAX_FILTER_KEYS.CATEGORY && filters['no']) { - return 'No category'; + if (fieldName === CONST.SEARCH.SYNTAX_FILTER_KEYS.CATEGORY && filters[CONST.SEARCH.SYNTAX_FILTER_KEYS.NO]) { + return translate('search.filters.noCategory'); } - if (fieldName === CONST.SEARCH.SYNTAX_FILTER_KEYS.TAG && filters['no']) { - return 'No tag'; + + if (fieldName === CONST.SEARCH.SYNTAX_FILTER_KEYS.TAG && filters[CONST.SEARCH.SYNTAX_FILTER_KEYS.NO]) { + return translate('search.filters.noTag'); } + if ( (fieldName === CONST.SEARCH.SYNTAX_FILTER_KEYS.CATEGORY || fieldName === CONST.SEARCH.SYNTAX_FILTER_KEYS.CURRENCY || fieldName === CONST.SEARCH.SYNTAX_FILTER_KEYS.TAG) && filters[fieldName] diff --git a/src/pages/Search/SearchAdvancedFiltersPage/SearchFiltersCategoryPage.tsx b/src/pages/Search/SearchAdvancedFiltersPage/SearchFiltersCategoryPage.tsx index 315e336be74..ae22990f36a 100644 --- a/src/pages/Search/SearchAdvancedFiltersPage/SearchFiltersCategoryPage.tsx +++ b/src/pages/Search/SearchAdvancedFiltersPage/SearchFiltersCategoryPage.tsx @@ -9,6 +9,7 @@ import useSafePaddingBottomStyle from '@hooks/useSafePaddingBottomStyle'; import useThemeStyles from '@hooks/useThemeStyles'; import Navigation from '@libs/Navigation/Navigation'; import * as SearchActions from '@userActions/Search'; +import CONST from '@src/CONST'; import ONYXKEYS from '@src/ONYXKEYS'; import ROUTES from '@src/ROUTES'; @@ -26,14 +27,20 @@ function SearchFiltersCategoryPage() { if (!singlePolicyCategories) { const uniqueCategoryNames = new Set(); Object.values(allPolicyIDCategories ?? {}).map((policyCategories) => Object.values(policyCategories ?? {}).forEach((category) => uniqueCategoryNames.add(category.name))); - return [{name: 'No category', value: 'no:category'}, ...Array.from(uniqueCategoryNames).map((categoryName) => ({name: categoryName, value: categoryName}))]; + return [ + {name: translate('search.filters.noCategory'), value: CONST.SEARCH.FILTER_NO.CATEGORY}, + ...Array.from(uniqueCategoryNames).map((categoryName) => ({name: categoryName, value: categoryName})), + ]; } - return [{name: 'No category', value: 'no:category'}, ...Object.values(singlePolicyCategories ?? {}).map((category) => ({name: category.name, value: category.name}))]; - }, [allPolicyIDCategories, singlePolicyCategories]); + return [ + {name: translate('search.filters.noCategory'), value: CONST.SEARCH.FILTER_NO.CATEGORY}, + ...Object.values(singlePolicyCategories ?? {}).map((category) => ({name: category.name, value: category.name})), + ]; + }, [allPolicyIDCategories, singlePolicyCategories, translate]); const onSaveSelection = useCallback((values: string[]) => { - if (values.at(0) === 'no:category') { - SearchActions.updateAdvancedFilters({no: ['category']}); + if (values.at(0) === CONST.SEARCH.FILTER_NO.CATEGORY) { + SearchActions.updateAdvancedFilters({no: [CONST.SEARCH.SYNTAX_FILTER_KEYS.CATEGORY]}); return; } SearchActions.updateAdvancedFilters({category: values}); From af3f047a6660480880218f9a627f57d0d436cdc1 Mon Sep 17 00:00:00 2001 From: daledah Date: Fri, 4 Oct 2024 01:19:12 +0700 Subject: [PATCH 035/121] fix: lint --- src/libs/actions/TaxRate.ts | 15 ++++++++------- 1 file changed, 8 insertions(+), 7 deletions(-) diff --git a/src/libs/actions/TaxRate.ts b/src/libs/actions/TaxRate.ts index d3b2fdfc9e4..0061497be07 100644 --- a/src/libs/actions/TaxRate.ts +++ b/src/libs/actions/TaxRate.ts @@ -289,7 +289,8 @@ function deletePolicyTaxes(policyID: string, taxesToDelete: string[]) { .sort((a, b) => a.localeCompare(b)) .at(0); const customUnits = policy?.customUnits ?? {}; - const ratesToUpdate = Object.values(customUnits?.[Object.keys(customUnits)[0]]?.rates ?? {}).filter( + const customUnitID = Object.keys(customUnits).at(0) ?? '-1'; + const ratesToUpdate = Object.values(customUnits?.[customUnitID]?.rates ?? {}).filter( (rate) => !!rate.attributes?.taxRateExternalID && taxesToDelete.includes(rate.attributes?.taxRateExternalID), ); @@ -341,8 +342,8 @@ function deletePolicyTaxes(policyID: string, taxesToDelete: string[]) { return acc; }, {}), }, - customUnits: policy.customUnits && { - [Object.keys(policy.customUnits)[0]]: { + customUnits: customUnits && { + [customUnitID]: { rates: optimisticRates, }, }, @@ -361,8 +362,8 @@ function deletePolicyTaxes(policyID: string, taxesToDelete: string[]) { return acc; }, {}), }, - customUnits: policy.customUnits && { - [Object.keys(policy.customUnits)[0]]: { + customUnits: customUnits && { + [customUnitID]: { rates: successRates, }, }, @@ -384,8 +385,8 @@ function deletePolicyTaxes(policyID: string, taxesToDelete: string[]) { return acc; }, {}), }, - customUnits: policy.customUnits && { - [Object.keys(policy.customUnits)[0]]: { + customUnits: customUnits && { + [customUnitID]: { rates: failureRates, }, }, From 8bf7a494d455afc06c328c060835e38b629aaf36 Mon Sep 17 00:00:00 2001 From: Carlos Martins Date: Thu, 3 Oct 2024 12:20:07 -0600 Subject: [PATCH 036/121] add new section --- .../Search/SearchMultipleSelectionPicker.tsx | 6 ++++++ .../SearchFiltersCategoryPage.tsx | 12 +++--------- 2 files changed, 9 insertions(+), 9 deletions(-) diff --git a/src/components/Search/SearchMultipleSelectionPicker.tsx b/src/components/Search/SearchMultipleSelectionPicker.tsx index 558b89715b6..a363a90591e 100644 --- a/src/components/Search/SearchMultipleSelectionPicker.tsx +++ b/src/components/Search/SearchMultipleSelectionPicker.tsx @@ -52,10 +52,16 @@ function SearchMultipleSelectionPicker({items, initiallySelectedItems, pickerTit value: item.value, })); const isEmpty = !selectedItemsSection.length && !remainingItemsSection.length; + console.log('over here', selectedItemsSection) return { sections: isEmpty ? [] : [ + { + title: undefined, + data: [{text: 'No category', keyForList: 'No category', value: 'no:category', isSelected: false}], + shouldShow: selectedItemsSection.length === 0, + }, { title: undefined, data: selectedItemsSection, diff --git a/src/pages/Search/SearchAdvancedFiltersPage/SearchFiltersCategoryPage.tsx b/src/pages/Search/SearchAdvancedFiltersPage/SearchFiltersCategoryPage.tsx index ae22990f36a..0bf0f83d4df 100644 --- a/src/pages/Search/SearchAdvancedFiltersPage/SearchFiltersCategoryPage.tsx +++ b/src/pages/Search/SearchAdvancedFiltersPage/SearchFiltersCategoryPage.tsx @@ -27,16 +27,10 @@ function SearchFiltersCategoryPage() { if (!singlePolicyCategories) { const uniqueCategoryNames = new Set(); Object.values(allPolicyIDCategories ?? {}).map((policyCategories) => Object.values(policyCategories ?? {}).forEach((category) => uniqueCategoryNames.add(category.name))); - return [ - {name: translate('search.filters.noCategory'), value: CONST.SEARCH.FILTER_NO.CATEGORY}, - ...Array.from(uniqueCategoryNames).map((categoryName) => ({name: categoryName, value: categoryName})), - ]; + return Array.from(uniqueCategoryNames).map((categoryName) => ({name: categoryName, value: categoryName})); } - return [ - {name: translate('search.filters.noCategory'), value: CONST.SEARCH.FILTER_NO.CATEGORY}, - ...Object.values(singlePolicyCategories ?? {}).map((category) => ({name: category.name, value: category.name})), - ]; - }, [allPolicyIDCategories, singlePolicyCategories, translate]); + return Object.values(singlePolicyCategories ?? {}).map((category) => ({name: category.name, value: category.name})); + }, [allPolicyIDCategories, singlePolicyCategories]); const onSaveSelection = useCallback((values: string[]) => { if (values.at(0) === CONST.SEARCH.FILTER_NO.CATEGORY) { From 830ac47fc05e52a975b7a4a1316f12cec622adf7 Mon Sep 17 00:00:00 2001 From: Mateusz Titz Date: Mon, 30 Sep 2024 16:00:08 +0200 Subject: [PATCH 037/121] Remove old chat finder functionality --- src/SCREENS.ts | 1 - src/libs/E2E/reactNativeLaunchingTest.ts | 1 - .../Navigators/LeftModalNavigator.tsx | 5 - .../ChatFinderPage/ChatFinderPageFooter.tsx | 11 - src/pages/ChatFinderPage/index.tsx | 209 ---------------- tests/e2e/config.ts | 4 - tests/perf-test/ChatFinderPage.perf-test.tsx | 232 ------------------ 7 files changed, 463 deletions(-) delete mode 100644 src/pages/ChatFinderPage/ChatFinderPageFooter.tsx delete mode 100644 src/pages/ChatFinderPage/index.tsx delete mode 100644 tests/perf-test/ChatFinderPage.perf-test.tsx diff --git a/src/SCREENS.ts b/src/SCREENS.ts index 9a94d612dc8..8d363e6317c 100644 --- a/src/SCREENS.ts +++ b/src/SCREENS.ts @@ -141,7 +141,6 @@ const SCREENS = { ROOT: 'SaveTheWorld_Root', }, LEFT_MODAL: { - CHAT_FINDER: 'ChatFinder', WORKSPACE_SWITCHER: 'WorkspaceSwitcher', }, RIGHT_MODAL: { diff --git a/src/libs/E2E/reactNativeLaunchingTest.ts b/src/libs/E2E/reactNativeLaunchingTest.ts index f952998f0aa..21cb0336c5f 100644 --- a/src/libs/E2E/reactNativeLaunchingTest.ts +++ b/src/libs/E2E/reactNativeLaunchingTest.ts @@ -32,7 +32,6 @@ if (!appInstanceId) { // import your test here, define its name and config first in e2e/config.js const tests: Tests = { [E2EConfig.TEST_NAMES.AppStartTime]: require('./tests/appStartTimeTest.e2e').default, - [E2EConfig.TEST_NAMES.OpenChatFinderPage]: require('./tests/openChatFinderPageTest.e2e').default, [E2EConfig.TEST_NAMES.ChatOpening]: require('./tests/chatOpeningTest.e2e').default, [E2EConfig.TEST_NAMES.ReportTyping]: require('./tests/reportTypingTest.e2e').default, [E2EConfig.TEST_NAMES.Linking]: require('./tests/linkingTest.e2e').default, diff --git a/src/libs/Navigation/AppNavigator/Navigators/LeftModalNavigator.tsx b/src/libs/Navigation/AppNavigator/Navigators/LeftModalNavigator.tsx index 077bdce9454..50439c19845 100644 --- a/src/libs/Navigation/AppNavigator/Navigators/LeftModalNavigator.tsx +++ b/src/libs/Navigation/AppNavigator/Navigators/LeftModalNavigator.tsx @@ -14,7 +14,6 @@ import Overlay from './Overlay'; type LeftModalNavigatorProps = StackScreenProps; -const loadChatFinder = () => require('../../../../pages/ChatFinderPage').default; const loadWorkspaceSwitcherPage = () => require('../../../../pages/WorkspaceSwitcherPage').default; const Stack = createStackNavigator(); @@ -37,10 +36,6 @@ function LeftModalNavigator({navigation}: LeftModalNavigatorProps) { screenOptions={screenOptions} id={NAVIGATORS.LEFT_MODAL_NAVIGATOR} > - ; -} - -ChatFinderPageFooter.displayName = 'ChatFinderPageFooter'; - -export default ChatFinderPageFooter; diff --git a/src/pages/ChatFinderPage/index.tsx b/src/pages/ChatFinderPage/index.tsx deleted file mode 100644 index aabf881a8be..00000000000 --- a/src/pages/ChatFinderPage/index.tsx +++ /dev/null @@ -1,209 +0,0 @@ -import type {StackScreenProps} from '@react-navigation/stack'; -import isEmpty from 'lodash/isEmpty'; -import React, {useCallback, useEffect, useMemo, useState} from 'react'; -import type {OnyxEntry} from 'react-native-onyx'; -import {withOnyx} from 'react-native-onyx'; -import HeaderWithBackButton from '@components/HeaderWithBackButton'; -import {useOptionsList} from '@components/OptionListContextProvider'; -import ScreenWrapper from '@components/ScreenWrapper'; -import SelectionList from '@components/SelectionList'; -import UserListItem from '@components/SelectionList/UserListItem'; -import useCancelSearchOnModalClose from '@hooks/useCancelSearchOnModalClose'; -import useDebouncedState from '@hooks/useDebouncedState'; -import useDismissedReferralBanners from '@hooks/useDismissedReferralBanners'; -import useLocalize from '@hooks/useLocalize'; -import useNetwork from '@hooks/useNetwork'; -import Navigation from '@libs/Navigation/Navigation'; -import type {RootStackParamList} from '@libs/Navigation/types'; -import * as OptionsListUtils from '@libs/OptionsListUtils'; -import Performance from '@libs/Performance'; -import type {OptionData} from '@libs/ReportUtils'; -import * as Report from '@userActions/Report'; -import Timing from '@userActions/Timing'; -import CONST from '@src/CONST'; -import ONYXKEYS from '@src/ONYXKEYS'; -import ROUTES from '@src/ROUTES'; -import type SCREENS from '@src/SCREENS'; -import type * as OnyxTypes from '@src/types/onyx'; -import ChatFinderPageFooter from './ChatFinderPageFooter'; - -type ChatFinderPageOnyxProps = { - /** Beta features list */ - betas: OnyxEntry; - - /** Whether or not we are searching for reports on the server */ - isSearchingForReports: OnyxEntry; -}; - -type ChatFinderPageProps = ChatFinderPageOnyxProps & StackScreenProps; - -type ChatFinderPageSectionItem = { - data: OptionData[]; - shouldShow: boolean; -}; - -type ChatFinderPageSectionList = ChatFinderPageSectionItem[]; - -const setPerformanceTimersEnd = () => { - Timing.end(CONST.TIMING.CHAT_FINDER_RENDER); - Performance.markEnd(CONST.TIMING.CHAT_FINDER_RENDER); -}; - -const ChatFinderPageFooterInstance = ; - -function ChatFinderPage({betas, isSearchingForReports, navigation}: ChatFinderPageProps) { - const [isScreenTransitionEnd, setIsScreenTransitionEnd] = useState(false); - const {translate} = useLocalize(); - const {isOffline} = useNetwork(); - const {options, areOptionsInitialized} = useOptionsList({ - shouldInitialize: isScreenTransitionEnd, - }); - - const offlineMessage: string = isOffline ? `${translate('common.youAppearToBeOffline')} ${translate('search.resultsAreLimited')}` : ''; - - const [searchValue, debouncedSearchValue, setSearchValue] = useDebouncedState(''); - const [, debouncedSearchValueInServer, setSearchValueInServer] = useDebouncedState('', 500); - const updateSearchValue = useCallback( - (value: string) => { - setSearchValue(value); - setSearchValueInServer(value); - }, - [setSearchValue, setSearchValueInServer], - ); - useCancelSearchOnModalClose(); - - useEffect(() => { - Report.searchInServer(debouncedSearchValueInServer.trim()); - }, [debouncedSearchValueInServer]); - - const searchOptions = useMemo(() => { - if (!areOptionsInitialized || !isScreenTransitionEnd) { - return { - recentReports: [], - personalDetails: [], - userToInvite: null, - currentUserOption: null, - categoryOptions: [], - tagOptions: [], - taxRatesOptions: [], - headerMessage: '', - }; - } - const optionList = OptionsListUtils.getSearchOptions(options, '', betas ?? []); - const header = OptionsListUtils.getHeaderMessage(optionList.recentReports.length + optionList.personalDetails.length !== 0, !!optionList.userToInvite, ''); - return {...optionList, headerMessage: header}; - }, [areOptionsInitialized, betas, isScreenTransitionEnd, options]); - - const filteredOptions = useMemo(() => { - if (debouncedSearchValue.trim() === '') { - return { - recentReports: [], - personalDetails: [], - userToInvite: null, - headerMessage: '', - }; - } - - Timing.start(CONST.TIMING.SEARCH_FILTER_OPTIONS); - const newOptions = OptionsListUtils.filterOptions(searchOptions, debouncedSearchValue, {sortByReportTypeInSearch: true, preferChatroomsOverThreads: true}); - Timing.end(CONST.TIMING.SEARCH_FILTER_OPTIONS); - - const header = OptionsListUtils.getHeaderMessage(newOptions.recentReports.length + Number(!!newOptions.userToInvite) > 0, false, debouncedSearchValue); - return { - recentReports: newOptions.recentReports, - personalDetails: newOptions.personalDetails, - userToInvite: newOptions.userToInvite, - headerMessage: header, - }; - }, [debouncedSearchValue, searchOptions]); - - const {recentReports, personalDetails: localPersonalDetails, userToInvite, headerMessage} = debouncedSearchValue.trim() !== '' ? filteredOptions : searchOptions; - - const sections = useMemo((): ChatFinderPageSectionList => { - const newSections: ChatFinderPageSectionList = []; - - if (recentReports?.length > 0) { - newSections.push({ - data: recentReports, - shouldShow: true, - }); - } - - if (localPersonalDetails.length > 0) { - newSections.push({ - data: localPersonalDetails, - shouldShow: true, - }); - } - - if (!isEmpty(userToInvite)) { - newSections.push({ - data: [userToInvite], - shouldShow: true, - }); - } - - return newSections; - }, [localPersonalDetails, recentReports, userToInvite]); - - const selectReport = (option: OptionData) => { - if (!option) { - return; - } - - if (option.reportID) { - Navigation.closeAndNavigate(ROUTES.REPORT_WITH_ID.getRoute(option.reportID)); - } else { - Report.navigateToAndOpenReport(option.login ? [option.login] : []); - } - }; - - const handleScreenTransitionEnd = () => { - setIsScreenTransitionEnd(true); - }; - - const {isDismissed} = useDismissedReferralBanners({referralContentType: CONST.REFERRAL_PROGRAM.CONTENT_TYPES.REFER_FRIEND}); - - return ( - - - - sections={areOptionsInitialized ? sections : CONST.EMPTY_ARRAY} - ListItem={UserListItem} - textInputValue={searchValue} - textInputLabel={translate('selectionList.nameEmailOrPhoneNumber')} - textInputHint={offlineMessage} - onChangeText={updateSearchValue} - headerMessage={headerMessage} - onLayout={setPerformanceTimersEnd} - onSelectRow={selectReport} - shouldSingleExecuteRowSelect - showLoadingPlaceholder={!areOptionsInitialized || !isScreenTransitionEnd} - footerContent={!isDismissed && ChatFinderPageFooterInstance} - isLoadingNewOptions={!!isSearchingForReports} - shouldDelayFocus={false} - /> - - ); -} - -ChatFinderPage.displayName = 'ChatFinderPage'; - -export default withOnyx({ - betas: { - key: ONYXKEYS.BETAS, - }, - isSearchingForReports: { - key: ONYXKEYS.IS_SEARCHING_FOR_REPORTS, - initWithStoredValues: false, - }, -})(ChatFinderPage); diff --git a/tests/e2e/config.ts b/tests/e2e/config.ts index bdb24bee0bc..8b14fb8de7a 100644 --- a/tests/e2e/config.ts +++ b/tests/e2e/config.ts @@ -4,7 +4,6 @@ const OUTPUT_DIR = process.env.WORKING_DIRECTORY || './tests/e2e/results'; // add your test name here … const TEST_NAMES = { AppStartTime: 'App start time', - OpenChatFinderPage: 'Open chat finder page TTI', ReportTyping: 'Report typing', ChatOpening: 'Chat opening', Linking: 'Linking', @@ -73,9 +72,6 @@ export default { name: TEST_NAMES.AppStartTime, // ... any additional config you might need }, - [TEST_NAMES.OpenChatFinderPage]: { - name: TEST_NAMES.OpenChatFinderPage, - }, [TEST_NAMES.ReportTyping]: { name: TEST_NAMES.ReportTyping, reportScreen: { diff --git a/tests/perf-test/ChatFinderPage.perf-test.tsx b/tests/perf-test/ChatFinderPage.perf-test.tsx deleted file mode 100644 index 4346977a1cd..00000000000 --- a/tests/perf-test/ChatFinderPage.perf-test.tsx +++ /dev/null @@ -1,232 +0,0 @@ -import type * as NativeNavigation from '@react-navigation/native'; -import type {StackNavigationProp, StackScreenProps} from '@react-navigation/stack'; -import {fireEvent, screen} from '@testing-library/react-native'; -import React, {useMemo} from 'react'; -import type {ComponentType} from 'react'; -import Onyx from 'react-native-onyx'; -import type {OnyxCollection, OnyxEntry} from 'react-native-onyx'; -import {measurePerformance} from 'reassure'; -import {LocaleContextProvider} from '@components/LocaleContextProvider'; -import OptionListContextProvider, {OptionsListContext} from '@components/OptionListContextProvider'; -import {KeyboardStateProvider} from '@components/withKeyboardState'; -import type {WithNavigationFocusProps} from '@components/withNavigationFocus'; -import type {RootStackParamList} from '@libs/Navigation/types'; -import {createOptionList} from '@libs/OptionsListUtils'; -import ChatFinderPage from '@pages/ChatFinderPage'; -import ComposeProviders from '@src/components/ComposeProviders'; -import OnyxProvider from '@src/components/OnyxProvider'; -import CONST from '@src/CONST'; -import ONYXKEYS from '@src/ONYXKEYS'; -import type SCREENS from '@src/SCREENS'; -import type {Beta, PersonalDetails, Report} from '@src/types/onyx'; -import createCollection from '../utils/collections/createCollection'; -import createPersonalDetails from '../utils/collections/personalDetails'; -import createRandomReport from '../utils/collections/reports'; -import createAddListenerMock from '../utils/createAddListenerMock'; -import * as TestHelper from '../utils/TestHelper'; -import waitForBatchedUpdates from '../utils/waitForBatchedUpdates'; -import wrapOnyxWithWaitForBatchedUpdates from '../utils/wrapOnyxWithWaitForBatchedUpdates'; - -jest.mock('lodash/debounce', () => - jest.fn((fn: Record) => { - // eslint-disable-next-line no-param-reassign - fn.cancel = jest.fn(); - return fn; - }), -); - -jest.mock('@src/libs/Log'); - -jest.mock('@src/libs/API', () => ({ - write: jest.fn(), - makeRequestWithSideEffects: jest.fn(), - read: jest.fn(), -})); - -jest.mock('@src/libs/Navigation/Navigation', () => ({ - dismissModalWithReport: jest.fn(), - getTopmostReportId: jest.fn(), - isNavigationReady: jest.fn(() => Promise.resolve()), - isDisplayedInModal: jest.fn(() => false), -})); - -jest.mock('@react-navigation/native', () => { - const actualNav = jest.requireActual('@react-navigation/native'); - return { - ...actualNav, - useFocusEffect: jest.fn(), - useIsFocused: () => true, - useRoute: () => jest.fn(), - useNavigation: () => ({ - navigate: jest.fn(), - addListener: () => jest.fn(), - }), - createNavigationContainerRef: () => ({ - addListener: () => jest.fn(), - removeListener: () => jest.fn(), - isReady: () => jest.fn(), - getCurrentRoute: () => jest.fn(), - getState: () => jest.fn(), - }), - }; -}); - -jest.mock('@src/components/withNavigationFocus', () => (Component: ComponentType) => { - function WithNavigationFocus(props: WithNavigationFocusProps) { - return ( - - ); - } - - WithNavigationFocus.displayName = 'WithNavigationFocus'; - - return WithNavigationFocus; -}); -// mock of useDismissedReferralBanners -jest.mock('../../src/hooks/useDismissedReferralBanners', () => ({ - // eslint-disable-next-line @typescript-eslint/naming-convention - __esModule: true, - default: jest.fn(() => ({ - isDismissed: false, - setAsDismissed: () => {}, - })), -})); - -const getMockedReports = (length = 100) => - createCollection( - (item) => `${ONYXKEYS.COLLECTION.REPORT}${item.reportID}`, - (index) => createRandomReport(index), - length, - ); - -const getMockedPersonalDetails = (length = 100) => - createCollection( - (item) => item.accountID, - (index) => createPersonalDetails(index), - length, - ); - -const mockedReports = getMockedReports(600); -const mockedBetas = Object.values(CONST.BETAS); -const mockedPersonalDetails = getMockedPersonalDetails(100); -const mockedOptions = createOptionList(mockedPersonalDetails, mockedReports); - -beforeAll(() => - Onyx.init({ - keys: ONYXKEYS, - safeEvictionKeys: [ONYXKEYS.COLLECTION.REPORT], - }), -); - -// Initialize the network key for OfflineWithFeedback -beforeEach(() => { - global.fetch = TestHelper.getGlobalFetchMock(); - wrapOnyxWithWaitForBatchedUpdates(Onyx); - Onyx.merge(ONYXKEYS.NETWORK, {isOffline: false}); -}); - -// Clear out Onyx after each test so that each test starts with a clean state -afterEach(() => { - Onyx.clear(); -}); - -type ChatFinderPageProps = StackScreenProps & { - betas?: OnyxEntry; - reports?: OnyxCollection; - isSearchingForReports?: OnyxEntry; -}; - -function ChatFinderPageWrapper(args: ChatFinderPageProps) { - return ( - - - - - - ); -} - -function ChatFinderPageWithCachedOptions(args: ChatFinderPageProps) { - return ( - - ({options: mockedOptions, initializeOptions: () => {}, areOptionsInitialized: true}), [])}> - - - - ); -} - -test('[ChatFinderPage] should render list with cached options', async () => { - const {addListener} = createAddListenerMock(); - - const scenario = async () => { - await screen.findByTestId('ChatFinderPage'); - }; - - const navigation = {addListener} as unknown as StackNavigationProp; - - return waitForBatchedUpdates() - .then(() => - Onyx.multiSet({ - ...mockedReports, - [ONYXKEYS.PERSONAL_DETAILS_LIST]: mockedPersonalDetails, - [ONYXKEYS.BETAS]: mockedBetas, - [ONYXKEYS.IS_SEARCHING_FOR_REPORTS]: true, - }), - ) - .then(() => - measurePerformance( - , - {scenario}, - ), - ); -}); - -test('[ChatFinderPage] should interact when text input changes', async () => { - const {addListener} = createAddListenerMock(); - - const scenario = async () => { - await screen.findByTestId('ChatFinderPage'); - - const input = screen.getByTestId('selection-list-text-input'); - fireEvent.changeText(input, 'Email Four'); - fireEvent.changeText(input, 'Report'); - fireEvent.changeText(input, 'Email Five'); - }; - - const navigation = {addListener} as unknown as StackNavigationProp; - - return waitForBatchedUpdates() - .then(() => - Onyx.multiSet({ - ...mockedReports, - [ONYXKEYS.PERSONAL_DETAILS_LIST]: mockedPersonalDetails, - [ONYXKEYS.BETAS]: mockedBetas, - [ONYXKEYS.IS_SEARCHING_FOR_REPORTS]: true, - }), - ) - .then(() => - measurePerformance( - , - {scenario}, - ), - ); -}); From 4052f9a4db738cce4afbe8573a33f838db2afe59 Mon Sep 17 00:00:00 2001 From: Mateusz Titz Date: Wed, 2 Oct 2024 13:06:17 +0200 Subject: [PATCH 038/121] Use SearchRouter instead of Chat finder and migrate e2e tests --- src/CONST.ts | 2 +- src/ROUTES.ts | 1 - .../Search/SearchRouter/SearchButton.tsx | 12 +- src/libs/E2E/reactNativeLaunchingTest.ts | 2 + ...est.e2e.ts => openSearchRouterTest.e2e.ts} | 17 +- .../Navigation/AppNavigator/AuthScreens.tsx | 10 +- .../createCustomBottomTabNavigator/TopBar.tsx | 33 +-- src/libs/Navigation/linkingConfig/config.ts | 1 - src/libs/Navigation/types.ts | 1 - src/pages/Search/SearchPageBottomTab.tsx | 7 +- .../SidebarScreen/BaseSidebarScreen.tsx | 5 + tests/e2e/config.ts | 4 + tests/perf-test/ChatFinderPage.perf-test.tsx | 217 ++++++++++++++++++ 13 files changed, 259 insertions(+), 53 deletions(-) rename src/libs/E2E/tests/{openChatFinderPageTest.e2e.ts => openSearchRouterTest.e2e.ts} (79%) create mode 100644 tests/perf-test/ChatFinderPage.perf-test.tsx diff --git a/src/CONST.ts b/src/CONST.ts index 9a6bf21db30..0b130ae7564 100755 --- a/src/CONST.ts +++ b/src/CONST.ts @@ -1084,7 +1084,7 @@ const CONST = { }, TIMING: { CALCULATE_MOST_RECENT_LAST_MODIFIED_ACTION: 'calc_most_recent_last_modified_action', - CHAT_FINDER_RENDER: 'search_render', + SEARCH_ROUTER_OPEN: 'search_router_render', CHAT_RENDER: 'chat_render', OPEN_REPORT: 'open_report', HOMEPAGE_INITIAL_RENDER: 'homepage_initial_render', diff --git a/src/ROUTES.ts b/src/ROUTES.ts index 9c429dd3e90..d7e6b37a60f 100644 --- a/src/ROUTES.ts +++ b/src/ROUTES.ts @@ -74,7 +74,6 @@ const ROUTES = { route: 'flag/:reportID/:reportActionID', getRoute: (reportID: string, reportActionID: string, backTo?: string) => getUrlWithBackToParam(`flag/${reportID}/${reportActionID}` as const, backTo), }, - CHAT_FINDER: 'chat-finder', PROFILE: { route: 'a/:accountID', getRoute: (accountID?: string | number, backTo?: string, login?: string) => { diff --git a/src/components/Search/SearchRouter/SearchButton.tsx b/src/components/Search/SearchRouter/SearchButton.tsx index 05693ad5ea2..53660ee1f6c 100644 --- a/src/components/Search/SearchRouter/SearchButton.tsx +++ b/src/components/Search/SearchRouter/SearchButton.tsx @@ -5,7 +5,11 @@ import {PressableWithoutFeedback} from '@components/Pressable'; import useLocalize from '@hooks/useLocalize'; import useTheme from '@hooks/useTheme'; import useThemeStyles from '@hooks/useThemeStyles'; +import Performance from '@libs/Performance'; import Permissions from '@libs/Permissions'; +import * as Session from '@userActions/Session'; +import Timing from '@userActions/Timing'; +import CONST from '@src/CONST'; import {useSearchRouterContext} from './SearchRouterContext'; function SearchButton() { @@ -22,9 +26,13 @@ function SearchButton() { { + onPress={Session.checkIfActionIsAllowed(() => { + // Todo [Search] add finishing for this timing in + Timing.start(CONST.TIMING.SEARCH_ROUTER_OPEN); + Performance.markStart(CONST.TIMING.SEARCH_ROUTER_OPEN); + openSearchRouter(); - }} + })} > ('./tests/appStartTimeTest.e2e').default, + // Todo [Search] rename + [E2EConfig.TEST_NAMES.OpenSearchRouter]: require('./tests/openSearchRouterTest.e2e').default, [E2EConfig.TEST_NAMES.ChatOpening]: require('./tests/chatOpeningTest.e2e').default, [E2EConfig.TEST_NAMES.ReportTyping]: require('./tests/reportTypingTest.e2e').default, [E2EConfig.TEST_NAMES.Linking]: require('./tests/linkingTest.e2e').default, diff --git a/src/libs/E2E/tests/openChatFinderPageTest.e2e.ts b/src/libs/E2E/tests/openSearchRouterTest.e2e.ts similarity index 79% rename from src/libs/E2E/tests/openChatFinderPageTest.e2e.ts rename to src/libs/E2E/tests/openSearchRouterTest.e2e.ts index 2c2f2eda4ef..6ce1891a63d 100644 --- a/src/libs/E2E/tests/openChatFinderPageTest.e2e.ts +++ b/src/libs/E2E/tests/openSearchRouterTest.e2e.ts @@ -3,14 +3,12 @@ import E2ELogin from '@libs/E2E/actions/e2eLogin'; import waitForAppLoaded from '@libs/E2E/actions/waitForAppLoaded'; import E2EClient from '@libs/E2E/client'; import getPromiseWithResolve from '@libs/E2E/utils/getPromiseWithResolve'; -import Navigation from '@libs/Navigation/Navigation'; import Performance from '@libs/Performance'; import CONST from '@src/CONST'; -import ROUTES from '@src/ROUTES'; const test = () => { // check for login (if already logged in the action will simply resolve) - console.debug('[E2E] Logging in for chat finder'); + console.debug('[E2E] Logging in for new search router'); E2ELogin().then((neededLogin: boolean): Promise | undefined => { if (neededLogin) { @@ -20,7 +18,7 @@ const test = () => { ); } - console.debug('[E2E] Logged in, getting chat finder metrics and submitting them…'); + console.debug('[E2E] Logged in, getting search router metrics and submitting them…'); const [openSearchPagePromise, openSearchPageResolve] = getPromiseWithResolve(); const [loadSearchOptionsPromise, loadSearchOptionsResolve] = getPromiseWithResolve(); @@ -32,19 +30,12 @@ const test = () => { }); Performance.subscribeToMeasurements((entry) => { - if (entry.name === CONST.TIMING.SIDEBAR_LOADED) { - console.debug(`[E2E] Sidebar loaded, navigating to chat finder route…`); - Performance.markStart(CONST.TIMING.CHAT_FINDER_RENDER); - Navigation.navigate(ROUTES.CHAT_FINDER); - return; - } - console.debug(`[E2E] Entry: ${JSON.stringify(entry)}`); - if (entry.name === CONST.TIMING.CHAT_FINDER_RENDER) { + if (entry.name === CONST.TIMING.SEARCH_ROUTER_OPEN) { E2EClient.submitTestResults({ branch: Config.E2E_BRANCH, - name: 'Open Chat Finder Page TTI', + name: 'Open Search Router TTI', metric: entry.duration, unit: 'ms', }) diff --git a/src/libs/Navigation/AppNavigator/AuthScreens.tsx b/src/libs/Navigation/AppNavigator/AuthScreens.tsx index f5f35fd2102..62be199ee18 100644 --- a/src/libs/Navigation/AppNavigator/AuthScreens.tsx +++ b/src/libs/Navigation/AppNavigator/AuthScreens.tsx @@ -8,6 +8,7 @@ import ComposeProviders from '@components/ComposeProviders'; import OptionsListContextProvider from '@components/OptionListContextProvider'; import {SearchContextProvider} from '@components/Search/SearchContext'; import SearchRouterModal from '@components/Search/SearchRouter/SearchRouterModal'; +import {useSearchRouterContext} from '@components/Search/SearchRouter/SearchRouterContext'; import useActiveWorkspace from '@hooks/useActiveWorkspace'; import useOnboardingFlowRouter from '@hooks/useOnboardingFlow'; import usePermissions from '@hooks/usePermissions'; @@ -233,6 +234,8 @@ function AuthScreens({session, lastOpenedPublicRoomID, initialLastUpdateIDApplie const screenOptions = getRootNavigatorScreenOptions(shouldUseNarrowLayout, styles, StyleUtils); const {canUseDefaultRooms} = usePermissions(); const {activeWorkspaceID} = useActiveWorkspace(); + const {openSearchRouter} = useSearchRouterContext(); + const onboardingModalScreenOptions = useMemo(() => screenOptions.onboardingModalNavigator(onboardingIsMediumOrLargerScreenWidth), [screenOptions, onboardingIsMediumOrLargerScreenWidth]); const onboardingScreenOptions = useMemo( () => getOnboardingModalScreenOptions(shouldUseNarrowLayout, styles, StyleUtils, onboardingIsMediumOrLargerScreenWidth), @@ -241,6 +244,7 @@ function AuthScreens({session, lastOpenedPublicRoomID, initialLastUpdateIDApplie const modal = useRef({}); const [didPusherInit, setDidPusherInit] = useState(false); const {isOnboardingCompleted} = useOnboardingFlowRouter(); + let initialReportID: string | undefined; const isInitialRender = useRef(true); if (isInitialRender.current) { @@ -363,13 +367,15 @@ function AuthScreens({session, lastOpenedPublicRoomID, initialLastUpdateIDApplie ); // Listen for the key K being pressed so that focus can be given to - // the chat switcher, or new group chat + // Search Router, or new group chat // based on the key modifiers pressed and the operating system const unsubscribeSearchShortcut = KeyboardShortcut.subscribe( searchShortcutConfig.shortcutKey, () => { Modal.close( - Session.checkIfActionIsAllowed(() => Navigation.navigate(ROUTES.CHAT_FINDER)), + Session.checkIfActionIsAllowed(() => { + openSearchRouter(); + }), true, true, ); diff --git a/src/libs/Navigation/AppNavigator/createCustomBottomTabNavigator/TopBar.tsx b/src/libs/Navigation/AppNavigator/createCustomBottomTabNavigator/TopBar.tsx index 4684eb9637b..8967486165f 100644 --- a/src/libs/Navigation/AppNavigator/createCustomBottomTabNavigator/TopBar.tsx +++ b/src/libs/Navigation/AppNavigator/createCustomBottomTabNavigator/TopBar.tsx @@ -2,32 +2,25 @@ import React from 'react'; import {View} from 'react-native'; import {useOnyx} from 'react-native-onyx'; import Breadcrumbs from '@components/Breadcrumbs'; -import Icon from '@components/Icon'; -import * as Expensicons from '@components/Icon/Expensicons'; import {PressableWithoutFeedback} from '@components/Pressable'; import SearchButton from '@components/Search/SearchRouter/SearchButton'; import Text from '@components/Text'; -import Tooltip from '@components/Tooltip'; import WorkspaceSwitcherButton from '@components/WorkspaceSwitcherButton'; import useLocalize from '@hooks/useLocalize'; import usePolicy from '@hooks/usePolicy'; -import useTheme from '@hooks/useTheme'; import useThemeStyles from '@hooks/useThemeStyles'; import Navigation from '@libs/Navigation/Navigation'; -import Performance from '@libs/Performance'; import * as SearchUtils from '@libs/SearchUtils'; import SignInButton from '@pages/home/sidebar/SignInButton'; import * as Session from '@userActions/Session'; -import Timing from '@userActions/Timing'; import CONST from '@src/CONST'; import ONYXKEYS from '@src/ONYXKEYS'; import ROUTES from '@src/ROUTES'; -type TopBarProps = {breadcrumbLabel: string; activeWorkspaceID?: string; shouldDisplaySearch?: boolean; isCustomSearchQuery?: boolean; shouldDisplaySearchRouter?: boolean}; +type TopBarProps = {breadcrumbLabel: string; activeWorkspaceID?: string; shouldDisplaySearch?: boolean; shouldDisplayCancelSearch?: boolean}; -function TopBar({breadcrumbLabel, activeWorkspaceID, shouldDisplaySearch = true, isCustomSearchQuery = false, shouldDisplaySearchRouter = false}: TopBarProps) { +function TopBar({breadcrumbLabel, activeWorkspaceID, shouldDisplaySearch = true, shouldDisplayCancelSearch = false}: TopBarProps) { const styles = useThemeStyles(); - const theme = useTheme(); const {translate} = useLocalize(); const policy = usePolicy(activeWorkspaceID); const [session] = useOnyx(ONYXKEYS.SESSION, {selector: (sessionValue) => sessionValue && {authTokenType: sessionValue.authTokenType}}); @@ -63,7 +56,7 @@ function TopBar({breadcrumbLabel, activeWorkspaceID, shouldDisplaySearch = true, {displaySignIn && } - {isCustomSearchQuery && ( + {shouldDisplayCancelSearch && ( {translate('common.cancel')} )} - {shouldDisplaySearchRouter && } - {displaySearch && ( - - { - Timing.start(CONST.TIMING.CHAT_FINDER_RENDER); - Performance.markStart(CONST.TIMING.CHAT_FINDER_RENDER); - Navigation.navigate(ROUTES.CHAT_FINDER); - })} - > - - - - )} + {displaySearch && } ); diff --git a/src/libs/Navigation/linkingConfig/config.ts b/src/libs/Navigation/linkingConfig/config.ts index 114e89ff2bf..980b28c3d63 100644 --- a/src/libs/Navigation/linkingConfig/config.ts +++ b/src/libs/Navigation/linkingConfig/config.ts @@ -76,7 +76,6 @@ const config: LinkingOptions['config'] = { [SCREENS.NOT_FOUND]: '*', [NAVIGATORS.LEFT_MODAL_NAVIGATOR]: { screens: { - [SCREENS.LEFT_MODAL.CHAT_FINDER]: ROUTES.CHAT_FINDER, [SCREENS.LEFT_MODAL.WORKSPACE_SWITCHER]: { path: ROUTES.WORKSPACE_SWITCHER, }, diff --git a/src/libs/Navigation/types.ts b/src/libs/Navigation/types.ts index b698681966e..ecd2895053f 100644 --- a/src/libs/Navigation/types.ts +++ b/src/libs/Navigation/types.ts @@ -1236,7 +1236,6 @@ type TransactionDuplicateNavigatorParamList = { }; type LeftModalNavigatorParamList = { - [SCREENS.LEFT_MODAL.CHAT_FINDER]: undefined; [SCREENS.LEFT_MODAL.WORKSPACE_SWITCHER]: undefined; }; diff --git a/src/pages/Search/SearchPageBottomTab.tsx b/src/pages/Search/SearchPageBottomTab.tsx index 38e4c516688..b4fb2aac61f 100644 --- a/src/pages/Search/SearchPageBottomTab.tsx +++ b/src/pages/Search/SearchPageBottomTab.tsx @@ -85,6 +85,8 @@ function SearchPageBottomTab() { ); } + const shouldDisplayCancelSearch = shouldUseNarrowLayout && !SearchUtils.isCannedSearchQuery(queryJSON); + return ( diff --git a/src/pages/home/sidebar/SidebarScreen/BaseSidebarScreen.tsx b/src/pages/home/sidebar/SidebarScreen/BaseSidebarScreen.tsx index edc8dfb3cb3..e77f2000b85 100644 --- a/src/pages/home/sidebar/SidebarScreen/BaseSidebarScreen.tsx +++ b/src/pages/home/sidebar/SidebarScreen/BaseSidebarScreen.tsx @@ -4,6 +4,7 @@ import {useOnyx} from 'react-native-onyx'; import ScreenWrapper from '@components/ScreenWrapper'; import useActiveWorkspaceFromNavigationState from '@hooks/useActiveWorkspaceFromNavigationState'; import useLocalize from '@hooks/useLocalize'; +import useResponsiveLayout from '@hooks/useResponsiveLayout'; import useThemeStyles from '@hooks/useThemeStyles'; import {updateLastAccessedWorkspace} from '@libs/actions/Policy/Policy'; import * as Browser from '@libs/Browser'; @@ -27,6 +28,7 @@ function BaseSidebarScreen() { const styles = useThemeStyles(); const activeWorkspaceID = useActiveWorkspaceFromNavigationState(); const {translate} = useLocalize(); + const {shouldUseNarrowLayout} = useResponsiveLayout(); const [activeWorkspace] = useOnyx(`${ONYXKEYS.COLLECTION.POLICY}${activeWorkspaceID ?? -1}`); useEffect(() => { @@ -43,6 +45,8 @@ function BaseSidebarScreen() { updateLastAccessedWorkspace(undefined); }, [activeWorkspace, activeWorkspaceID]); + const shouldDisplaySearch = shouldUseNarrowLayout; + return ( + jest.fn((fn: Record) => { + // eslint-disable-next-line no-param-reassign + fn.cancel = jest.fn(); + return fn; + }), +); + +jest.mock('@src/libs/Log'); + +jest.mock('@src/libs/API', () => ({ + write: jest.fn(), + makeRequestWithSideEffects: jest.fn(), + read: jest.fn(), +})); + +jest.mock('@src/libs/Navigation/Navigation', () => ({ + dismissModalWithReport: jest.fn(), + getTopmostReportId: jest.fn(), + isNavigationReady: jest.fn(() => Promise.resolve()), + isDisplayedInModal: jest.fn(() => false), +})); + +jest.mock('@react-navigation/native', () => { + const actualNav = jest.requireActual('@react-navigation/native'); + return { + ...actualNav, + useFocusEffect: jest.fn(), + useIsFocused: () => true, + useRoute: () => jest.fn(), + useNavigation: () => ({ + navigate: jest.fn(), + addListener: () => jest.fn(), + }), + createNavigationContainerRef: () => ({ + addListener: () => jest.fn(), + removeListener: () => jest.fn(), + isReady: () => jest.fn(), + getCurrentRoute: () => jest.fn(), + getState: () => jest.fn(), + }), + }; +}); + +jest.mock('@src/components/withNavigationFocus', () => (Component: ComponentType) => { + function WithNavigationFocus(props: WithNavigationFocusProps) { + return ( + + ); + } + + WithNavigationFocus.displayName = 'WithNavigationFocus'; + + return WithNavigationFocus; +}); +// mock of useDismissedReferralBanners +jest.mock('../../src/hooks/useDismissedReferralBanners', () => ({ + // eslint-disable-next-line @typescript-eslint/naming-convention + __esModule: true, + default: jest.fn(() => ({ + isDismissed: false, + setAsDismissed: () => {}, + })), +})); + +const getMockedReports = (length = 100) => + createCollection( + (item) => `${ONYXKEYS.COLLECTION.REPORT}${item.reportID}`, + (index) => createRandomReport(index), + length, + ); + +const getMockedPersonalDetails = (length = 100) => + createCollection( + (item) => item.accountID, + (index) => createPersonalDetails(index), + length, + ); + +const mockedReports = getMockedReports(600); +const mockedBetas = Object.values(CONST.BETAS); +const mockedPersonalDetails = getMockedPersonalDetails(100); +const mockedOptions = createOptionList(mockedPersonalDetails, mockedReports); + +beforeAll(() => + Onyx.init({ + keys: ONYXKEYS, + safeEvictionKeys: [ONYXKEYS.COLLECTION.REPORT], + }), +); + +// Initialize the network key for OfflineWithFeedback +beforeEach(() => { + global.fetch = TestHelper.getGlobalFetchMock(); + wrapOnyxWithWaitForBatchedUpdates(Onyx); + Onyx.merge(ONYXKEYS.NETWORK, {isOffline: false}); +}); + +// Clear out Onyx after each test so that each test starts with a clean state +afterEach(() => { + Onyx.clear(); +}); + +function ChatFinderPageWrapper(args: any) { + return ( + + + + {/* */} + + + ); +} + +function ChatFinderPageWithCachedOptions(args: any) { + return ( + + ({options: mockedOptions, initializeOptions: () => {}, areOptionsInitialized: true}), [])}> + {/* */} + + + ); +} + +test('[ChatFinderPage] should render list with cached options', async () => { + const {addListener} = createAddListenerMock(); + + const scenario = async () => { + await screen.findByTestId('ChatFinderPage'); // Todo [Search] fix testID no longer existing + }; + + // const navigation = {addListener} as unknown as StackNavigationProp; + + return waitForBatchedUpdates() + .then(() => + Onyx.multiSet({ + ...mockedReports, + [ONYXKEYS.PERSONAL_DETAILS_LIST]: mockedPersonalDetails, + [ONYXKEYS.BETAS]: mockedBetas, + [ONYXKEYS.IS_SEARCHING_FOR_REPORTS]: true, + }), + ) + .then(() => measureRenders(, {scenario})); +}); + +test('[ChatFinderPage] should interact when text input changes', async () => { + const {addListener} = createAddListenerMock(); + + const scenario = async () => { + await screen.findByTestId('ChatFinderPage'); + + const input = screen.getByTestId('selection-list-text-input'); + fireEvent.changeText(input, 'Email Four'); + fireEvent.changeText(input, 'Report'); + fireEvent.changeText(input, 'Email Five'); + }; + + // const navigation = {addListener} as unknown as StackNavigationProp; + + return waitForBatchedUpdates() + .then(() => + Onyx.multiSet({ + ...mockedReports, + [ONYXKEYS.PERSONAL_DETAILS_LIST]: mockedPersonalDetails, + [ONYXKEYS.BETAS]: mockedBetas, + [ONYXKEYS.IS_SEARCHING_FOR_REPORTS]: true, + }), + ) + .then(() => + measureRenders( + , + {scenario}, + ), + ); +}); From cb51d04588f2c442d9b18b1223104d10f73474b7 Mon Sep 17 00:00:00 2001 From: Mateusz Titz Date: Thu, 3 Oct 2024 16:55:14 +0200 Subject: [PATCH 039/121] Add performance timings handling for SearchRouterList --- src/components/Search/SearchRouter/SearchButton.tsx | 1 - src/components/Search/SearchRouter/SearchRouter.tsx | 3 +++ src/components/Search/SearchRouter/SearchRouterList.tsx | 9 +++++++++ src/libs/E2E/tests/openSearchRouterTest.e2e.ts | 6 +++--- 4 files changed, 15 insertions(+), 4 deletions(-) diff --git a/src/components/Search/SearchRouter/SearchButton.tsx b/src/components/Search/SearchRouter/SearchButton.tsx index 53660ee1f6c..b1e59a32392 100644 --- a/src/components/Search/SearchRouter/SearchButton.tsx +++ b/src/components/Search/SearchRouter/SearchButton.tsx @@ -27,7 +27,6 @@ function SearchButton() { accessibilityLabel={translate('common.search')} style={[styles.flexRow, styles.touchableButtonImage]} onPress={Session.checkIfActionIsAllowed(() => { - // Todo [Search] add finishing for this timing in Timing.start(CONST.TIMING.SEARCH_ROUTER_OPEN); Performance.markStart(CONST.TIMING.SEARCH_ROUTER_OPEN); diff --git a/src/components/Search/SearchRouter/SearchRouter.tsx b/src/components/Search/SearchRouter/SearchRouter.tsx index 8f5ad55bc0c..868144492ce 100644 --- a/src/components/Search/SearchRouter/SearchRouter.tsx +++ b/src/components/Search/SearchRouter/SearchRouter.tsx @@ -19,6 +19,7 @@ import * as SearchUtils from '@libs/SearchUtils'; import Navigation from '@navigation/Navigation'; import variables from '@styles/variables'; import * as Report from '@userActions/Report'; +import Timing from '@userActions/Timing'; import CONST from '@src/CONST'; import ONYXKEYS from '@src/ONYXKEYS'; import ROUTES from '@src/ROUTES'; @@ -65,7 +66,9 @@ function SearchRouter() { }; } + Timing.start(CONST.TIMING.SEARCH_FILTER_OPTIONS); const newOptions = OptionsListUtils.filterOptions(searchOptions, debouncedInputValue, {sortByReportTypeInSearch: true, preferChatroomsOverThreads: true}); + Timing.end(CONST.TIMING.SEARCH_FILTER_OPTIONS); return { recentReports: newOptions.recentReports, diff --git a/src/components/Search/SearchRouter/SearchRouterList.tsx b/src/components/Search/SearchRouter/SearchRouterList.tsx index 7d86ce1150d..a58ea969d28 100644 --- a/src/components/Search/SearchRouter/SearchRouterList.tsx +++ b/src/components/Search/SearchRouter/SearchRouterList.tsx @@ -13,10 +13,13 @@ import useLocalize from '@hooks/useLocalize'; import useResponsiveLayout from '@hooks/useResponsiveLayout'; import useThemeStyles from '@hooks/useThemeStyles'; import Navigation from '@libs/Navigation/Navigation'; +import Performance from '@libs/Performance'; import {getAllTaxRates} from '@libs/PolicyUtils'; import type {OptionData} from '@libs/ReportUtils'; import * as SearchUtils from '@libs/SearchUtils'; import * as Report from '@userActions/Report'; +import Timing from '@userActions/Timing'; +import CONST from '@src/CONST'; import ONYXKEYS from '@src/ONYXKEYS'; import ROUTES from '@src/ROUTES'; @@ -47,6 +50,11 @@ type SearchRouterListProps = { closeAndClearRouter: () => void; }; +const setPerformanceTimersEnd = () => { + Timing.end(CONST.TIMING.SEARCH_ROUTER_OPEN); + Performance.markEnd(CONST.TIMING.SEARCH_ROUTER_OPEN); +}; + function isSearchQueryItem(item: OptionData | SearchQueryItem): item is SearchQueryItem { if ('singleIcon' in item && item.singleIcon && 'query' in item && item.query) { return true; @@ -174,6 +182,7 @@ function SearchRouterList( ListItem={SearchRouterItem} containerStyle={[styles.mh100]} sectionListStyle={[isSmallScreenWidth ? styles.ph5 : styles.ph2, styles.pb2]} + onLayout={setPerformanceTimersEnd} ref={ref} showScrollIndicator={!isSmallScreenWidth} /> diff --git a/src/libs/E2E/tests/openSearchRouterTest.e2e.ts b/src/libs/E2E/tests/openSearchRouterTest.e2e.ts index 6ce1891a63d..6c4646b09fb 100644 --- a/src/libs/E2E/tests/openSearchRouterTest.e2e.ts +++ b/src/libs/E2E/tests/openSearchRouterTest.e2e.ts @@ -20,10 +20,10 @@ const test = () => { console.debug('[E2E] Logged in, getting search router metrics and submitting them…'); - const [openSearchPagePromise, openSearchPageResolve] = getPromiseWithResolve(); + const [openSearchRouterPromise, openSearchRouterResolve] = getPromiseWithResolve(); const [loadSearchOptionsPromise, loadSearchOptionsResolve] = getPromiseWithResolve(); - Promise.all([openSearchPagePromise, loadSearchOptionsPromise]).then(() => { + Promise.all([openSearchRouterPromise, loadSearchOptionsPromise]).then(() => { console.debug(`[E2E] Submitting!`); E2EClient.submitTestDone(); @@ -40,7 +40,7 @@ const test = () => { unit: 'ms', }) .then(() => { - openSearchPageResolve(); + openSearchRouterResolve(); console.debug('[E2E] Done with search, exiting…'); }) .catch((err) => { From 635e0da1032b297e9834791d4c7200b5852bc625 Mon Sep 17 00:00:00 2001 From: Mateusz Titz Date: Fri, 4 Oct 2024 11:52:05 +0200 Subject: [PATCH 040/121] Migrate reassure tests for ChatFinder to SearchRouter --- .../Search/SearchRouter/SearchRouter.tsx | 34 +++++----- .../Search/SearchRouter/SearchRouterInput.tsx | 1 + .../Search/SearchRouter/SearchRouterModal.tsx | 2 +- src/libs/E2E/reactNativeLaunchingTest.ts | 1 - .../Navigation/AppNavigator/AuthScreens.tsx | 2 +- ...rf-test.tsx => SearchRouter.perf-test.tsx} | 65 +++++-------------- 6 files changed, 35 insertions(+), 70 deletions(-) rename tests/perf-test/{ChatFinderPage.perf-test.tsx => SearchRouter.perf-test.tsx} (72%) diff --git a/src/components/Search/SearchRouter/SearchRouter.tsx b/src/components/Search/SearchRouter/SearchRouter.tsx index 868144492ce..f49112e86c6 100644 --- a/src/components/Search/SearchRouter/SearchRouter.tsx +++ b/src/components/Search/SearchRouter/SearchRouter.tsx @@ -23,13 +23,16 @@ import Timing from '@userActions/Timing'; import CONST from '@src/CONST'; import ONYXKEYS from '@src/ONYXKEYS'; import ROUTES from '@src/ROUTES'; -import {useSearchRouterContext} from './SearchRouterContext'; import SearchRouterInput from './SearchRouterInput'; import SearchRouterList from './SearchRouterList'; const SEARCH_DEBOUNCE_DELAY = 150; -function SearchRouter() { +type SearchRouterProps = { + onRouterClose: () => void; +}; + +function SearchRouter({onRouterClose}: SearchRouterProps) { const styles = useThemeStyles(); const {translate} = useLocalize(); const [betas] = useOnyx(ONYXKEYS.BETAS); @@ -37,7 +40,6 @@ function SearchRouter() { const [isSearchingForReports] = useOnyx(ONYXKEYS.IS_SEARCHING_FOR_REPORTS, {initWithStoredValues: false}); const {isSmallScreenWidth} = useResponsiveLayout(); - const {isSearchRouterDisplayed, closeSearchRouter} = useSearchRouterContext(); const listRef = useRef(null); const [textInputValue, debouncedInputValue, setTextInputValue] = useDebouncedState('', 500); @@ -90,15 +92,6 @@ function SearchRouter() { Report.searchInServer(debouncedInputValue.trim()); }, [debouncedInputValue]); - useEffect(() => { - if (!textInputValue && isSearchRouterDisplayed) { - return; - } - listRef.current?.updateAndScrollToFocusedIndex(0); - // eslint-disable-next-line react-compiler/react-compiler - // eslint-disable-next-line react-hooks/exhaustive-deps - }, [isSearchRouterDisplayed]); - const contextualReportData = contextualReportID ? searchOptions.recentReports?.find((option) => option.reportID === contextualReportID) : undefined; const clearUserQuery = () => { @@ -135,40 +128,43 @@ function SearchRouter() { }; const closeAndClearRouter = useCallback(() => { - closeSearchRouter(); + onRouterClose(); clearUserQuery(); // eslint-disable-next-line react-compiler/react-compiler // eslint-disable-next-line react-hooks/exhaustive-deps - }, [closeSearchRouter]); + }, [onRouterClose]); const onSearchSubmit = useCallback( (query: SearchQueryJSON | undefined) => { if (!query) { return; } - closeSearchRouter(); + onRouterClose(); const queryString = SearchUtils.buildSearchQueryString(query); Navigation.navigate(ROUTES.SEARCH_CENTRAL_PANE.getRoute({query: queryString})); clearUserQuery(); }, // eslint-disable-next-line react-compiler/react-compiler // eslint-disable-next-line react-hooks/exhaustive-deps - [closeSearchRouter], + [onRouterClose], ); useKeyboardShortcut(CONST.KEYBOARD_SHORTCUTS.ESCAPE, () => { - closeSearchRouter(); + onRouterClose(); clearUserQuery(); }); const modalWidth = isSmallScreenWidth ? styles.w100 : {width: variables.popoverWidth}; return ( - + {isSmallScreenWidth && ( closeSearchRouter()} + onBackButtonPress={() => onRouterClose()} /> )} - {isSearchRouterDisplayed && } + {isSearchRouterDisplayed && } ); } diff --git a/src/libs/E2E/reactNativeLaunchingTest.ts b/src/libs/E2E/reactNativeLaunchingTest.ts index 672d66637ef..fdd305baf88 100644 --- a/src/libs/E2E/reactNativeLaunchingTest.ts +++ b/src/libs/E2E/reactNativeLaunchingTest.ts @@ -32,7 +32,6 @@ if (!appInstanceId) { // import your test here, define its name and config first in e2e/config.js const tests: Tests = { [E2EConfig.TEST_NAMES.AppStartTime]: require('./tests/appStartTimeTest.e2e').default, - // Todo [Search] rename [E2EConfig.TEST_NAMES.OpenSearchRouter]: require('./tests/openSearchRouterTest.e2e').default, [E2EConfig.TEST_NAMES.ChatOpening]: require('./tests/chatOpeningTest.e2e').default, [E2EConfig.TEST_NAMES.ReportTyping]: require('./tests/reportTypingTest.e2e').default, diff --git a/src/libs/Navigation/AppNavigator/AuthScreens.tsx b/src/libs/Navigation/AppNavigator/AuthScreens.tsx index 62be199ee18..2ec31d8a868 100644 --- a/src/libs/Navigation/AppNavigator/AuthScreens.tsx +++ b/src/libs/Navigation/AppNavigator/AuthScreens.tsx @@ -7,8 +7,8 @@ import ActiveGuidesEventListener from '@components/ActiveGuidesEventListener'; import ComposeProviders from '@components/ComposeProviders'; import OptionsListContextProvider from '@components/OptionListContextProvider'; import {SearchContextProvider} from '@components/Search/SearchContext'; -import SearchRouterModal from '@components/Search/SearchRouter/SearchRouterModal'; import {useSearchRouterContext} from '@components/Search/SearchRouter/SearchRouterContext'; +import SearchRouterModal from '@components/Search/SearchRouter/SearchRouterModal'; import useActiveWorkspace from '@hooks/useActiveWorkspace'; import useOnboardingFlowRouter from '@hooks/useOnboardingFlow'; import usePermissions from '@hooks/usePermissions'; diff --git a/tests/perf-test/ChatFinderPage.perf-test.tsx b/tests/perf-test/SearchRouter.perf-test.tsx similarity index 72% rename from tests/perf-test/ChatFinderPage.perf-test.tsx rename to tests/perf-test/SearchRouter.perf-test.tsx index ed43b699adc..5be0c93105c 100644 --- a/tests/perf-test/ChatFinderPage.perf-test.tsx +++ b/tests/perf-test/SearchRouter.perf-test.tsx @@ -2,11 +2,11 @@ import type * as NativeNavigation from '@react-navigation/native'; import {fireEvent, screen} from '@testing-library/react-native'; import React, {useMemo} from 'react'; import type {ComponentType} from 'react'; -import {View} from 'react-native'; import Onyx from 'react-native-onyx'; import {measureRenders} from 'reassure'; import {LocaleContextProvider} from '@components/LocaleContextProvider'; import OptionListContextProvider, {OptionsListContext} from '@components/OptionListContextProvider'; +import SearchRouter from '@components/Search/SearchRouter/SearchRouter'; import {KeyboardStateProvider} from '@components/withKeyboardState'; import type {WithNavigationFocusProps} from '@components/withNavigationFocus'; import {createOptionList} from '@libs/OptionsListUtils'; @@ -23,8 +23,6 @@ import * as TestHelper from '../utils/TestHelper'; import waitForBatchedUpdates from '../utils/waitForBatchedUpdates'; import wrapOnyxWithWaitForBatchedUpdates from '../utils/wrapOnyxWithWaitForBatchedUpdates'; -// Todo [Search] Either migrate this test to tests new SearchRouter or remove it completely. - jest.mock('lodash/debounce', () => jest.fn((fn: Record) => { // eslint-disable-next-line no-param-reassign @@ -66,6 +64,9 @@ jest.mock('@react-navigation/native', () => { getCurrentRoute: () => jest.fn(), getState: () => jest.fn(), }), + useNavigationState: () => ({ + routes: [], + }), }; }); @@ -84,15 +85,6 @@ jest.mock('@src/components/withNavigationFocus', () => (Component: ComponentType return WithNavigationFocus; }); -// mock of useDismissedReferralBanners -jest.mock('../../src/hooks/useDismissedReferralBanners', () => ({ - // eslint-disable-next-line @typescript-eslint/naming-convention - __esModule: true, - default: jest.fn(() => ({ - isDismissed: false, - setAsDismissed: () => {}, - })), -})); const getMockedReports = (length = 100) => createCollection( @@ -132,44 +124,33 @@ afterEach(() => { Onyx.clear(); }); -function ChatFinderPageWrapper(args: any) { +const mockOnClose = jest.fn(); + +function SearchRouterWrapper() { return ( - - {/* */} + ); } -function ChatFinderPageWithCachedOptions(args: any) { +function SearchRouterWrapperWithCachedOptions() { return ( ({options: mockedOptions, initializeOptions: () => {}, areOptionsInitialized: true}), [])}> - {/* */} + ); } -test('[ChatFinderPage] should render list with cached options', async () => { - const {addListener} = createAddListenerMock(); - +test('[SearchRouter] should render chat list with cached options', async () => { const scenario = async () => { - await screen.findByTestId('ChatFinderPage'); // Todo [Search] fix testID no longer existing + await screen.findByTestId('SearchRouter'); }; - // const navigation = {addListener} as unknown as StackNavigationProp; - return waitForBatchedUpdates() .then(() => Onyx.multiSet({ @@ -179,23 +160,19 @@ test('[ChatFinderPage] should render list with cached options', async () => { [ONYXKEYS.IS_SEARCHING_FOR_REPORTS]: true, }), ) - .then(() => measureRenders(, {scenario})); + .then(() => measureRenders(, {scenario})); }); -test('[ChatFinderPage] should interact when text input changes', async () => { - const {addListener} = createAddListenerMock(); - +test('[SearchRouter] should react to text input changes', async () => { const scenario = async () => { - await screen.findByTestId('ChatFinderPage'); + await screen.findByTestId('SearchRouter'); - const input = screen.getByTestId('selection-list-text-input'); + const input = screen.getByTestId('search-router-text-input'); fireEvent.changeText(input, 'Email Four'); fireEvent.changeText(input, 'Report'); fireEvent.changeText(input, 'Email Five'); }; - // const navigation = {addListener} as unknown as StackNavigationProp; - return waitForBatchedUpdates() .then(() => Onyx.multiSet({ @@ -205,13 +182,5 @@ test('[ChatFinderPage] should interact when text input changes', async () => { [ONYXKEYS.IS_SEARCHING_FOR_REPORTS]: true, }), ) - .then(() => - measureRenders( - , - {scenario}, - ), - ); + .then(() => measureRenders(, {scenario})); }); From 078420e53f8500f1e75fef3cfde10d64f2e7e8d5 Mon Sep 17 00:00:00 2001 From: Mateusz Titz Date: Fri, 4 Oct 2024 12:15:39 +0200 Subject: [PATCH 041/121] Remove SearchRouter dev check --- src/components/Search/SearchRouter/SearchButton.tsx | 5 ----- src/libs/Permissions.ts | 13 ------------- tests/perf-test/SearchRouter.perf-test.tsx | 1 - 3 files changed, 19 deletions(-) diff --git a/src/components/Search/SearchRouter/SearchButton.tsx b/src/components/Search/SearchRouter/SearchButton.tsx index b1e59a32392..2ddc9a8262c 100644 --- a/src/components/Search/SearchRouter/SearchButton.tsx +++ b/src/components/Search/SearchRouter/SearchButton.tsx @@ -6,7 +6,6 @@ import useLocalize from '@hooks/useLocalize'; import useTheme from '@hooks/useTheme'; import useThemeStyles from '@hooks/useThemeStyles'; import Performance from '@libs/Performance'; -import Permissions from '@libs/Permissions'; import * as Session from '@userActions/Session'; import Timing from '@userActions/Timing'; import CONST from '@src/CONST'; @@ -18,10 +17,6 @@ function SearchButton() { const {translate} = useLocalize(); const {openSearchRouter} = useSearchRouterContext(); - if (!Permissions.canUseNewSearchRouter()) { - return; - } - return ( ): boolean { return !!betas?.includes(CONST.BETAS.ALL); @@ -50,17 +49,6 @@ function canUseCombinedTrackSubmit(betas: OnyxEntry): boolean { return !!betas?.includes(CONST.BETAS.COMBINED_TRACK_SUBMIT); } -/** - * New Search Router is under construction and for now should be displayed only in dev to allow developers to work on it. - * We are not using BETA for this feature, as betas are heavier to cleanup, - * and the development of new router is expected to take 2-3 weeks at most - * - * After everything is implemented this function can be removed, as we will always use SearchRouter in the App. - */ -function canUseNewSearchRouter() { - return Environment.isDevelopment(); -} - /** * Link previews are temporarily disabled. */ @@ -80,5 +68,4 @@ export default { canUseNewDotCopilot, canUseWorkspaceRules, canUseCombinedTrackSubmit, - canUseNewSearchRouter, }; diff --git a/tests/perf-test/SearchRouter.perf-test.tsx b/tests/perf-test/SearchRouter.perf-test.tsx index 5be0c93105c..e9154a36a9a 100644 --- a/tests/perf-test/SearchRouter.perf-test.tsx +++ b/tests/perf-test/SearchRouter.perf-test.tsx @@ -18,7 +18,6 @@ import type {PersonalDetails, Report} from '@src/types/onyx'; import createCollection from '../utils/collections/createCollection'; import createPersonalDetails from '../utils/collections/personalDetails'; import createRandomReport from '../utils/collections/reports'; -import createAddListenerMock from '../utils/createAddListenerMock'; import * as TestHelper from '../utils/TestHelper'; import waitForBatchedUpdates from '../utils/waitForBatchedUpdates'; import wrapOnyxWithWaitForBatchedUpdates from '../utils/wrapOnyxWithWaitForBatchedUpdates'; From e21ed95a38c101515a3dbc8a10f7307375b6e22b Mon Sep 17 00:00:00 2001 From: Mateusz Titz Date: Fri, 4 Oct 2024 17:37:12 +0200 Subject: [PATCH 042/121] Fix SearchRouter opening from keyboard shortcut --- .../SearchRouter/SearchRouterContext.tsx | 25 ++++++++++++++++--- .../Navigation/AppNavigator/AuthScreens.tsx | 12 +++------ 2 files changed, 26 insertions(+), 11 deletions(-) diff --git a/src/components/Search/SearchRouter/SearchRouterContext.tsx b/src/components/Search/SearchRouter/SearchRouterContext.tsx index d935fff110a..4e01071a091 100644 --- a/src/components/Search/SearchRouter/SearchRouterContext.tsx +++ b/src/components/Search/SearchRouter/SearchRouterContext.tsx @@ -1,10 +1,11 @@ -import React, {useContext, useMemo, useState} from 'react'; +import React, {useContext, useMemo, useRef, useState} from 'react'; import type ChildrenProps from '@src/types/utils/ChildrenProps'; const defaultSearchContext = { isSearchRouterDisplayed: false, openSearchRouter: () => {}, closeSearchRouter: () => {}, + toggleSearchRouter: () => {}, }; type SearchRouterContext = typeof defaultSearchContext; @@ -13,15 +14,33 @@ const Context = React.createContext(defaultSearchContext); function SearchRouterContextProvider({children}: ChildrenProps) { const [isSearchRouterDisplayed, setIsSearchRouterDisplayed] = useState(false); + const searchRouterDisplayedRef = useRef(false); const routerContext = useMemo(() => { - const openSearchRouter = () => setIsSearchRouterDisplayed(true); - const closeSearchRouter = () => setIsSearchRouterDisplayed(false); + const openSearchRouter = () => { + setIsSearchRouterDisplayed(true); + searchRouterDisplayedRef.current = true; + }; + const closeSearchRouter = () => { + setIsSearchRouterDisplayed(false); + searchRouterDisplayedRef.current = false; + }; + + // There are callbacks that live outside of React render-loop and interact with SearchRouter + // So we need a function that is based on ref to correctly open/close it + const toggleSearchRouter = () => { + if (searchRouterDisplayedRef.current) { + closeSearchRouter(); + } else { + openSearchRouter(); + } + }; return { isSearchRouterDisplayed, openSearchRouter, closeSearchRouter, + toggleSearchRouter, }; }, [isSearchRouterDisplayed, setIsSearchRouterDisplayed]); diff --git a/src/libs/Navigation/AppNavigator/AuthScreens.tsx b/src/libs/Navigation/AppNavigator/AuthScreens.tsx index 2ec31d8a868..b9ea16a344f 100644 --- a/src/libs/Navigation/AppNavigator/AuthScreens.tsx +++ b/src/libs/Navigation/AppNavigator/AuthScreens.tsx @@ -234,7 +234,7 @@ function AuthScreens({session, lastOpenedPublicRoomID, initialLastUpdateIDApplie const screenOptions = getRootNavigatorScreenOptions(shouldUseNarrowLayout, styles, StyleUtils); const {canUseDefaultRooms} = usePermissions(); const {activeWorkspaceID} = useActiveWorkspace(); - const {openSearchRouter} = useSearchRouterContext(); + const {toggleSearchRouter} = useSearchRouterContext(); const onboardingModalScreenOptions = useMemo(() => screenOptions.onboardingModalNavigator(onboardingIsMediumOrLargerScreenWidth), [screenOptions, onboardingIsMediumOrLargerScreenWidth]); const onboardingScreenOptions = useMemo( @@ -372,13 +372,9 @@ function AuthScreens({session, lastOpenedPublicRoomID, initialLastUpdateIDApplie const unsubscribeSearchShortcut = KeyboardShortcut.subscribe( searchShortcutConfig.shortcutKey, () => { - Modal.close( - Session.checkIfActionIsAllowed(() => { - openSearchRouter(); - }), - true, - true, - ); + Session.checkIfActionIsAllowed(() => { + toggleSearchRouter(); + })(); }, shortcutsOverviewShortcutConfig.descriptionKey, shortcutsOverviewShortcutConfig.modifiers, From 4b81669e070a276265442998aaba3890603e66b1 Mon Sep 17 00:00:00 2001 From: Jakub Szymczak Date: Mon, 7 Oct 2024 11:47:54 +0200 Subject: [PATCH 043/121] close modals on openSearchRouter --- .../Search/SearchRouter/SearchRouterContext.tsx | 11 +++++++++-- 1 file changed, 9 insertions(+), 2 deletions(-) diff --git a/src/components/Search/SearchRouter/SearchRouterContext.tsx b/src/components/Search/SearchRouter/SearchRouterContext.tsx index 4e01071a091..2e4cbec0d6b 100644 --- a/src/components/Search/SearchRouter/SearchRouterContext.tsx +++ b/src/components/Search/SearchRouter/SearchRouterContext.tsx @@ -1,4 +1,5 @@ import React, {useContext, useMemo, useRef, useState} from 'react'; +import * as Modal from '@userActions/Modal'; import type ChildrenProps from '@src/types/utils/ChildrenProps'; const defaultSearchContext = { @@ -18,8 +19,14 @@ function SearchRouterContextProvider({children}: ChildrenProps) { const routerContext = useMemo(() => { const openSearchRouter = () => { - setIsSearchRouterDisplayed(true); - searchRouterDisplayedRef.current = true; + Modal.close( + () => { + setIsSearchRouterDisplayed(true); + searchRouterDisplayedRef.current = true; + }, + false, + true, + ); }; const closeSearchRouter = () => { setIsSearchRouterDisplayed(false); From 47ba23f6e5dd5b744135ed46dbe76fac100df4a7 Mon Sep 17 00:00:00 2001 From: Jakub Szymczak Date: Tue, 8 Oct 2024 11:05:11 +0200 Subject: [PATCH 044/121] fix design comments --- src/components/Search/SearchRouter/SearchRouterList.tsx | 1 + src/components/Search/SearchRouter/SearchRouterModal.tsx | 2 +- src/styles/index.ts | 4 ++-- src/styles/utils/spacing.ts | 4 ++++ 4 files changed, 8 insertions(+), 3 deletions(-) diff --git a/src/components/Search/SearchRouter/SearchRouterList.tsx b/src/components/Search/SearchRouter/SearchRouterList.tsx index a58ea969d28..70abc5c8955 100644 --- a/src/components/Search/SearchRouter/SearchRouterList.tsx +++ b/src/components/Search/SearchRouter/SearchRouterList.tsx @@ -185,6 +185,7 @@ function SearchRouterList( onLayout={setPerformanceTimersEnd} ref={ref} showScrollIndicator={!isSmallScreenWidth} + sectionTitleStyles={styles.mhn2} /> ); } diff --git a/src/components/Search/SearchRouter/SearchRouterModal.tsx b/src/components/Search/SearchRouter/SearchRouterModal.tsx index c44b66044ab..7e403461dd3 100644 --- a/src/components/Search/SearchRouter/SearchRouterModal.tsx +++ b/src/components/Search/SearchRouter/SearchRouterModal.tsx @@ -17,7 +17,7 @@ function SearchRouterModal() { type={modalType} fullscreen isVisible={isSearchRouterDisplayed} - popoverAnchorPosition={{right: 20, top: 20}} + popoverAnchorPosition={{right: 6, top: 6}} onClose={closeSearchRouter} > {isSearchRouterDisplayed && } diff --git a/src/styles/index.ts b/src/styles/index.ts index 8220389867d..b81d76c8d7d 100644 --- a/src/styles/index.ts +++ b/src/styles/index.ts @@ -3615,8 +3615,8 @@ const styles = (theme: ThemeColors) => searchInputStyle: { color: theme.textSupporting, - fontSize: 13, - lineHeight: 16, + fontSize: variables.fontSizeNormal, + lineHeight: variables.fontSizeNormalHeight, }, searchRouterTextInputContainer: { diff --git a/src/styles/utils/spacing.ts b/src/styles/utils/spacing.ts index c2008d8a68f..9f741c9d925 100644 --- a/src/styles/utils/spacing.ts +++ b/src/styles/utils/spacing.ts @@ -55,6 +55,10 @@ export default { marginHorizontal: 32, }, + mhn2: { + marginHorizontal: -8, + }, + mhn5: { marginHorizontal: -20, }, From bb24b8b75b569794d7c2b7e5eea8d0b0a4acd6bb Mon Sep 17 00:00:00 2001 From: Anusha Date: Tue, 8 Oct 2024 21:42:52 +0500 Subject: [PATCH 045/121] fix deleted message not found page --- src/pages/home/ReportScreen.tsx | 5 +++-- 1 file changed, 3 insertions(+), 2 deletions(-) diff --git a/src/pages/home/ReportScreen.tsx b/src/pages/home/ReportScreen.tsx index dd38a071637..75cdc3fd9b2 100644 --- a/src/pages/home/ReportScreen.tsx +++ b/src/pages/home/ReportScreen.tsx @@ -359,9 +359,11 @@ function ReportScreen({route, currentReportID = '', navigation}: ReportScreenPro !isCurrentReportLoadedFromOnyx || isLoading; + const isLinkedActionBecomesDeleted = prevIsLinkedActionDeleted !== undefined && !prevIsLinkedActionDeleted && isLinkedActionDeleted; + // eslint-disable-next-line rulesdir/no-negated-variables const shouldShowNotFoundLinkedAction = - (!isLinkedActionInaccessibleWhisper && isLinkedActionDeleted && !!prevIsLinkedActionDeleted) || + (!isLinkedActionInaccessibleWhisper && isLinkedActionDeleted && !isLinkedActionBecomesDeleted) || (shouldShowSkeleton && !reportMetadata.isLoadingInitialReportActions && !!reportActionIDFromRoute && @@ -661,7 +663,6 @@ function ReportScreen({route, currentReportID = '', navigation}: ReportScreenPro useEffect(() => { // If the linked action is previously available but now deleted, // remove the reportActionID from the params to not link to the deleted action. - const isLinkedActionBecomesDeleted = prevIsLinkedActionDeleted !== undefined && !prevIsLinkedActionDeleted && isLinkedActionDeleted; if (!isLinkedActionBecomesDeleted) { return; } From c0542d589a974e741d89b1484b0598d21eb30ab5 Mon Sep 17 00:00:00 2001 From: Pedro Guerreiro Date: Tue, 8 Oct 2024 20:03:34 +0100 Subject: [PATCH 046/121] fix(debug mode): wrong reason for why RBR reports are visible in LHN --- src/languages/en.ts | 1 + src/languages/es.ts | 1 + src/libs/DebugUtils.ts | 9 ++- src/libs/OptionsListUtils.ts | 119 +--------------------------- src/libs/ReportUtils.ts | 115 ++++++++++++++++++++++++++- src/libs/SidebarUtils.ts | 20 +---- src/libs/WorkspacesSettingsUtils.ts | 2 +- 7 files changed, 129 insertions(+), 138 deletions(-) diff --git a/src/languages/en.ts b/src/languages/en.ts index c44ac6f2ced..eb68cf9b23c 100755 --- a/src/languages/en.ts +++ b/src/languages/en.ts @@ -4931,6 +4931,7 @@ const translations = { reasonVisibleInLHN: { hasDraftComment: 'Has draft comment', hasGBR: 'Has GBR', + hasRBR: 'Has RBR', pinnedByUser: 'Pinned by user', hasIOUViolations: 'Has IOU violations', hasAddWorkspaceRoomErrors: 'Has add workspace room errors', diff --git a/src/languages/es.ts b/src/languages/es.ts index 76cb22772f8..7aec0d6eef5 100644 --- a/src/languages/es.ts +++ b/src/languages/es.ts @@ -5443,6 +5443,7 @@ const translations = { reasonVisibleInLHN: { hasDraftComment: 'Tiene comentario en borrador', hasGBR: 'Tiene GBR', + hasRBR: 'Tiene RBR', pinnedByUser: 'Fijado por el usuario', hasIOUViolations: 'Tiene violaciones de IOU', hasAddWorkspaceRoomErrors: 'Tiene errores al agregar sala de espacio de trabajo', diff --git a/src/libs/DebugUtils.ts b/src/libs/DebugUtils.ts index ed8e486517f..af682e9cdc6 100644 --- a/src/libs/DebugUtils.ts +++ b/src/libs/DebugUtils.ts @@ -7,7 +7,6 @@ import CONST from '@src/CONST'; import type {TranslationPaths} from '@src/languages/types'; import ONYXKEYS from '@src/ONYXKEYS'; import type {Beta, Policy, Report, ReportAction, ReportActions, TransactionViolation} from '@src/types/onyx'; -import * as OptionsListUtils from './OptionsListUtils'; import * as ReportUtils from './ReportUtils'; class NumberError extends SyntaxError { @@ -598,7 +597,11 @@ function getReasonForShowingRowInLHN(report: OnyxEntry): TranslationPath return null; } - const doesReportHaveViolations = OptionsListUtils.shouldShowViolations(report, transactionViolations); + const doesReportHaveViolations = ReportUtils.shouldShowViolations(report, transactionViolations); + + if (ReportUtils.hasReportErrorsOtherThanFailedReceipt(report, doesReportHaveViolations, transactionViolations)) { + return `debug.reasonVisibleInLHN.hasRBR`; + } const reason = ReportUtils.reasonForReportToBeInOptionList({ report, @@ -646,7 +649,7 @@ function getReasonAndReportActionForGBRInLHNRow(report: OnyxEntry): GBRR * Gets the report action that is causing the RBR to show up in LHN */ function getRBRReportAction(report: OnyxEntry, reportActions: OnyxEntry): OnyxEntry { - const {reportAction} = OptionsListUtils.getAllReportActionsErrorsAndReportActionThatRequiresAttention(report, reportActions); + const {reportAction} = ReportUtils.getAllReportActionsErrorsAndReportActionThatRequiresAttention(report, reportActions); return reportAction; } diff --git a/src/libs/OptionsListUtils.ts b/src/libs/OptionsListUtils.ts index a004974b88e..2bb52b461f5 100644 --- a/src/libs/OptionsListUtils.ts +++ b/src/libs/OptionsListUtils.ts @@ -41,7 +41,6 @@ import type DeepValueOf from '@src/types/utils/DeepValueOf'; import {isEmptyObject} from '@src/types/utils/EmptyObject'; import times from '@src/utils/times'; import Timing from './actions/Timing'; -import * as ErrorUtils from './ErrorUtils'; import filterArrayByMatch from './filterArrayByMatch'; import localeCompare from './LocaleCompare'; import * as LocalePhoneNumber from './LocalePhoneNumber'; @@ -342,26 +341,6 @@ Onyx.connect({ }, }); -let allTransactions: OnyxCollection = {}; -Onyx.connect({ - key: ONYXKEYS.COLLECTION.TRANSACTION, - waitForCollectionCallback: true, - callback: (value) => { - if (!value) { - return; - } - - allTransactions = Object.keys(value) - .filter((key) => !!value[key]) - .reduce((result: OnyxCollection, key) => { - if (result) { - // eslint-disable-next-line no-param-reassign - result[key] = value[key]; - } - return result; - }, {}); - }, -}); let activePolicyID: OnyxEntry; Onyx.connect({ key: ONYXKEYS.NVP_ACTIVE_POLICY_ID, @@ -480,78 +459,6 @@ function uniqFast(items: string[]): string[] { return result; } -type ReportErrorsAndReportActionThatRequiresAttention = { - errors: OnyxCommon.ErrorFields; - reportAction?: OnyxEntry; -}; - -function getAllReportActionsErrorsAndReportActionThatRequiresAttention(report: OnyxEntry, reportActions: OnyxEntry): ReportErrorsAndReportActionThatRequiresAttention { - const reportActionsArray = Object.values(reportActions ?? {}); - const reportActionErrors: OnyxCommon.ErrorFields = {}; - let reportAction: OnyxEntry; - - for (const action of reportActionsArray) { - if (action && !isEmptyObject(action.errors)) { - Object.assign(reportActionErrors, action.errors); - - if (!reportAction) { - reportAction = action; - } - } - } - const parentReportAction: OnyxEntry = - !report?.parentReportID || !report?.parentReportActionID ? undefined : allReportActions?.[report.parentReportID ?? '-1']?.[report.parentReportActionID ?? '-1']; - - if (ReportActionUtils.wasActionTakenByCurrentUser(parentReportAction) && ReportActionUtils.isTransactionThread(parentReportAction)) { - const transactionID = ReportActionUtils.isMoneyRequestAction(parentReportAction) ? ReportActionUtils.getOriginalMessage(parentReportAction)?.IOUTransactionID : null; - const transaction = allTransactions?.[`${ONYXKEYS.COLLECTION.TRANSACTION}${transactionID}`]; - if (TransactionUtils.hasMissingSmartscanFields(transaction ?? null) && !ReportUtils.isSettled(transaction?.reportID)) { - reportActionErrors.smartscan = ErrorUtils.getMicroSecondOnyxErrorWithTranslationKey('iou.error.genericSmartscanFailureMessage'); - reportAction = undefined; - } - } else if ((ReportUtils.isIOUReport(report) || ReportUtils.isExpenseReport(report)) && report?.ownerAccountID === currentUserAccountID) { - if (ReportUtils.shouldShowRBRForMissingSmartscanFields(report?.reportID ?? '-1') && !ReportUtils.isSettled(report?.reportID)) { - reportActionErrors.smartscan = ErrorUtils.getMicroSecondOnyxErrorWithTranslationKey('iou.error.genericSmartscanFailureMessage'); - reportAction = ReportUtils.getReportActionWithMissingSmartscanFields(report?.reportID ?? '-1'); - } - } else if (ReportUtils.hasSmartscanError(reportActionsArray)) { - reportActionErrors.smartscan = ErrorUtils.getMicroSecondOnyxErrorWithTranslationKey('iou.error.genericSmartscanFailureMessage'); - reportAction = ReportUtils.getReportActionWithSmartscanError(reportActionsArray); - } - - return { - errors: reportActionErrors, - reportAction, - }; -} - -/** - * Get an object of error messages keyed by microtime by combining all error objects related to the report. - */ -function getAllReportErrors(report: OnyxEntry, reportActions: OnyxEntry): OnyxCommon.Errors { - const reportErrors = report?.errors ?? {}; - const reportErrorFields = report?.errorFields ?? {}; - const {errors: reportActionErrors} = getAllReportActionsErrorsAndReportActionThatRequiresAttention(report, reportActions); - - // All error objects related to the report. Each object in the sources contains error messages keyed by microtime - const errorSources = { - reportErrors, - ...reportErrorFields, - ...reportActionErrors, - }; - - // Combine all error messages keyed by microtime into one object - const errorSourcesArray = Object.values(errorSources ?? {}); - const allReportErrors = {}; - - for (const errors of errorSourcesArray) { - if (!isEmptyObject(errors)) { - Object.assign(allReportErrors, errors); - } - } - return allReportErrors; -} - /** * Get the last actor display name from last actor details. */ @@ -745,7 +652,7 @@ function getLastMessageTextForReport(report: OnyxEntry, lastActorDetails } function hasReportErrors(report: Report, reportActions: OnyxEntry) { - return !isEmptyObject(getAllReportErrors(report, reportActions)); + return !isEmptyObject(ReportUtils.getAllReportErrors(report, reportActions)); } /** @@ -813,7 +720,7 @@ function createOption( result.shouldShowSubscript = ReportUtils.shouldReportShowSubscript(report); result.isPolicyExpenseChat = ReportUtils.isPolicyExpenseChat(report); result.isOwnPolicyExpenseChat = report.isOwnPolicyExpenseChat ?? false; - result.allReportErrors = getAllReportErrors(report, reportActions); + result.allReportErrors = ReportUtils.getAllReportErrors(report, reportActions); result.brickRoadIndicator = hasReportErrors(report, reportActions) ? CONST.BRICK_ROAD_INDICATOR_STATUS.ERROR : ''; result.pendingAction = report.pendingFields ? report.pendingFields.addWorkspaceRoom ?? report.pendingFields.createChat : undefined; result.ownerAccountID = report.ownerAccountID; @@ -1767,23 +1674,6 @@ function getUserToInviteOption({ return userToInvite; } -/** - * Check whether report has violations - */ -function shouldShowViolations(report: Report, transactionViolations: OnyxCollection) { - const {parentReportID, parentReportActionID} = report ?? {}; - const canGetParentReport = parentReportID && parentReportActionID && allReportActions; - if (!canGetParentReport) { - return false; - } - const parentReportActions = allReportActions ? allReportActions[`${ONYXKEYS.COLLECTION.REPORT_ACTIONS}${parentReportID}`] ?? {} : {}; - const parentReportAction = parentReportActions[parentReportActionID] ?? null; - if (!parentReportAction) { - return false; - } - return ReportUtils.shouldDisplayTransactionThreadViolations(report, transactionViolations, parentReportAction); -} - /** * filter options based on specific conditions */ @@ -1894,7 +1784,7 @@ function getOptions( // Filter out all the reports that shouldn't be displayed const filteredReportOptions = options.reports.filter((option) => { const report = option.item; - const doesReportHaveViolations = shouldShowViolations(report, transactionViolations); + const doesReportHaveViolations = ReportUtils.shouldShowViolations(report, transactionViolations); return ReportUtils.shouldReportBeInOptionList({ report, @@ -2625,7 +2515,6 @@ export { getPersonalDetailsForAccountIDs, getIOUConfirmationOptionsFromPayeePersonalDetail, isSearchStringMatchUserDetails, - getAllReportErrors, getPolicyExpenseReportOption, getIOUReportIDOfLastAction, getParticipantsOption, @@ -2651,13 +2540,11 @@ export { getFirstKeyForList, canCreateOptimisticPersonalDetailOption, getUserToInviteOption, - shouldShowViolations, getPersonalDetailSearchTerms, getCurrentUserSearchTerms, getEmptyOptions, shouldUseBoldText, getAlternateText, - getAllReportActionsErrorsAndReportActionThatRequiresAttention, hasReportErrors, }; diff --git a/src/libs/ReportUtils.ts b/src/libs/ReportUtils.ts index 4a8d48d4d81..8bf7984e3e3 100644 --- a/src/libs/ReportUtils.ts +++ b/src/libs/ReportUtils.ts @@ -48,7 +48,7 @@ import type {Participant} from '@src/types/onyx/IOU'; import type {SelectedParticipant} from '@src/types/onyx/NewGroupChatDraft'; import type {OriginalMessageExportedToIntegration} from '@src/types/onyx/OldDotAction'; import type Onboarding from '@src/types/onyx/Onboarding'; -import type {Errors, Icon, PendingAction} from '@src/types/onyx/OnyxCommon'; +import type {ErrorFields, Errors, Icon, PendingAction} from '@src/types/onyx/OnyxCommon'; import type {OriginalMessageChangeLog, PaymentMethodType} from '@src/types/onyx/OriginalMessage'; import type {Status} from '@src/types/onyx/PersonalDetails'; import type {ConnectionName} from '@src/types/onyx/Policy'; @@ -64,6 +64,7 @@ import * as SessionUtils from './actions/Session'; import * as CurrencyUtils from './CurrencyUtils'; import DateUtils from './DateUtils'; import {hasValidDraftComment} from './DraftCommentUtils'; +import * as ErrorUtils from './ErrorUtils'; import getAttachmentDetails from './fileDownload/getAttachmentDetails'; import getIsSmallScreenWidth from './getIsSmallScreenWidth'; import isReportMessageAttachment from './isReportMessageAttachment'; @@ -1375,7 +1376,7 @@ function findLastAccessedReport(ignoreDomainRooms: boolean, openOnAdminRoom = fa } // We allow public announce rooms, admins, and announce rooms through since we bypass the default rooms beta for them. - // Check where ReportUtils.findLastAccessedReport is called in MainDrawerNavigator.js for more context. + // Check where findLastAccessedReport is called in MainDrawerNavigator.js for more context. // Domain rooms are now the only type of default room that are on the defaultRooms beta. if (ignoreDomainRooms && isDomainRoom(report) && !hasExpensifyGuidesEmails(Object.keys(report?.participants ?? {}).map(Number))) { return false; @@ -6217,6 +6218,112 @@ function shouldAdminsRoomBeVisible(report: OnyxEntry): boolean { return true; } +/** + * Check whether report has violations + */ +function shouldShowViolations(report: Report, transactionViolations: OnyxCollection) { + const {parentReportID, parentReportActionID} = report ?? {}; + const canGetParentReport = parentReportID && parentReportActionID && allReportActions; + if (!canGetParentReport) { + return false; + } + const parentReportActions = allReportActions ? allReportActions[`${ONYXKEYS.COLLECTION.REPORT_ACTIONS}${parentReportID}`] ?? {} : {}; + const parentReportAction = parentReportActions[parentReportActionID] ?? null; + if (!parentReportAction) { + return false; + } + return shouldDisplayTransactionThreadViolations(report, transactionViolations, parentReportAction); +} + +type ReportErrorsAndReportActionThatRequiresAttention = { + errors: ErrorFields; + reportAction?: OnyxEntry; +}; + +function getAllReportActionsErrorsAndReportActionThatRequiresAttention(report: OnyxEntry, reportActions: OnyxEntry): ReportErrorsAndReportActionThatRequiresAttention { + const reportActionsArray = Object.values(reportActions ?? {}); + const reportActionErrors: ErrorFields = {}; + let reportAction: OnyxEntry; + + for (const action of reportActionsArray) { + if (action && !isEmptyObject(action.errors)) { + Object.assign(reportActionErrors, action.errors); + + if (!reportAction) { + reportAction = action; + } + } + } + const parentReportAction: OnyxEntry = + !report?.parentReportID || !report?.parentReportActionID ? undefined : allReportActions?.[report.parentReportID ?? '-1']?.[report.parentReportActionID ?? '-1']; + + if (ReportActionsUtils.wasActionTakenByCurrentUser(parentReportAction) && ReportActionsUtils.isTransactionThread(parentReportAction)) { + const transactionID = ReportActionsUtils.isMoneyRequestAction(parentReportAction) ? ReportActionsUtils.getOriginalMessage(parentReportAction)?.IOUTransactionID : null; + const transaction = allTransactions?.[`${ONYXKEYS.COLLECTION.TRANSACTION}${transactionID}`]; + if (TransactionUtils.hasMissingSmartscanFields(transaction ?? null) && !isSettled(transaction?.reportID)) { + reportActionErrors.smartscan = ErrorUtils.getMicroSecondOnyxErrorWithTranslationKey('iou.error.genericSmartscanFailureMessage'); + reportAction = undefined; + } + } else if ((isIOUReport(report) || isExpenseReport(report)) && report?.ownerAccountID === currentUserAccountID) { + if (shouldShowRBRForMissingSmartscanFields(report?.reportID ?? '-1') && !isSettled(report?.reportID)) { + reportActionErrors.smartscan = ErrorUtils.getMicroSecondOnyxErrorWithTranslationKey('iou.error.genericSmartscanFailureMessage'); + reportAction = getReportActionWithMissingSmartscanFields(report?.reportID ?? '-1'); + } + } else if (hasSmartscanError(reportActionsArray)) { + reportActionErrors.smartscan = ErrorUtils.getMicroSecondOnyxErrorWithTranslationKey('iou.error.genericSmartscanFailureMessage'); + reportAction = getReportActionWithSmartscanError(reportActionsArray); + } + + return { + errors: reportActionErrors, + reportAction, + }; +} + +/** + * Get an object of error messages keyed by microtime by combining all error objects related to the report. + */ +function getAllReportErrors(report: OnyxEntry, reportActions: OnyxEntry): Errors { + const reportErrors = report?.errors ?? {}; + const reportErrorFields = report?.errorFields ?? {}; + const {errors: reportActionErrors} = getAllReportActionsErrorsAndReportActionThatRequiresAttention(report, reportActions); + + // All error objects related to the report. Each object in the sources contains error messages keyed by microtime + const errorSources = { + reportErrors, + ...reportErrorFields, + ...reportActionErrors, + }; + + // Combine all error messages keyed by microtime into one object + const errorSourcesArray = Object.values(errorSources ?? {}); + const allReportErrors = {}; + + for (const errors of errorSourcesArray) { + if (!isEmptyObject(errors)) { + Object.assign(allReportErrors, errors); + } + } + return allReportErrors; +} + +function hasReportErrorsOtherThanFailedReceipt(report: Report, doesReportHaveViolations: boolean, transactionViolations: OnyxCollection) { + const reportActions = allReportActions?.[`${ONYXKEYS.COLLECTION.REPORT_ACTIONS}${report.reportID}`] ?? {}; + const allReportErrors = getAllReportErrors(report, reportActions) ?? {}; + const transactionReportActions = ReportActionsUtils.getAllReportActions(report.reportID); + const oneTransactionThreadReportID = ReportActionsUtils.getOneTransactionThreadReportID(report.reportID, transactionReportActions, undefined); + let doesTransactionThreadReportHasViolations = false; + if (oneTransactionThreadReportID) { + const transactionReport = getReport(oneTransactionThreadReportID); + doesTransactionThreadReportHasViolations = !!transactionReport && shouldShowViolations(transactionReport, transactionViolations); + } + return ( + doesTransactionThreadReportHasViolations || + doesReportHaveViolations || + Object.values(allReportErrors).some((error) => error?.[0] !== Localize.translateLocal('iou.error.genericSmartscanFailureMessage')) + ); +} + type ShouldReportBeInOptionListParams = { report: OnyxEntry; currentReportId: string; @@ -8428,6 +8535,10 @@ export { hasMissingInvoiceBankAccount, reasonForReportToBeInOptionList, getReasonAndReportActionThatRequiresAttention, + hasReportErrorsOtherThanFailedReceipt, + shouldShowViolations, + getAllReportErrors, + getAllReportActionsErrorsAndReportActionThatRequiresAttention, }; export type { diff --git a/src/libs/SidebarUtils.ts b/src/libs/SidebarUtils.ts index 99811645a6e..dd62b40a249 100644 --- a/src/libs/SidebarUtils.ts +++ b/src/libs/SidebarUtils.ts @@ -105,23 +105,11 @@ function getOrderedReportIDs( if ((Object.values(CONST.REPORT.UNSUPPORTED_TYPE) as string[]).includes(report?.type ?? '')) { return; } - const reportActions = allReportActions?.[`${ONYXKEYS.COLLECTION.REPORT_ACTIONS}${report.reportID}`] ?? {}; const parentReportAction = ReportActionsUtils.getReportAction(report?.parentReportID ?? '-1', report?.parentReportActionID ?? '-1'); - const doesReportHaveViolations = OptionsListUtils.shouldShowViolations(report, transactionViolations); + const doesReportHaveViolations = ReportUtils.shouldShowViolations(report, transactionViolations); const isHidden = ReportUtils.getReportNotificationPreference(report) === CONST.REPORT.NOTIFICATION_PREFERENCE.HIDDEN; const isFocused = report.reportID === currentReportId; - const allReportErrors = OptionsListUtils.getAllReportErrors(report, reportActions) ?? {}; - const transactionReportActions = ReportActionsUtils.getAllReportActions(report.reportID); - const oneTransactionThreadReportID = ReportActionsUtils.getOneTransactionThreadReportID(report.reportID, transactionReportActions, undefined); - let doesTransactionThreadReportHasViolations = false; - if (oneTransactionThreadReportID) { - const transactionReport = ReportUtils.getReport(oneTransactionThreadReportID); - doesTransactionThreadReportHasViolations = !!transactionReport && OptionsListUtils.shouldShowViolations(transactionReport, transactionViolations); - } - const hasErrorsOtherThanFailedReceipt = - doesTransactionThreadReportHasViolations || - doesReportHaveViolations || - Object.values(allReportErrors).some((error) => error?.[0] !== Localize.translateLocal('iou.error.genericSmartscanFailureMessage')); + const hasErrorsOtherThanFailedReceipt = ReportUtils.hasReportErrorsOtherThanFailedReceipt(report, doesReportHaveViolations, transactionViolations); const isReportInAccessible = report?.errorFields?.notFound; if (ReportUtils.isOneTransactionThread(report.reportID, report.parentReportID ?? '0', parentReportAction)) { return; @@ -235,7 +223,7 @@ function getOrderedReportIDs( } function shouldShowRedBrickRoad(report: Report, reportActions: OnyxEntry, hasViolations: boolean, transactionViolations?: OnyxCollection) { - const hasErrors = Object.keys(OptionsListUtils.getAllReportErrors(report, reportActions)).length !== 0; + const hasErrors = Object.keys(ReportUtils.getAllReportErrors(report, reportActions)).length !== 0; const oneTransactionThreadReportID = ReportActionsUtils.getOneTransactionThreadReportID(report.reportID, ReportActionsUtils.getAllReportActions(report.reportID)); if (oneTransactionThreadReportID) { @@ -291,7 +279,7 @@ function getOptionData({ const result: ReportUtils.OptionData = { text: '', alternateText: undefined, - allReportErrors: OptionsListUtils.getAllReportErrors(report, reportActions), + allReportErrors: ReportUtils.getAllReportErrors(report, reportActions), brickRoadIndicator: null, tooltipText: null, subtitle: undefined, diff --git a/src/libs/WorkspacesSettingsUtils.ts b/src/libs/WorkspacesSettingsUtils.ts index d8cd2ff0082..b8365d9e89d 100644 --- a/src/libs/WorkspacesSettingsUtils.ts +++ b/src/libs/WorkspacesSettingsUtils.ts @@ -60,7 +60,7 @@ Onyx.connect({ */ const getBrickRoadForPolicy = (report: Report, altReportActions?: OnyxCollection): BrickRoad => { const reportActions = (altReportActions ?? allReportActions)?.[`${ONYXKEYS.COLLECTION.REPORT_ACTIONS}${report.reportID}`] ?? {}; - const reportErrors = OptionsListUtils.getAllReportErrors(report, reportActions); + const reportErrors = ReportUtils.getAllReportErrors(report, reportActions); const oneTransactionThreadReportID = ReportActionsUtils.getOneTransactionThreadReportID(report.reportID, reportActions); let doesReportContainErrors = Object.keys(reportErrors ?? {}).length !== 0 ? CONST.BRICK_ROAD_INDICATOR_STATUS.ERROR : undefined; From 39bdf3309c65663506eedcab50cc50b58c7b81e2 Mon Sep 17 00:00:00 2001 From: Pedro Guerreiro Date: Tue, 8 Oct 2024 20:53:29 +0100 Subject: [PATCH 047/121] chore: fix test cases failing for DebugUtils --- src/libs/DebugUtils.ts | 10 +++++----- 1 file changed, 5 insertions(+), 5 deletions(-) diff --git a/src/libs/DebugUtils.ts b/src/libs/DebugUtils.ts index af682e9cdc6..6e95dca7114 100644 --- a/src/libs/DebugUtils.ts +++ b/src/libs/DebugUtils.ts @@ -599,10 +599,6 @@ function getReasonForShowingRowInLHN(report: OnyxEntry): TranslationPath const doesReportHaveViolations = ReportUtils.shouldShowViolations(report, transactionViolations); - if (ReportUtils.hasReportErrorsOtherThanFailedReceipt(report, doesReportHaveViolations, transactionViolations)) { - return `debug.reasonVisibleInLHN.hasRBR`; - } - const reason = ReportUtils.reasonForReportToBeInOptionList({ report, // We can't pass report.reportID because it will cause reason to always be isFocused @@ -615,8 +611,12 @@ function getReasonForShowingRowInLHN(report: OnyxEntry): TranslationPath includeSelfDM: true, }); - // When there's no specific reason, we default to isFocused since the report is only showing because we're viewing it + // When there's no specific reason, we default to isFocused if the report is only showing because we're viewing it + // Otherwise we return hasRBR if the report has errors other that failed receipt if (reason === null || reason === CONST.REPORT_IN_LHN_REASONS.DEFAULT) { + if (ReportUtils.hasReportErrorsOtherThanFailedReceipt(report, doesReportHaveViolations, transactionViolations)) { + return `debug.reasonVisibleInLHN.hasRBR`; + } return 'debug.reasonVisibleInLHN.isFocused'; } From f0dc3c16dfbee852a5eb5d026ece715568b3ba02 Mon Sep 17 00:00:00 2001 From: Pedro Guerreiro Date: Tue, 8 Oct 2024 20:53:43 +0100 Subject: [PATCH 048/121] chore: remove unused dependency --- src/libs/WorkspacesSettingsUtils.ts | 1 - 1 file changed, 1 deletion(-) diff --git a/src/libs/WorkspacesSettingsUtils.ts b/src/libs/WorkspacesSettingsUtils.ts index b8365d9e89d..2be641035be 100644 --- a/src/libs/WorkspacesSettingsUtils.ts +++ b/src/libs/WorkspacesSettingsUtils.ts @@ -9,7 +9,6 @@ import type {Policy, ReimbursementAccount, Report, ReportAction, ReportActions, import type {PolicyConnectionSyncProgress, Unit} from '@src/types/onyx/Policy'; import {isConnectionInProgress} from './actions/connections'; import * as CurrencyUtils from './CurrencyUtils'; -import * as OptionsListUtils from './OptionsListUtils'; import {hasCustomUnitsError, hasEmployeeListError, hasPolicyError, hasSyncError, hasTaxRateError} from './PolicyUtils'; import * as ReportActionsUtils from './ReportActionsUtils'; import * as ReportConnection from './ReportConnection'; From 5103474c5ab6945c857d17eca6afe671372e9304 Mon Sep 17 00:00:00 2001 From: krishna2323 Date: Wed, 9 Oct 2024 11:43:39 +0530 Subject: [PATCH 049/121] fix: Workspace settings - There is back button on not found page for wide screen. Signed-off-by: krishna2323 --- src/pages/workspace/AccessOrNotFoundWrapper.tsx | 3 +++ 1 file changed, 3 insertions(+) diff --git a/src/pages/workspace/AccessOrNotFoundWrapper.tsx b/src/pages/workspace/AccessOrNotFoundWrapper.tsx index 5deae769531..e5d42eb612d 100644 --- a/src/pages/workspace/AccessOrNotFoundWrapper.tsx +++ b/src/pages/workspace/AccessOrNotFoundWrapper.tsx @@ -6,6 +6,7 @@ import type {FullPageNotFoundViewProps} from '@components/BlockingViews/FullPage import FullscreenLoadingIndicator from '@components/FullscreenLoadingIndicator'; import useCurrentUserPersonalDetails from '@hooks/useCurrentUserPersonalDetails'; import useNetwork from '@hooks/useNetwork'; +import useResponsiveLayout from '@hooks/useResponsiveLayout'; import * as IOUUtils from '@libs/IOUUtils'; import Navigation from '@libs/Navigation/Navigation'; import * as PolicyUtils from '@libs/PolicyUtils'; @@ -86,6 +87,7 @@ type AccessOrNotFoundWrapperProps = { type PageNotFoundFallbackProps = Pick & {shouldShowFullScreenFallback: boolean; isMoneyRequest: boolean}; function PageNotFoundFallback({policyID, shouldShowFullScreenFallback, fullPageNotFoundViewProps, isMoneyRequest}: PageNotFoundFallbackProps) { + const {shouldUseNarrowLayout} = useResponsiveLayout(); return ( ); } From 266fd69debc91df1ff38c5bc5b4343596148c42c Mon Sep 17 00:00:00 2001 From: Anusha Date: Wed, 9 Oct 2024 22:39:39 +0500 Subject: [PATCH 050/121] fix dependency --- src/pages/home/ReportScreen.tsx | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/pages/home/ReportScreen.tsx b/src/pages/home/ReportScreen.tsx index 587fe84c75b..4a87d51e3c8 100644 --- a/src/pages/home/ReportScreen.tsx +++ b/src/pages/home/ReportScreen.tsx @@ -667,7 +667,7 @@ function ReportScreen({route, currentReportID = '', navigation}: ReportScreenPro return; } Navigation.setParams({reportActionID: ''}); - }, [prevIsLinkedActionDeleted, isLinkedActionDeleted]); + }, [isLinkedActionBecomesDeleted]); // If user redirects to an inaccessible whisper via a deeplink, on a report they have access to, // then we set reportActionID as empty string, so we display them the report and not the "Not found page". From 13807c5f0132562e6569284c0b41261ba3ea8558 Mon Sep 17 00:00:00 2001 From: Pedro Guerreiro Date: Thu, 10 Oct 2024 09:51:59 +0100 Subject: [PATCH 051/121] fix(debug mode): hasGBR showing instead of hasRBR in debug details page --- src/libs/DebugUtils.ts | 7 ++++--- 1 file changed, 4 insertions(+), 3 deletions(-) diff --git a/src/libs/DebugUtils.ts b/src/libs/DebugUtils.ts index 3b06c8b9854..db84c2b473a 100644 --- a/src/libs/DebugUtils.ts +++ b/src/libs/DebugUtils.ts @@ -610,12 +610,13 @@ function getReasonForShowingRowInLHN(report: OnyxEntry): TranslationPath includeSelfDM: true, }); + if (ReportUtils.hasReportErrorsOtherThanFailedReceipt(report, doesReportHaveViolations, transactionViolations)) { + return `debug.reasonVisibleInLHN.hasRBR`; + } + // When there's no specific reason, we default to isFocused if the report is only showing because we're viewing it // Otherwise we return hasRBR if the report has errors other that failed receipt if (reason === null || reason === CONST.REPORT_IN_LHN_REASONS.DEFAULT) { - if (ReportUtils.hasReportErrorsOtherThanFailedReceipt(report, doesReportHaveViolations, transactionViolations)) { - return `debug.reasonVisibleInLHN.hasRBR`; - } return 'debug.reasonVisibleInLHN.isFocused'; } From 9358babd497b9c7f7b857bb32c6adc87660122fd Mon Sep 17 00:00:00 2001 From: Pedro Guerreiro Date: Thu, 10 Oct 2024 10:18:17 +0100 Subject: [PATCH 052/121] fix(debug mode): not showing specific RBR reasons for report to be in LHN --- src/libs/DebugUtils.ts | 5 ++++- tests/unit/DebugUtilsTest.ts | 5 ++--- 2 files changed, 6 insertions(+), 4 deletions(-) diff --git a/src/libs/DebugUtils.ts b/src/libs/DebugUtils.ts index db84c2b473a..3b6c1c77713 100644 --- a/src/libs/DebugUtils.ts +++ b/src/libs/DebugUtils.ts @@ -610,7 +610,10 @@ function getReasonForShowingRowInLHN(report: OnyxEntry): TranslationPath includeSelfDM: true, }); - if (ReportUtils.hasReportErrorsOtherThanFailedReceipt(report, doesReportHaveViolations, transactionViolations)) { + if ( + !([CONST.REPORT_IN_LHN_REASONS.HAS_ADD_WORKSPACE_ROOM_ERRORS, CONST.REPORT_IN_LHN_REASONS.HAS_IOU_VIOLATIONS] as Array).includes(reason) && + ReportUtils.hasReportErrorsOtherThanFailedReceipt(report, doesReportHaveViolations, transactionViolations) + ) { return `debug.reasonVisibleInLHN.hasRBR`; } diff --git a/tests/unit/DebugUtilsTest.ts b/tests/unit/DebugUtilsTest.ts index 34c2ad2bde7..7ac77fa1b44 100644 --- a/tests/unit/DebugUtilsTest.ts +++ b/tests/unit/DebugUtilsTest.ts @@ -963,8 +963,7 @@ describe('DebugUtils', () => { ); expect(reportAction).toBeUndefined(); }); - // TODO: remove '.failing' once the implementation is fixed - it.failing('returns parentReportAction if it is a transaction thread, the transaction is missing smart scan fields and the report is not settled', async () => { + it('returns undefined if it is a transaction thread, the transaction is missing smart scan fields and the report is not settled', async () => { const MOCK_REPORTS: ReportCollectionDataSet = { [`${ONYXKEYS.COLLECTION.REPORT}1` as const]: { reportID: '1', @@ -1011,7 +1010,7 @@ describe('DebugUtils', () => { MOCK_REPORTS[`${ONYXKEYS.COLLECTION.REPORT}1`] as Report, undefined, ); - expect(reportAction).toBe(1); + expect(reportAction).toBe(undefined); }); describe("Report has missing fields, isn't settled and it's owner is the current user", () => { describe('Report is IOU', () => { From 9f8e8c5abcb7c3d842398de18a516eb8b79d36ce Mon Sep 17 00:00:00 2001 From: nkdengineer Date: Thu, 10 Oct 2024 16:32:29 +0700 Subject: [PATCH 053/121] fix: Not here page opens when opening distance receipt from another workspace --- src/ROUTES.ts | 4 +++- src/components/ReportActionItem/MoneyRequestView.tsx | 5 ++++- .../ReportActionItem/ReportActionItemImage.tsx | 10 +++++++++- src/libs/Navigation/types.ts | 1 + src/pages/TransactionDuplicate/Confirmation.tsx | 1 + src/pages/TransactionReceiptPage.tsx | 4 +++- 6 files changed, 21 insertions(+), 4 deletions(-) diff --git a/src/ROUTES.ts b/src/ROUTES.ts index 02bb07de83b..e554b623bd5 100644 --- a/src/ROUTES.ts +++ b/src/ROUTES.ts @@ -1176,8 +1176,10 @@ const ROUTES = { TRANSACTION_RECEIPT: { route: 'r/:reportID/transaction/:transactionID/receipt', - getRoute: (reportID: string, transactionID: string, readonly = false) => `r/${reportID}/transaction/${transactionID}/receipt${readonly ? '?readonly=true' : ''}` as const, + getRoute: (reportID: string, transactionID: string, readonly = false, isFromReviewDuplicates = false) => + `r/${reportID}/transaction/${transactionID}/receipt${readonly ? '?readonly=true' : ''}${isFromReviewDuplicates ? '?isFromReviewDuplicates=true' : ''}` as const, }, + TRANSACTION_DUPLICATE_REVIEW_PAGE: { route: 'r/:threadReportID/duplicates/review', getRoute: (threadReportID: string, backTo?: string) => getUrlWithBackToParam(`r/${threadReportID}/duplicates/review` as const, backTo), diff --git a/src/components/ReportActionItem/MoneyRequestView.tsx b/src/components/ReportActionItem/MoneyRequestView.tsx index 9a086196263..4af17b194d2 100644 --- a/src/components/ReportActionItem/MoneyRequestView.tsx +++ b/src/components/ReportActionItem/MoneyRequestView.tsx @@ -56,6 +56,8 @@ type MoneyRequestViewProps = { /** Whether we should show Money Request with disabled all fields */ readonly?: boolean; + isFromReviewDuplicates?: boolean; + /** Updated transaction to show in duplicate transaction flow */ updatedTransaction?: OnyxEntry; }; @@ -75,7 +77,7 @@ const getTransactionID = (report: OnyxEntry, parentReportActio return originalMessage?.IOUTransactionID ?? -1; }; -function MoneyRequestView({report, shouldShowAnimatedBackground, readonly = false, updatedTransaction}: MoneyRequestViewProps) { +function MoneyRequestView({report, shouldShowAnimatedBackground, readonly = false, updatedTransaction, isFromReviewDuplicates}: MoneyRequestViewProps) { const theme = useTheme(); const styles = useThemeStyles(); const session = useSession(); @@ -508,6 +510,7 @@ function MoneyRequestView({report, shouldShowAnimatedBackground, readonly = fals transaction={updatedTransaction ?? transaction} enablePreviewModal readonly={readonly || !canEditReceipt} + isFromReviewDuplicates={isFromReviewDuplicates} /> )} diff --git a/src/components/ReportActionItem/ReportActionItemImage.tsx b/src/components/ReportActionItem/ReportActionItemImage.tsx index d967a914c9f..c140222517c 100644 --- a/src/components/ReportActionItem/ReportActionItemImage.tsx +++ b/src/components/ReportActionItem/ReportActionItemImage.tsx @@ -55,6 +55,8 @@ type ReportActionItemImageProps = { /** Whether the receipt is not editable */ readonly?: boolean; + + isFromReviewDuplicates?: boolean; }; /** @@ -75,6 +77,7 @@ function ReportActionItemImage({ isSingleImage = true, readonly = false, shouldMapHaveBorderRadius, + isFromReviewDuplicates, }: ReportActionItemImageProps) { const styles = useThemeStyles(); const {translate} = useLocalize(); @@ -135,7 +138,12 @@ function ReportActionItemImage({ style={[styles.w100, styles.h100, styles.noOutline as ViewStyle]} onPress={() => Navigation.navigate( - ROUTES.TRANSACTION_RECEIPT.getRoute(transactionThreadReport?.reportID ?? report?.reportID ?? '-1', transaction?.transactionID ?? '-1', readonly), + ROUTES.TRANSACTION_RECEIPT.getRoute( + transactionThreadReport?.reportID ?? report?.reportID ?? '-1', + transaction?.transactionID ?? '-1', + readonly, + isFromReviewDuplicates, + ), ) } accessibilityLabel={translate('accessibilityHints.viewAttachment')} diff --git a/src/libs/Navigation/types.ts b/src/libs/Navigation/types.ts index 3e7c03b4098..60049a22a3f 100644 --- a/src/libs/Navigation/types.ts +++ b/src/libs/Navigation/types.ts @@ -1481,6 +1481,7 @@ type AuthScreensParamList = CentralPaneScreensParamList & reportID: string; transactionID: string; readonly?: boolean; + isFromReviewDuplicates?: boolean; }; [SCREENS.CONNECTION_COMPLETE]: undefined; }; diff --git a/src/pages/TransactionDuplicate/Confirmation.tsx b/src/pages/TransactionDuplicate/Confirmation.tsx index b26ae615b46..87748a9697a 100644 --- a/src/pages/TransactionDuplicate/Confirmation.tsx +++ b/src/pages/TransactionDuplicate/Confirmation.tsx @@ -118,6 +118,7 @@ function Confirmation() { report={report} shouldShowAnimatedBackground={false} readonly + isFromReviewDuplicates updatedTransaction={transaction as OnyxEntry} /> diff --git a/src/pages/TransactionReceiptPage.tsx b/src/pages/TransactionReceiptPage.tsx index 194759f78cc..c05d90a180e 100644 --- a/src/pages/TransactionReceiptPage.tsx +++ b/src/pages/TransactionReceiptPage.tsx @@ -28,6 +28,7 @@ function TransactionReceipt({route}: TransactionReceiptProps) { const isLocalFile = receiptURIs.isLocalFile; const readonly = route.params.readonly ?? false; + const isFromReviewDuplicates = route.params.isFromReviewDuplicates ?? false; const parentReportAction = ReportActionUtils.getReportAction(report?.parentReportID ?? '-1', report?.parentReportActionID ?? '-1'); const canEditReceipt = ReportUtils.canEditFieldOfMoneyRequest(parentReportAction, CONST.EDIT_REQUEST_FIELD.RECEIPT); @@ -61,7 +62,8 @@ function TransactionReceipt({route}: TransactionReceiptProps) { const isTrackExpenseReport = ReportUtils.isTrackExpenseReport(report); // eslint-disable-next-line rulesdir/no-negated-variables - const shouldShowNotFoundPage = isTrackExpenseReport || transaction?.reportID === CONST.REPORT.SPLIT_REPORTID ? !transaction : (moneyRequestReportID ?? '-1') !== transaction?.reportID; + const shouldShowNotFoundPage = + isTrackExpenseReport || transaction?.reportID === CONST.REPORT.SPLIT_REPORTID || isFromReviewDuplicates ? !transaction : (moneyRequestReportID ?? '-1') !== transaction?.reportID; return ( Date: Thu, 10 Oct 2024 13:03:03 +0300 Subject: [PATCH 054/121] Fix paid green marker for Native --- src/components/ReportActionItem/ReportPreview.tsx | 11 ++--------- 1 file changed, 2 insertions(+), 9 deletions(-) diff --git a/src/components/ReportActionItem/ReportPreview.tsx b/src/components/ReportActionItem/ReportPreview.tsx index f10951f2b1a..4cd0341a271 100644 --- a/src/components/ReportActionItem/ReportPreview.tsx +++ b/src/components/ReportActionItem/ReportPreview.tsx @@ -134,16 +134,9 @@ function ReportPreview({ const iouSettled = ReportUtils.isSettled(iouReportID) || action?.childStatusNum === CONST.REPORT.STATUS_NUM.REIMBURSED; const previewMessageOpacity = useSharedValue(1); const previewMessageStyle = useAnimatedStyle(() => ({ - ...styles.flex1, - ...styles.flexRow, - ...styles.alignItemsCenter, opacity: previewMessageOpacity.value, })); const checkMarkScale = useSharedValue(iouSettled ? 1 : 0); - const checkMarkStyle = useAnimatedStyle(() => ({ - ...styles.defaultCheckmarkWrapper, - transform: [{scale: checkMarkScale.value}], - })); const moneyRequestComment = action?.childLastMoneyRequestComment ?? ''; const isPolicyExpenseChat = ReportUtils.isPolicyExpenseChat(chatReport); @@ -471,7 +464,7 @@ function ReportPreview({ - + {previewMessage} {shouldShowRBR && ( @@ -493,7 +486,7 @@ function ReportPreview({ {getDisplayAmount()} {iouSettled && ( - + Date: Thu, 10 Oct 2024 12:16:03 +0200 Subject: [PATCH 055/121] fix PR comments --- src/components/HeaderWithBackButton/index.tsx | 2 +- src/components/Search/SearchRouter/SearchRouter.tsx | 3 +-- src/components/Search/SearchRouter/SearchRouterList.tsx | 2 +- src/styles/variables.ts | 2 +- 4 files changed, 4 insertions(+), 5 deletions(-) diff --git a/src/components/HeaderWithBackButton/index.tsx b/src/components/HeaderWithBackButton/index.tsx index eb04ad5540e..e1843ee506d 100755 --- a/src/components/HeaderWithBackButton/index.tsx +++ b/src/components/HeaderWithBackButton/index.tsx @@ -191,7 +191,7 @@ function HeaderWithBackButton({ /> )} {middleContent} - + {children} {shouldShowDownloadButton && ( diff --git a/src/components/Search/SearchRouter/SearchRouter.tsx b/src/components/Search/SearchRouter/SearchRouter.tsx index f49112e86c6..564e5f0feda 100644 --- a/src/components/Search/SearchRouter/SearchRouter.tsx +++ b/src/components/Search/SearchRouter/SearchRouter.tsx @@ -150,8 +150,7 @@ function SearchRouter({onRouterClose}: SearchRouterProps) { ); useKeyboardShortcut(CONST.KEYBOARD_SHORTCUTS.ESCAPE, () => { - onRouterClose(); - clearUserQuery(); + closeAndClearRouter(); }); const modalWidth = isSmallScreenWidth ? styles.w100 : {width: variables.popoverWidth}; diff --git a/src/components/Search/SearchRouter/SearchRouterList.tsx b/src/components/Search/SearchRouter/SearchRouterList.tsx index 70abc5c8955..d05588afbe9 100644 --- a/src/components/Search/SearchRouter/SearchRouterList.tsx +++ b/src/components/Search/SearchRouter/SearchRouterList.tsx @@ -167,7 +167,7 @@ function SearchRouterList( // Handle selection of "Recent chat" closeAndClearRouter(); if ('reportID' in item && item?.reportID) { - Navigation.closeAndNavigate(ROUTES.REPORT_WITH_ID.getRoute(item?.reportID)); + Navigation.navigate(ROUTES.REPORT_WITH_ID.getRoute(item?.reportID)); } else if ('login' in item) { Report.navigateToAndOpenReport(item?.login ? [item.login] : []); } diff --git a/src/styles/variables.ts b/src/styles/variables.ts index 82d443928c7..cd81e5fcb9c 100644 --- a/src/styles/variables.ts +++ b/src/styles/variables.ts @@ -166,7 +166,7 @@ export default { signInLogoWidthLargeScreenPill: 162, modalContentMaxWidth: 360, listItemHeightNormal: 64, - popoverWidth: 375, + popoverWidth: 512, bankAccountActionPopoverRightSpacing: 32, bankAccountActionPopoverTopSpacing: 14, addPaymentPopoverRightSpacing: 23, From 7e44690b9ffe42ad43d43e3b4d074efe3518ed81 Mon Sep 17 00:00:00 2001 From: Jakub Szymczak Date: Thu, 10 Oct 2024 12:32:26 +0200 Subject: [PATCH 056/121] fix paddings --- src/components/Search/SearchRouter/SearchButton.tsx | 9 +++++++-- src/pages/home/HeaderView.tsx | 2 +- 2 files changed, 8 insertions(+), 3 deletions(-) diff --git a/src/components/Search/SearchRouter/SearchButton.tsx b/src/components/Search/SearchRouter/SearchButton.tsx index 2ddc9a8262c..ae755fbf2e2 100644 --- a/src/components/Search/SearchRouter/SearchButton.tsx +++ b/src/components/Search/SearchRouter/SearchButton.tsx @@ -1,4 +1,5 @@ import React from 'react'; +import type {StyleProp, ViewStyle} from 'react-native'; import Icon from '@components/Icon'; import * as Expensicons from '@components/Icon/Expensicons'; import {PressableWithoutFeedback} from '@components/Pressable'; @@ -11,7 +12,11 @@ import Timing from '@userActions/Timing'; import CONST from '@src/CONST'; import {useSearchRouterContext} from './SearchRouterContext'; -function SearchButton() { +type SearchButtonProps = { + style?: StyleProp; +}; + +function SearchButton({style}: SearchButtonProps) { const styles = useThemeStyles(); const theme = useTheme(); const {translate} = useLocalize(); @@ -20,7 +25,7 @@ function SearchButton() { return ( { Timing.start(CONST.TIMING.SEARCH_ROUTER_OPEN); Performance.markStart(CONST.TIMING.SEARCH_ROUTER_OPEN); diff --git a/src/pages/home/HeaderView.tsx b/src/pages/home/HeaderView.tsx index 42483cc3d22..31aaf7cb1f1 100644 --- a/src/pages/home/HeaderView.tsx +++ b/src/pages/home/HeaderView.tsx @@ -280,7 +280,7 @@ function HeaderView({report, parentReportAction, reportID, onNavigationMenuButto {isTaskReport && !shouldUseNarrowLayout && ReportUtils.isOpenTaskReport(report, parentReportAction) && } {canJoin && !shouldUseNarrowLayout && joinButton} - + Date: Thu, 10 Oct 2024 15:21:25 +0200 Subject: [PATCH 057/121] fix linter --- .../TopBar.tsx | 33 +++---------------- 1 file changed, 4 insertions(+), 29 deletions(-) diff --git a/src/libs/Navigation/AppNavigator/createCustomPlatformStackBottomTabNavigator/TopBar.tsx b/src/libs/Navigation/AppNavigator/createCustomPlatformStackBottomTabNavigator/TopBar.tsx index 4684eb9637b..8967486165f 100644 --- a/src/libs/Navigation/AppNavigator/createCustomPlatformStackBottomTabNavigator/TopBar.tsx +++ b/src/libs/Navigation/AppNavigator/createCustomPlatformStackBottomTabNavigator/TopBar.tsx @@ -2,32 +2,25 @@ import React from 'react'; import {View} from 'react-native'; import {useOnyx} from 'react-native-onyx'; import Breadcrumbs from '@components/Breadcrumbs'; -import Icon from '@components/Icon'; -import * as Expensicons from '@components/Icon/Expensicons'; import {PressableWithoutFeedback} from '@components/Pressable'; import SearchButton from '@components/Search/SearchRouter/SearchButton'; import Text from '@components/Text'; -import Tooltip from '@components/Tooltip'; import WorkspaceSwitcherButton from '@components/WorkspaceSwitcherButton'; import useLocalize from '@hooks/useLocalize'; import usePolicy from '@hooks/usePolicy'; -import useTheme from '@hooks/useTheme'; import useThemeStyles from '@hooks/useThemeStyles'; import Navigation from '@libs/Navigation/Navigation'; -import Performance from '@libs/Performance'; import * as SearchUtils from '@libs/SearchUtils'; import SignInButton from '@pages/home/sidebar/SignInButton'; import * as Session from '@userActions/Session'; -import Timing from '@userActions/Timing'; import CONST from '@src/CONST'; import ONYXKEYS from '@src/ONYXKEYS'; import ROUTES from '@src/ROUTES'; -type TopBarProps = {breadcrumbLabel: string; activeWorkspaceID?: string; shouldDisplaySearch?: boolean; isCustomSearchQuery?: boolean; shouldDisplaySearchRouter?: boolean}; +type TopBarProps = {breadcrumbLabel: string; activeWorkspaceID?: string; shouldDisplaySearch?: boolean; shouldDisplayCancelSearch?: boolean}; -function TopBar({breadcrumbLabel, activeWorkspaceID, shouldDisplaySearch = true, isCustomSearchQuery = false, shouldDisplaySearchRouter = false}: TopBarProps) { +function TopBar({breadcrumbLabel, activeWorkspaceID, shouldDisplaySearch = true, shouldDisplayCancelSearch = false}: TopBarProps) { const styles = useThemeStyles(); - const theme = useTheme(); const {translate} = useLocalize(); const policy = usePolicy(activeWorkspaceID); const [session] = useOnyx(ONYXKEYS.SESSION, {selector: (sessionValue) => sessionValue && {authTokenType: sessionValue.authTokenType}}); @@ -63,7 +56,7 @@ function TopBar({breadcrumbLabel, activeWorkspaceID, shouldDisplaySearch = true, {displaySignIn && } - {isCustomSearchQuery && ( + {shouldDisplayCancelSearch && ( {translate('common.cancel')} )} - {shouldDisplaySearchRouter && } - {displaySearch && ( - - { - Timing.start(CONST.TIMING.CHAT_FINDER_RENDER); - Performance.markStart(CONST.TIMING.CHAT_FINDER_RENDER); - Navigation.navigate(ROUTES.CHAT_FINDER); - })} - > - - - - )} + {displaySearch && } ); From 2fd6a047a5cc45353e016c42d5fbf72ce252347f Mon Sep 17 00:00:00 2001 From: Pedro Guerreiro Date: Thu, 10 Oct 2024 16:05:19 +0100 Subject: [PATCH 058/121] chore: add test cases for hasRBR --- tests/unit/DebugUtilsTest.ts | 125 +++++++++++++++++++++++++++++++++++ 1 file changed, 125 insertions(+) diff --git a/tests/unit/DebugUtilsTest.ts b/tests/unit/DebugUtilsTest.ts index 7ac77fa1b44..a87648d08ee 100644 --- a/tests/unit/DebugUtilsTest.ts +++ b/tests/unit/DebugUtilsTest.ts @@ -783,6 +783,131 @@ describe('DebugUtils', () => { const reason = DebugUtils.getReasonForShowingRowInLHN(baseReport); expect(reason).toBe('debug.reasonVisibleInLHN.isFocused'); }); + it('returns correct reason when report has one transaction thread with violations', async () => { + const MOCK_TRANSACTION_REPORT: Report = { + reportID: '1', + ownerAccountID: 12345, + type: CONST.REPORT.TYPE.EXPENSE, + }; + const MOCK_REPORTS: ReportCollectionDataSet = { + [`${ONYXKEYS.COLLECTION.REPORT}1` as const]: MOCK_TRANSACTION_REPORT, + [`${ONYXKEYS.COLLECTION.REPORT}2` as const]: { + reportID: '2', + type: CONST.REPORT.TYPE.CHAT, + parentReportID: '1', + parentReportActionID: '1', + stateNum: CONST.REPORT.STATE_NUM.SUBMITTED, + }, + }; + const MOCK_REPORT_ACTIONS: ReportActionsCollectionDataSet = { + [`${ONYXKEYS.COLLECTION.REPORT_ACTIONS}1` as const]: { + // eslint-disable-next-line @typescript-eslint/naming-convention + '1': { + reportActionID: '1', + actionName: CONST.REPORT.ACTIONS.TYPE.IOU, + actorAccountID: 12345, + created: '2024-08-08 18:20:44.171', + childReportID: '2', + message: { + type: CONST.IOU.REPORT_ACTION_TYPE.CREATE, + amount: 10, + currency: CONST.CURRENCY.USD, + IOUReportID: '1', + text: 'Vacation expense', + IOUTransactionID: '1', + }, + }, + }, + }; + await Onyx.multiSet({ + ...MOCK_REPORTS, + ...MOCK_REPORT_ACTIONS, + [ONYXKEYS.SESSION]: { + accountID: 12345, + }, + [`${ONYXKEYS.COLLECTION.TRANSACTION}1` as const]: { + transactionID: '1', + amount: 10, + modifiedAmount: 10, + reportID: '1', + }, + [`${ONYXKEYS.COLLECTION.TRANSACTION_VIOLATIONS}1` as const]: [ + { + type: CONST.VIOLATION_TYPES.VIOLATION, + name: CONST.VIOLATIONS.MISSING_CATEGORY, + }, + ], + }); + const reason = DebugUtils.getReasonForShowingRowInLHN(MOCK_TRANSACTION_REPORT); + expect(reason).toBe('debug.reasonVisibleInLHN.hasRBR'); + }); + it('returns correct reason when report has violations', async () => { + const MOCK_EXPENSE_REPORT: Report = { + reportID: '1', + chatReportID: '2', + parentReportID: '2', + parentReportActionID: '1', + ownerAccountID: 12345, + stateNum: CONST.REPORT.STATE_NUM.SUBMITTED, + type: CONST.REPORT.TYPE.EXPENSE, + }; + const MOCK_REPORTS: ReportCollectionDataSet = { + [`${ONYXKEYS.COLLECTION.REPORT}1` as const]: MOCK_EXPENSE_REPORT, + [`${ONYXKEYS.COLLECTION.REPORT}2` as const]: { + reportID: '2', + chatType: CONST.REPORT.CHAT_TYPE.POLICY_EXPENSE_CHAT, + }, + }; + const MOCK_REPORT_ACTIONS: ReportActionsCollectionDataSet = { + [`${ONYXKEYS.COLLECTION.REPORT_ACTIONS}2` as const]: { + // eslint-disable-next-line @typescript-eslint/naming-convention + '1': { + reportActionID: '1', + actionName: CONST.REPORT.ACTIONS.TYPE.IOU, + actorAccountID: 12345, + created: '2024-08-08 18:20:44.171', + message: { + type: CONST.IOU.REPORT_ACTION_TYPE.CREATE, + amount: 10, + currency: CONST.CURRENCY.USD, + IOUReportID: '1', + text: 'Vacation expense', + IOUTransactionID: '1', + }, + }, + }, + }; + await Onyx.multiSet({ + ...MOCK_REPORTS, + ...MOCK_REPORT_ACTIONS, + [ONYXKEYS.SESSION]: { + accountID: 12345, + }, + [`${ONYXKEYS.COLLECTION.TRANSACTION}1` as const]: { + transactionID: '1', + amount: 10, + modifiedAmount: 10, + reportID: '1', + }, + [`${ONYXKEYS.COLLECTION.TRANSACTION_VIOLATIONS}1` as const]: [ + { + type: CONST.VIOLATION_TYPES.VIOLATION, + name: CONST.VIOLATIONS.MISSING_CATEGORY, + }, + ], + }); + const reason = DebugUtils.getReasonForShowingRowInLHN(MOCK_EXPENSE_REPORT); + expect(reason).toBe('debug.reasonVisibleInLHN.hasRBR'); + }); + it('returns correct reason when report has errors', () => { + const reason = DebugUtils.getReasonForShowingRowInLHN({ + ...baseReport, + errors: { + error: 'Something went wrong', + }, + }); + expect(reason).toBe('debug.reasonVisibleInLHN.hasRBR'); + }); }); describe('getReasonAndReportActionForGBRInLHNRow', () => { beforeAll(() => { From 519561d26a63812436f17b28658a990d08efe6f9 Mon Sep 17 00:00:00 2001 From: nkdengineer Date: Thu, 10 Oct 2024 23:17:02 +0700 Subject: [PATCH 059/121] minor change --- src/ROUTES.ts | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/ROUTES.ts b/src/ROUTES.ts index e554b623bd5..de675fa8b3b 100644 --- a/src/ROUTES.ts +++ b/src/ROUTES.ts @@ -1177,7 +1177,7 @@ const ROUTES = { TRANSACTION_RECEIPT: { route: 'r/:reportID/transaction/:transactionID/receipt', getRoute: (reportID: string, transactionID: string, readonly = false, isFromReviewDuplicates = false) => - `r/${reportID}/transaction/${transactionID}/receipt${readonly ? '?readonly=true' : ''}${isFromReviewDuplicates ? '?isFromReviewDuplicates=true' : ''}` as const, + `r/${reportID}/transaction/${transactionID}/receipt${readonly ? '?readonly=true' : ''}${isFromReviewDuplicates ? '&isFromReviewDuplicates=true' : ''}` as const, }, TRANSACTION_DUPLICATE_REVIEW_PAGE: { From bf6fe38559f96e7c52fe5f86ff449ae70d4bbae9 Mon Sep 17 00:00:00 2001 From: Carlos Martins Date: Thu, 10 Oct 2024 12:21:21 -0600 Subject: [PATCH 060/121] move option to list --- .../Search/SearchMultipleSelectionPicker.tsx | 19 +++++++++++-------- .../SearchFiltersCategoryPage.tsx | 7 +++++-- 2 files changed, 16 insertions(+), 10 deletions(-) diff --git a/src/components/Search/SearchMultipleSelectionPicker.tsx b/src/components/Search/SearchMultipleSelectionPicker.tsx index a363a90591e..68fa0b72ed0 100644 --- a/src/components/Search/SearchMultipleSelectionPicker.tsx +++ b/src/components/Search/SearchMultipleSelectionPicker.tsx @@ -7,6 +7,7 @@ import useLocalize from '@hooks/useLocalize'; import localeCompare from '@libs/LocaleCompare'; import Navigation from '@libs/Navigation/Navigation'; import type {OptionData} from '@libs/ReportUtils'; +import CONST from '@src/CONST'; import ROUTES from '@src/ROUTES'; type SearchMultipleSelectionPickerItem = { @@ -28,6 +29,14 @@ function SearchMultipleSelectionPicker({items, initiallySelectedItems, pickerTit const [searchTerm, debouncedSearchTerm, setSearchTerm] = useDebouncedState(''); const [selectedItems, setSelectedItems] = useState(initiallySelectedItems ?? []); + const sortOptions = (a: SearchMultipleSelectionPickerItem, b: SearchMultipleSelectionPickerItem) => { + const filterNoValues = Object.values(CONST.SEARCH.FILTER_NO) as string[]; + if (filterNoValues.includes(a.value) || filterNoValues.includes(b.value)) { + return 1; + } + return localeCompare(a.name, b.name); + }; + useEffect(() => { setSelectedItems(initiallySelectedItems ?? []); }, [initiallySelectedItems]); @@ -35,7 +44,7 @@ function SearchMultipleSelectionPicker({items, initiallySelectedItems, pickerTit const {sections, noResultsFound} = useMemo(() => { const selectedItemsSection = selectedItems .filter((item) => item?.name.toLowerCase().includes(debouncedSearchTerm?.toLowerCase())) - .sort((a, b) => localeCompare(a.name, b.name)) + .sort((a, b) => sortOptions(a, b)) .map((item) => ({ text: item.name, keyForList: item.name, @@ -44,7 +53,7 @@ function SearchMultipleSelectionPicker({items, initiallySelectedItems, pickerTit })); const remainingItemsSection = items .filter((item) => selectedItems.some((selectedItem) => selectedItem.value === item.value) === false && item?.name.toLowerCase().includes(debouncedSearchTerm?.toLowerCase())) - .sort((a, b) => localeCompare(a.name, b.name)) + .sort((a, b) => sortOptions(a, b)) .map((item) => ({ text: item.name, keyForList: item.name, @@ -52,16 +61,10 @@ function SearchMultipleSelectionPicker({items, initiallySelectedItems, pickerTit value: item.value, })); const isEmpty = !selectedItemsSection.length && !remainingItemsSection.length; - console.log('over here', selectedItemsSection) return { sections: isEmpty ? [] : [ - { - title: undefined, - data: [{text: 'No category', keyForList: 'No category', value: 'no:category', isSelected: false}], - shouldShow: selectedItemsSection.length === 0, - }, { title: undefined, data: selectedItemsSection, diff --git a/src/pages/Search/SearchAdvancedFiltersPage/SearchFiltersCategoryPage.tsx b/src/pages/Search/SearchAdvancedFiltersPage/SearchFiltersCategoryPage.tsx index 0bf0f83d4df..2a013993444 100644 --- a/src/pages/Search/SearchAdvancedFiltersPage/SearchFiltersCategoryPage.tsx +++ b/src/pages/Search/SearchAdvancedFiltersPage/SearchFiltersCategoryPage.tsx @@ -24,12 +24,15 @@ function SearchFiltersCategoryPage() { const singlePolicyCategories = allPolicyIDCategories?.[`${ONYXKEYS.COLLECTION.POLICY_CATEGORIES}${policyID}`]; const categoryItems = useMemo(() => { + const items = [{name: 'No category', value: 'no:category'}]; if (!singlePolicyCategories) { const uniqueCategoryNames = new Set(); Object.values(allPolicyIDCategories ?? {}).map((policyCategories) => Object.values(policyCategories ?? {}).forEach((category) => uniqueCategoryNames.add(category.name))); - return Array.from(uniqueCategoryNames).map((categoryName) => ({name: categoryName, value: categoryName})); + items.push(...Array.from(uniqueCategoryNames).map((categoryName) => ({name: categoryName, value: categoryName}))); + } else { + items.push(...Object.values(singlePolicyCategories ?? {}).map((category) => ({name: category.name, value: category.name}))); } - return Object.values(singlePolicyCategories ?? {}).map((category) => ({name: category.name, value: category.name})); + return items; }, [allPolicyIDCategories, singlePolicyCategories]); const onSaveSelection = useCallback((values: string[]) => { From 4c1bffca8238b5c8861d699de4e7d2802a3a895f Mon Sep 17 00:00:00 2001 From: Carlos Martins Date: Thu, 10 Oct 2024 12:27:08 -0600 Subject: [PATCH 061/121] fix ts --- src/components/Search/SearchMultipleSelectionPicker.tsx | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/components/Search/SearchMultipleSelectionPicker.tsx b/src/components/Search/SearchMultipleSelectionPicker.tsx index 68fa0b72ed0..da2e2dfc2cc 100644 --- a/src/components/Search/SearchMultipleSelectionPicker.tsx +++ b/src/components/Search/SearchMultipleSelectionPicker.tsx @@ -12,7 +12,7 @@ import ROUTES from '@src/ROUTES'; type SearchMultipleSelectionPickerItem = { name: string; - value: string | string[]; + value: string; }; type SearchMultipleSelectionPickerProps = { From 7b08ff4f36584f2b88e1934c09d15ef248b004f2 Mon Sep 17 00:00:00 2001 From: Carlos Martins Date: Thu, 10 Oct 2024 12:38:52 -0600 Subject: [PATCH 062/121] use const, apply logic to tags --- .../SearchFiltersCategoryPage.tsx | 4 ++-- .../SearchFiltersTagPage.tsx | 17 +++++++++++++---- 2 files changed, 15 insertions(+), 6 deletions(-) diff --git a/src/pages/Search/SearchAdvancedFiltersPage/SearchFiltersCategoryPage.tsx b/src/pages/Search/SearchAdvancedFiltersPage/SearchFiltersCategoryPage.tsx index 2a013993444..b9b74605a74 100644 --- a/src/pages/Search/SearchAdvancedFiltersPage/SearchFiltersCategoryPage.tsx +++ b/src/pages/Search/SearchAdvancedFiltersPage/SearchFiltersCategoryPage.tsx @@ -24,7 +24,7 @@ function SearchFiltersCategoryPage() { const singlePolicyCategories = allPolicyIDCategories?.[`${ONYXKEYS.COLLECTION.POLICY_CATEGORIES}${policyID}`]; const categoryItems = useMemo(() => { - const items = [{name: 'No category', value: 'no:category'}]; + const items = [{name: translate('search.filters.noCategory'), value: CONST.SEARCH.FILTER_NO.CATEGORY as string}]; if (!singlePolicyCategories) { const uniqueCategoryNames = new Set(); Object.values(allPolicyIDCategories ?? {}).map((policyCategories) => Object.values(policyCategories ?? {}).forEach((category) => uniqueCategoryNames.add(category.name))); @@ -33,7 +33,7 @@ function SearchFiltersCategoryPage() { items.push(...Object.values(singlePolicyCategories ?? {}).map((category) => ({name: category.name, value: category.name}))); } return items; - }, [allPolicyIDCategories, singlePolicyCategories]); + }, [allPolicyIDCategories, singlePolicyCategories, translate]); const onSaveSelection = useCallback((values: string[]) => { if (values.at(0) === CONST.SEARCH.FILTER_NO.CATEGORY) { diff --git a/src/pages/Search/SearchAdvancedFiltersPage/SearchFiltersTagPage.tsx b/src/pages/Search/SearchAdvancedFiltersPage/SearchFiltersTagPage.tsx index d0b0b2e234f..971b233e87e 100644 --- a/src/pages/Search/SearchAdvancedFiltersPage/SearchFiltersTagPage.tsx +++ b/src/pages/Search/SearchAdvancedFiltersPage/SearchFiltersTagPage.tsx @@ -9,6 +9,7 @@ import useThemeStyles from '@hooks/useThemeStyles'; import Navigation from '@libs/Navigation/Navigation'; import {getTagNamesFromTagsLists} from '@libs/PolicyUtils'; import * as SearchActions from '@userActions/Search'; +import CONST from '@src/CONST'; import ONYXKEYS from '@src/ONYXKEYS'; import ROUTES from '@src/ROUTES'; import type {PolicyTagLists} from '@src/types/onyx'; @@ -24,6 +25,7 @@ function SearchFiltersTagPage() { const singlePolicyTagsList: PolicyTagLists | undefined = allPoliciesTagsLists?.[`${ONYXKEYS.COLLECTION.POLICY_TAGS}${policyID}`]; const tagItems = useMemo(() => { + const items = [{name: translate('search.filters.noTag'), value: CONST.SEARCH.FILTER_NO.TAG as string}]; if (!singlePolicyTagsList) { const uniqueTagNames = new Set(); const tagListsUnpacked = Object.values(allPoliciesTagsLists ?? {}).filter((item) => !!item) as PolicyTagLists[]; @@ -33,12 +35,19 @@ function SearchFiltersTagPage() { }) .flat() .forEach((tag) => uniqueTagNames.add(tag)); - return Array.from(uniqueTagNames).map((tagName) => ({name: tagName, value: tagName})); + items.push(...Array.from(uniqueTagNames).map((tagName) => ({name: tagName, value: tagName}))); + } else { + items.push(...getTagNamesFromTagsLists(singlePolicyTagsList).map((name) => ({name, value: name}))); } - return getTagNamesFromTagsLists(singlePolicyTagsList).map((name) => ({name, value: name})); - }, [allPoliciesTagsLists, singlePolicyTagsList]); + return items; + }, [allPoliciesTagsLists, singlePolicyTagsList, translate]); - const updateTagFilter = useCallback((values: string[]) => SearchActions.updateAdvancedFilters({tag: values}), []); + const updateTagFilter = useCallback((values: string[]) => { + if (values.at(0) === CONST.SEARCH.FILTER_NO.TAG) { + SearchActions.updateAdvancedFilters({no: [CONST.SEARCH.SYNTAX_FILTER_KEYS.TAG]}); + } + SearchActions.updateAdvancedFilters({tag: values}); + }, []); return ( Date: Thu, 10 Oct 2024 12:46:39 -0600 Subject: [PATCH 063/121] fix route, types --- src/ROUTES.ts | 4 +--- src/pages/Search/AdvancedSearchFilters.tsx | 7 +++++-- 2 files changed, 6 insertions(+), 5 deletions(-) diff --git a/src/ROUTES.ts b/src/ROUTES.ts index 795ca3d96ac..371fb2b9652 100644 --- a/src/ROUTES.ts +++ b/src/ROUTES.ts @@ -57,9 +57,7 @@ const ROUTES = { SEARCH_ADVANCED_FILTERS_FROM: 'search/filters/from', SEARCH_ADVANCED_FILTERS_TO: 'search/filters/to', SEARCH_ADVANCED_FILTERS_IN: 'search/filters/in', - - // The 'no' filter is a special case because it's a modifier built into other filters, e.g. categories and tags so it doesn't have it's own page - SEARCH_ADVANCED_FILTERS_NO: 'search/filters/', + SEARCH_ADVANCED_FILTERS_NO: 'search/filters/no', SEARCH_REPORT: { route: 'search/view/:reportID/:reportActionID?', getRoute: ({reportID, reportActionID, backTo}: {reportID: string; reportActionID?: string; backTo?: string}) => { diff --git a/src/pages/Search/AdvancedSearchFilters.tsx b/src/pages/Search/AdvancedSearchFilters.tsx index b1e3e389b79..b7a3fe3ab66 100644 --- a/src/pages/Search/AdvancedSearchFilters.tsx +++ b/src/pages/Search/AdvancedSearchFilters.tsx @@ -105,9 +105,8 @@ const baseFilterConfig = { description: 'common.in' as const, route: ROUTES.SEARCH_ADVANCED_FILTERS_IN, }, - - // The no filter is just a modifier for other filters, e.g. categories and tags so it does't have its own page no: { + getTitle: getFilterInDisplayTitle, description: 'common.no' as const, route: ROUTES.SEARCH_ADVANCED_FILTERS_NO, }, @@ -145,6 +144,10 @@ function getFilterParticipantDisplayTitle(accountIDs: string[], personalDetails: } function getFilterDisplayTitle(filters: Partial, fieldName: AdvancedFiltersKeys, translate: LocaleContextProps['translate']) { + if (fieldName === CONST.SEARCH.SYNTAX_FILTER_KEYS.NO) { + return ''; + } + if (fieldName === CONST.SEARCH.SYNTAX_FILTER_KEYS.DATE) { // the value of date filter is a combination of dateBefore + dateAfter values const {dateAfter, dateBefore} = filters; From 29e01cd088958d66b6c9acbf787d928dcd4886aa Mon Sep 17 00:00:00 2001 From: Carlos Martins Date: Thu, 10 Oct 2024 14:40:26 -0600 Subject: [PATCH 064/121] revert changes, add entry to list --- src/CONST.ts | 6 +- src/ROUTES.ts | 1 - .../Search/SearchMultipleSelectionPicker.tsx | 6 +- src/libs/SearchParser/searchParser.js | 107 ++++++++---------- src/libs/SearchParser/searchParser.peggy | 1 - src/libs/SearchUtils.ts | 1 - src/pages/Search/AdvancedSearchFilters.tsx | 18 +-- .../SearchFiltersCategoryPage.tsx | 10 +- .../SearchFiltersTagPage.tsx | 9 +- src/types/form/SearchAdvancedFiltersForm.ts | 2 - 10 files changed, 57 insertions(+), 104 deletions(-) diff --git a/src/CONST.ts b/src/CONST.ts index 4008067b55f..d058fda641b 100755 --- a/src/CONST.ts +++ b/src/CONST.ts @@ -5609,12 +5609,8 @@ const CONST = { REPORT_ID: 'reportID', KEYWORD: 'keyword', IN: 'in', - NO: 'no', - }, - FILTER_NO: { - CATEGORY: 'no:category', - TAG: 'no:tag', }, + VALUE_NONE: 'none', }, REFERRER: { diff --git a/src/ROUTES.ts b/src/ROUTES.ts index 371fb2b9652..47fabe3c421 100644 --- a/src/ROUTES.ts +++ b/src/ROUTES.ts @@ -57,7 +57,6 @@ const ROUTES = { SEARCH_ADVANCED_FILTERS_FROM: 'search/filters/from', SEARCH_ADVANCED_FILTERS_TO: 'search/filters/to', SEARCH_ADVANCED_FILTERS_IN: 'search/filters/in', - SEARCH_ADVANCED_FILTERS_NO: 'search/filters/no', SEARCH_REPORT: { route: 'search/view/:reportID/:reportActionID?', getRoute: ({reportID, reportActionID, backTo}: {reportID: string; reportActionID?: string; backTo?: string}) => { diff --git a/src/components/Search/SearchMultipleSelectionPicker.tsx b/src/components/Search/SearchMultipleSelectionPicker.tsx index da2e2dfc2cc..8e733da24b0 100644 --- a/src/components/Search/SearchMultipleSelectionPicker.tsx +++ b/src/components/Search/SearchMultipleSelectionPicker.tsx @@ -12,7 +12,7 @@ import ROUTES from '@src/ROUTES'; type SearchMultipleSelectionPickerItem = { name: string; - value: string; + value: string | string[]; }; type SearchMultipleSelectionPickerProps = { @@ -30,8 +30,8 @@ function SearchMultipleSelectionPicker({items, initiallySelectedItems, pickerTit const [selectedItems, setSelectedItems] = useState(initiallySelectedItems ?? []); const sortOptions = (a: SearchMultipleSelectionPickerItem, b: SearchMultipleSelectionPickerItem) => { - const filterNoValues = Object.values(CONST.SEARCH.FILTER_NO) as string[]; - if (filterNoValues.includes(a.value) || filterNoValues.includes(b.value)) { + // Always show `No category` and `No tag` as the first option + if (a.value === CONST.SEARCH.VALUE_NONE || b.value === CONST.SEARCH.VALUE_NONE) { return 1; } return localeCompare(a.name, b.name); diff --git a/src/libs/SearchParser/searchParser.js b/src/libs/SearchParser/searchParser.js index e8f0dc388eb..713996d0cf0 100644 --- a/src/libs/SearchParser/searchParser.js +++ b/src/libs/SearchParser/searchParser.js @@ -200,13 +200,12 @@ function peg$parse(input, options) { var peg$c17 = "cardID"; var peg$c18 = "from"; var peg$c19 = "expenseType"; - var peg$c20 = "no"; - var peg$c21 = "type"; - var peg$c22 = "status"; - var peg$c23 = "sortBy"; - var peg$c24 = "sortOrder"; - var peg$c25 = "policyID"; - var peg$c26 = "\""; + var peg$c20 = "type"; + var peg$c21 = "status"; + var peg$c22 = "sortBy"; + var peg$c23 = "sortOrder"; + var peg$c24 = "policyID"; + var peg$c25 = "\""; var peg$r0 = /^[:=]/; var peg$r1 = /^[^"\r\n]/; @@ -236,20 +235,19 @@ function peg$parse(input, options) { var peg$e20 = peg$literalExpectation("cardID", false); var peg$e21 = peg$literalExpectation("from", false); var peg$e22 = peg$literalExpectation("expenseType", false); - var peg$e23 = peg$literalExpectation("no", false); - var peg$e24 = peg$otherExpectation("default key"); - var peg$e25 = peg$literalExpectation("type", false); - var peg$e26 = peg$literalExpectation("status", false); - var peg$e27 = peg$literalExpectation("sortBy", false); - var peg$e28 = peg$literalExpectation("sortOrder", false); - var peg$e29 = peg$literalExpectation("policyID", false); - var peg$e30 = peg$otherExpectation("quote"); - var peg$e31 = peg$literalExpectation("\"", false); - var peg$e32 = peg$classExpectation(["\"", "\r", "\n"], true, false); - var peg$e33 = peg$otherExpectation("word"); - var peg$e34 = peg$classExpectation([["A", "Z"], ["a", "z"], ["0", "9"], "_", "@", ".", "/", "#", "&", "+", "-", "\\", "'", ",", ";", "%"], false, false); - var peg$e35 = peg$otherExpectation("whitespace"); - var peg$e36 = peg$classExpectation([" ", "\t", "\r", "\n"], false, false); + var peg$e23 = peg$otherExpectation("default key"); + var peg$e24 = peg$literalExpectation("type", false); + var peg$e25 = peg$literalExpectation("status", false); + var peg$e26 = peg$literalExpectation("sortBy", false); + var peg$e27 = peg$literalExpectation("sortOrder", false); + var peg$e28 = peg$literalExpectation("policyID", false); + var peg$e29 = peg$otherExpectation("quote"); + var peg$e30 = peg$literalExpectation("\"", false); + var peg$e31 = peg$classExpectation(["\"", "\r", "\n"], true, false); + var peg$e32 = peg$otherExpectation("word"); + var peg$e33 = peg$classExpectation([["A", "Z"], ["a", "z"], ["0", "9"], "_", "@", ".", "/", "#", "&", "+", "-", "\\", "'", ",", ";", "%"], false, false); + var peg$e34 = peg$otherExpectation("whitespace"); + var peg$e35 = peg$classExpectation([" ", "\t", "\r", "\n"], false, false); var peg$f0 = function(filters) { return applyDefaults(filters); }; var peg$f1 = function(head, tail) { @@ -858,15 +856,6 @@ function peg$parse(input, options) { s1 = peg$FAILED; if (peg$silentFails === 0) { peg$fail(peg$e14); } } - if (s1 === peg$FAILED) { - if (input.substr(peg$currPos, 2) === peg$c20) { - s1 = peg$c20; - peg$currPos += 2; - } else { - s1 = peg$FAILED; - if (peg$silentFails === 0) { peg$fail(peg$e23); } - } - } } } } @@ -902,44 +891,44 @@ function peg$parse(input, options) { peg$silentFails++; s0 = peg$currPos; - if (input.substr(peg$currPos, 4) === peg$c21) { - s1 = peg$c21; + if (input.substr(peg$currPos, 4) === peg$c20) { + s1 = peg$c20; peg$currPos += 4; } else { s1 = peg$FAILED; - if (peg$silentFails === 0) { peg$fail(peg$e25); } + if (peg$silentFails === 0) { peg$fail(peg$e24); } } if (s1 === peg$FAILED) { - if (input.substr(peg$currPos, 6) === peg$c22) { - s1 = peg$c22; + if (input.substr(peg$currPos, 6) === peg$c21) { + s1 = peg$c21; peg$currPos += 6; } else { s1 = peg$FAILED; - if (peg$silentFails === 0) { peg$fail(peg$e26); } + if (peg$silentFails === 0) { peg$fail(peg$e25); } } if (s1 === peg$FAILED) { - if (input.substr(peg$currPos, 6) === peg$c23) { - s1 = peg$c23; + if (input.substr(peg$currPos, 6) === peg$c22) { + s1 = peg$c22; peg$currPos += 6; } else { s1 = peg$FAILED; - if (peg$silentFails === 0) { peg$fail(peg$e27); } + if (peg$silentFails === 0) { peg$fail(peg$e26); } } if (s1 === peg$FAILED) { - if (input.substr(peg$currPos, 9) === peg$c24) { - s1 = peg$c24; + if (input.substr(peg$currPos, 9) === peg$c23) { + s1 = peg$c23; peg$currPos += 9; } else { s1 = peg$FAILED; - if (peg$silentFails === 0) { peg$fail(peg$e28); } + if (peg$silentFails === 0) { peg$fail(peg$e27); } } if (s1 === peg$FAILED) { - if (input.substr(peg$currPos, 8) === peg$c25) { - s1 = peg$c25; + if (input.substr(peg$currPos, 8) === peg$c24) { + s1 = peg$c24; peg$currPos += 8; } else { s1 = peg$FAILED; - if (peg$silentFails === 0) { peg$fail(peg$e29); } + if (peg$silentFails === 0) { peg$fail(peg$e28); } } } } @@ -954,7 +943,7 @@ function peg$parse(input, options) { peg$silentFails--; if (s0 === peg$FAILED) { s1 = peg$FAILED; - if (peg$silentFails === 0) { peg$fail(peg$e24); } + if (peg$silentFails === 0) { peg$fail(peg$e23); } } return s0; @@ -995,11 +984,11 @@ function peg$parse(input, options) { peg$silentFails++; s0 = peg$currPos; if (input.charCodeAt(peg$currPos) === 34) { - s1 = peg$c26; + s1 = peg$c25; peg$currPos++; } else { s1 = peg$FAILED; - if (peg$silentFails === 0) { peg$fail(peg$e31); } + if (peg$silentFails === 0) { peg$fail(peg$e30); } } if (s1 !== peg$FAILED) { s2 = []; @@ -1008,7 +997,7 @@ function peg$parse(input, options) { peg$currPos++; } else { s3 = peg$FAILED; - if (peg$silentFails === 0) { peg$fail(peg$e32); } + if (peg$silentFails === 0) { peg$fail(peg$e31); } } while (s3 !== peg$FAILED) { s2.push(s3); @@ -1017,15 +1006,15 @@ function peg$parse(input, options) { peg$currPos++; } else { s3 = peg$FAILED; - if (peg$silentFails === 0) { peg$fail(peg$e32); } + if (peg$silentFails === 0) { peg$fail(peg$e31); } } } if (input.charCodeAt(peg$currPos) === 34) { - s3 = peg$c26; + s3 = peg$c25; peg$currPos++; } else { s3 = peg$FAILED; - if (peg$silentFails === 0) { peg$fail(peg$e31); } + if (peg$silentFails === 0) { peg$fail(peg$e30); } } if (s3 !== peg$FAILED) { peg$savedPos = s0; @@ -1041,7 +1030,7 @@ function peg$parse(input, options) { peg$silentFails--; if (s0 === peg$FAILED) { s1 = peg$FAILED; - if (peg$silentFails === 0) { peg$fail(peg$e30); } + if (peg$silentFails === 0) { peg$fail(peg$e29); } } return s0; @@ -1058,7 +1047,7 @@ function peg$parse(input, options) { peg$currPos++; } else { s2 = peg$FAILED; - if (peg$silentFails === 0) { peg$fail(peg$e34); } + if (peg$silentFails === 0) { peg$fail(peg$e33); } } if (s2 !== peg$FAILED) { while (s2 !== peg$FAILED) { @@ -1068,7 +1057,7 @@ function peg$parse(input, options) { peg$currPos++; } else { s2 = peg$FAILED; - if (peg$silentFails === 0) { peg$fail(peg$e34); } + if (peg$silentFails === 0) { peg$fail(peg$e33); } } } } else { @@ -1082,7 +1071,7 @@ function peg$parse(input, options) { peg$silentFails--; if (s0 === peg$FAILED) { s1 = peg$FAILED; - if (peg$silentFails === 0) { peg$fail(peg$e33); } + if (peg$silentFails === 0) { peg$fail(peg$e32); } } return s0; @@ -1110,7 +1099,7 @@ function peg$parse(input, options) { peg$currPos++; } else { s1 = peg$FAILED; - if (peg$silentFails === 0) { peg$fail(peg$e36); } + if (peg$silentFails === 0) { peg$fail(peg$e35); } } while (s1 !== peg$FAILED) { s0.push(s1); @@ -1119,12 +1108,12 @@ function peg$parse(input, options) { peg$currPos++; } else { s1 = peg$FAILED; - if (peg$silentFails === 0) { peg$fail(peg$e36); } + if (peg$silentFails === 0) { peg$fail(peg$e35); } } } peg$silentFails--; s1 = peg$FAILED; - if (peg$silentFails === 0) { peg$fail(peg$e35); } + if (peg$silentFails === 0) { peg$fail(peg$e34); } return s0; } diff --git a/src/libs/SearchParser/searchParser.peggy b/src/libs/SearchParser/searchParser.peggy index b2c6a0940bd..32d44f24d0d 100644 --- a/src/libs/SearchParser/searchParser.peggy +++ b/src/libs/SearchParser/searchParser.peggy @@ -120,7 +120,6 @@ key "key" / "from" / "expenseType" / "in" - / "no" ) defaultKey "default key" diff --git a/src/libs/SearchUtils.ts b/src/libs/SearchUtils.ts index f372ae71c81..47cee2c7c2b 100644 --- a/src/libs/SearchUtils.ts +++ b/src/libs/SearchUtils.ts @@ -626,7 +626,6 @@ function buildQueryStringFromFilterFormValues(filterValues: Partial 0 diff --git a/src/pages/Search/AdvancedSearchFilters.tsx b/src/pages/Search/AdvancedSearchFilters.tsx index b7a3fe3ab66..7009764629b 100644 --- a/src/pages/Search/AdvancedSearchFilters.tsx +++ b/src/pages/Search/AdvancedSearchFilters.tsx @@ -105,11 +105,6 @@ const baseFilterConfig = { description: 'common.in' as const, route: ROUTES.SEARCH_ADVANCED_FILTERS_IN, }, - no: { - getTitle: getFilterInDisplayTitle, - description: 'common.no' as const, - route: ROUTES.SEARCH_ADVANCED_FILTERS_NO, - }, }; const typeFiltersKeys: Record>> = { @@ -144,10 +139,6 @@ function getFilterParticipantDisplayTitle(accountIDs: string[], personalDetails: } function getFilterDisplayTitle(filters: Partial, fieldName: AdvancedFiltersKeys, translate: LocaleContextProps['translate']) { - if (fieldName === CONST.SEARCH.SYNTAX_FILTER_KEYS.NO) { - return ''; - } - if (fieldName === CONST.SEARCH.SYNTAX_FILTER_KEYS.DATE) { // the value of date filter is a combination of dateBefore + dateAfter values const {dateAfter, dateBefore} = filters; @@ -183,14 +174,6 @@ function getFilterDisplayTitle(filters: Partial, fiel return; } - if (fieldName === CONST.SEARCH.SYNTAX_FILTER_KEYS.CATEGORY && filters[CONST.SEARCH.SYNTAX_FILTER_KEYS.NO]) { - return translate('search.filters.noCategory'); - } - - if (fieldName === CONST.SEARCH.SYNTAX_FILTER_KEYS.TAG && filters[CONST.SEARCH.SYNTAX_FILTER_KEYS.NO]) { - return translate('search.filters.noTag'); - } - if ( (fieldName === CONST.SEARCH.SYNTAX_FILTER_KEYS.CATEGORY || fieldName === CONST.SEARCH.SYNTAX_FILTER_KEYS.CURRENCY || fieldName === CONST.SEARCH.SYNTAX_FILTER_KEYS.TAG) && filters[fieldName] @@ -252,6 +235,7 @@ function AdvancedSearchFilters() { const queryString = useMemo(() => SearchUtils.buildQueryStringFromFilterFormValues(searchAdvancedFilters), [searchAdvancedFilters]); const queryJSON = useMemo(() => SearchUtils.buildSearchQueryJSON(queryString || SearchUtils.buildCannedSearchQuery()), [queryString]); + const applyFiltersAndNavigate = () => { SearchActions.clearAllFilters(); Navigation.dismissModal(); diff --git a/src/pages/Search/SearchAdvancedFiltersPage/SearchFiltersCategoryPage.tsx b/src/pages/Search/SearchAdvancedFiltersPage/SearchFiltersCategoryPage.tsx index b9b74605a74..58cdfb4b267 100644 --- a/src/pages/Search/SearchAdvancedFiltersPage/SearchFiltersCategoryPage.tsx +++ b/src/pages/Search/SearchAdvancedFiltersPage/SearchFiltersCategoryPage.tsx @@ -24,7 +24,7 @@ function SearchFiltersCategoryPage() { const singlePolicyCategories = allPolicyIDCategories?.[`${ONYXKEYS.COLLECTION.POLICY_CATEGORIES}${policyID}`]; const categoryItems = useMemo(() => { - const items = [{name: translate('search.filters.noCategory'), value: CONST.SEARCH.FILTER_NO.CATEGORY as string}]; + const items = [{name: translate('search.filters.noCategory'), value: CONST.SEARCH.VALUE_NONE as string}]; if (!singlePolicyCategories) { const uniqueCategoryNames = new Set(); Object.values(allPolicyIDCategories ?? {}).map((policyCategories) => Object.values(policyCategories ?? {}).forEach((category) => uniqueCategoryNames.add(category.name))); @@ -35,13 +35,7 @@ function SearchFiltersCategoryPage() { return items; }, [allPolicyIDCategories, singlePolicyCategories, translate]); - const onSaveSelection = useCallback((values: string[]) => { - if (values.at(0) === CONST.SEARCH.FILTER_NO.CATEGORY) { - SearchActions.updateAdvancedFilters({no: [CONST.SEARCH.SYNTAX_FILTER_KEYS.CATEGORY]}); - return; - } - SearchActions.updateAdvancedFilters({category: values}); - }, []); + const onSaveSelection = useCallback((values: string[]) => SearchActions.updateAdvancedFilters({category: values}), []); const safePaddingBottomStyle = useSafePaddingBottomStyle(); return ( diff --git a/src/pages/Search/SearchAdvancedFiltersPage/SearchFiltersTagPage.tsx b/src/pages/Search/SearchAdvancedFiltersPage/SearchFiltersTagPage.tsx index 971b233e87e..f43ed0586f7 100644 --- a/src/pages/Search/SearchAdvancedFiltersPage/SearchFiltersTagPage.tsx +++ b/src/pages/Search/SearchAdvancedFiltersPage/SearchFiltersTagPage.tsx @@ -25,7 +25,7 @@ function SearchFiltersTagPage() { const singlePolicyTagsList: PolicyTagLists | undefined = allPoliciesTagsLists?.[`${ONYXKEYS.COLLECTION.POLICY_TAGS}${policyID}`]; const tagItems = useMemo(() => { - const items = [{name: translate('search.filters.noTag'), value: CONST.SEARCH.FILTER_NO.TAG as string}]; + const items = [{name: translate('search.filters.noTag'), value: CONST.SEARCH.VALUE_NONE as string}]; if (!singlePolicyTagsList) { const uniqueTagNames = new Set(); const tagListsUnpacked = Object.values(allPoliciesTagsLists ?? {}).filter((item) => !!item) as PolicyTagLists[]; @@ -42,12 +42,7 @@ function SearchFiltersTagPage() { return items; }, [allPoliciesTagsLists, singlePolicyTagsList, translate]); - const updateTagFilter = useCallback((values: string[]) => { - if (values.at(0) === CONST.SEARCH.FILTER_NO.TAG) { - SearchActions.updateAdvancedFilters({no: [CONST.SEARCH.SYNTAX_FILTER_KEYS.TAG]}); - } - SearchActions.updateAdvancedFilters({tag: values}); - }, []); + const updateTagFilter = useCallback((values: string[]) => SearchActions.updateAdvancedFilters({tag: values}), []); return ( ; @@ -50,7 +49,6 @@ type SearchAdvancedFiltersForm = Form< [FILTER_KEYS.FROM]: string[]; [FILTER_KEYS.TO]: string[]; [FILTER_KEYS.IN]: string[]; - [FILTER_KEYS.NO]: string[]; } >; From 8b165dbd2239dc56dfed956eae62d677299b1b6f Mon Sep 17 00:00:00 2001 From: Carlos Martins Date: Thu, 10 Oct 2024 16:05:29 -0600 Subject: [PATCH 065/121] update sort logic --- src/languages/en.ts | 4 +-- src/languages/es.ts | 4 +-- src/pages/Search/AdvancedSearchFilters.tsx | 30 ++++++++++++++++--- .../SearchFiltersCategoryPage.tsx | 2 +- .../SearchFiltersTagPage.tsx | 2 +- 5 files changed, 32 insertions(+), 10 deletions(-) diff --git a/src/languages/en.ts b/src/languages/en.ts index f6180c9be4d..7fcfc2ce855 100755 --- a/src/languages/en.ts +++ b/src/languages/en.ts @@ -4182,9 +4182,9 @@ const translations = { }, current: 'Current', past: 'Past', - noCategory: 'No category', - noTag: 'No tag', }, + noCategory: 'No category', + noTag: 'No tag', expenseType: 'Expense type', recentSearches: 'Recent searches', recentChats: 'Recent chats', diff --git a/src/languages/es.ts b/src/languages/es.ts index 24cf017018c..cd3044cb611 100644 --- a/src/languages/es.ts +++ b/src/languages/es.ts @@ -4227,9 +4227,9 @@ const translations = { }, current: 'Actual', past: 'Anterior', - noCategory: 'Sin categoría', - noTag: 'Sin etiqueta', }, + noCategory: 'Sin categoría', + noTag: 'Sin etiqueta', expenseType: 'Tipo de gasto', recentSearches: 'Búsquedas recientes', recentChats: 'Chats recientes', diff --git a/src/pages/Search/AdvancedSearchFilters.tsx b/src/pages/Search/AdvancedSearchFilters.tsx index 7009764629b..037c7d57de0 100644 --- a/src/pages/Search/AdvancedSearchFilters.tsx +++ b/src/pages/Search/AdvancedSearchFilters.tsx @@ -138,6 +138,15 @@ function getFilterParticipantDisplayTitle(accountIDs: string[], personalDetails: .join(', '); } +function sortOptions(a: string, b: string) { + console.log('over here', {a,b}) + // Always show `No category` and `No tag` as the first option + if (a === CONST.SEARCH.VALUE_NONE || b === CONST.SEARCH.VALUE_NONE) { + return 1; + } + return localeCompare(a, b); +} + function getFilterDisplayTitle(filters: Partial, fieldName: AdvancedFiltersKeys, translate: LocaleContextProps['translate']) { if (fieldName === CONST.SEARCH.SYNTAX_FILTER_KEYS.DATE) { // the value of date filter is a combination of dateBefore + dateAfter values @@ -174,14 +183,27 @@ function getFilterDisplayTitle(filters: Partial, fiel return; } - if ( - (fieldName === CONST.SEARCH.SYNTAX_FILTER_KEYS.CATEGORY || fieldName === CONST.SEARCH.SYNTAX_FILTER_KEYS.CURRENCY || fieldName === CONST.SEARCH.SYNTAX_FILTER_KEYS.TAG) && - filters[fieldName] - ) { + if (fieldName === CONST.SEARCH.SYNTAX_FILTER_KEYS.CURRENCY && filters[fieldName]) { const filterArray = filters[fieldName] ?? []; return filterArray.sort(localeCompare).join(', '); } + if (fieldName === CONST.SEARCH.SYNTAX_FILTER_KEYS.CATEGORY && filters[fieldName]) { + const filterArray = filters[fieldName] ?? []; + return filterArray + .sort(sortOptions) + .map((value) => (value === CONST.SEARCH.VALUE_NONE ? translate('search.noCategory') : value)) + .join(', '); + } + + if (fieldName === CONST.SEARCH.SYNTAX_FILTER_KEYS.TAG && filters[fieldName]) { + const filterArray = filters[fieldName] ?? []; + return filterArray + .sort(sortOptions) + .map((value) => (value === CONST.SEARCH.VALUE_NONE ? translate('search.noTag') : value)) + .join(', '); + } + if (fieldName === CONST.SEARCH.SYNTAX_FILTER_KEYS.DESCRIPTION) { return filters[fieldName]; } diff --git a/src/pages/Search/SearchAdvancedFiltersPage/SearchFiltersCategoryPage.tsx b/src/pages/Search/SearchAdvancedFiltersPage/SearchFiltersCategoryPage.tsx index 58cdfb4b267..d76dd7856c0 100644 --- a/src/pages/Search/SearchAdvancedFiltersPage/SearchFiltersCategoryPage.tsx +++ b/src/pages/Search/SearchAdvancedFiltersPage/SearchFiltersCategoryPage.tsx @@ -24,7 +24,7 @@ function SearchFiltersCategoryPage() { const singlePolicyCategories = allPolicyIDCategories?.[`${ONYXKEYS.COLLECTION.POLICY_CATEGORIES}${policyID}`]; const categoryItems = useMemo(() => { - const items = [{name: translate('search.filters.noCategory'), value: CONST.SEARCH.VALUE_NONE as string}]; + const items = [{name: translate('search.noCategory'), value: CONST.SEARCH.VALUE_NONE as string}]; if (!singlePolicyCategories) { const uniqueCategoryNames = new Set(); Object.values(allPolicyIDCategories ?? {}).map((policyCategories) => Object.values(policyCategories ?? {}).forEach((category) => uniqueCategoryNames.add(category.name))); diff --git a/src/pages/Search/SearchAdvancedFiltersPage/SearchFiltersTagPage.tsx b/src/pages/Search/SearchAdvancedFiltersPage/SearchFiltersTagPage.tsx index f43ed0586f7..aadb69afdb3 100644 --- a/src/pages/Search/SearchAdvancedFiltersPage/SearchFiltersTagPage.tsx +++ b/src/pages/Search/SearchAdvancedFiltersPage/SearchFiltersTagPage.tsx @@ -25,7 +25,7 @@ function SearchFiltersTagPage() { const singlePolicyTagsList: PolicyTagLists | undefined = allPoliciesTagsLists?.[`${ONYXKEYS.COLLECTION.POLICY_TAGS}${policyID}`]; const tagItems = useMemo(() => { - const items = [{name: translate('search.filters.noTag'), value: CONST.SEARCH.VALUE_NONE as string}]; + const items = [{name: translate('search.noTag'), value: CONST.SEARCH.VALUE_NONE as string}]; if (!singlePolicyTagsList) { const uniqueTagNames = new Set(); const tagListsUnpacked = Object.values(allPoliciesTagsLists ?? {}).filter((item) => !!item) as PolicyTagLists[]; From 223ad3de29935a17309f6250b4e0a14e660c8bf0 Mon Sep 17 00:00:00 2001 From: Carlos Martins Date: Thu, 10 Oct 2024 16:09:03 -0600 Subject: [PATCH 066/121] update display value in filter list --- .../SearchFiltersCategoryPage.tsx | 7 ++++++- .../SearchAdvancedFiltersPage/SearchFiltersTagPage.tsx | 7 ++++++- 2 files changed, 12 insertions(+), 2 deletions(-) diff --git a/src/pages/Search/SearchAdvancedFiltersPage/SearchFiltersCategoryPage.tsx b/src/pages/Search/SearchAdvancedFiltersPage/SearchFiltersCategoryPage.tsx index d76dd7856c0..87d300487d9 100644 --- a/src/pages/Search/SearchAdvancedFiltersPage/SearchFiltersCategoryPage.tsx +++ b/src/pages/Search/SearchAdvancedFiltersPage/SearchFiltersCategoryPage.tsx @@ -18,7 +18,12 @@ function SearchFiltersCategoryPage() { const {translate} = useLocalize(); const [searchAdvancedFiltersForm] = useOnyx(ONYXKEYS.FORMS.SEARCH_ADVANCED_FILTERS_FORM); - const selectedCategoriesItems = searchAdvancedFiltersForm?.category?.map((category) => ({name: category, value: category})); + const selectedCategoriesItems = searchAdvancedFiltersForm?.category?.map((category) => { + if (category === CONST.SEARCH.VALUE_NONE) { + return {name: translate('search.noCategory'), value: category}; + } + return {name: category, value: category}; + }); const policyID = searchAdvancedFiltersForm?.policyID ?? '-1'; const [allPolicyIDCategories] = useOnyx(ONYXKEYS.COLLECTION.POLICY_CATEGORIES); const singlePolicyCategories = allPolicyIDCategories?.[`${ONYXKEYS.COLLECTION.POLICY_CATEGORIES}${policyID}`]; diff --git a/src/pages/Search/SearchAdvancedFiltersPage/SearchFiltersTagPage.tsx b/src/pages/Search/SearchAdvancedFiltersPage/SearchFiltersTagPage.tsx index aadb69afdb3..9c808030b3a 100644 --- a/src/pages/Search/SearchAdvancedFiltersPage/SearchFiltersTagPage.tsx +++ b/src/pages/Search/SearchAdvancedFiltersPage/SearchFiltersTagPage.tsx @@ -19,7 +19,12 @@ function SearchFiltersTagPage() { const {translate} = useLocalize(); const [searchAdvancedFiltersForm] = useOnyx(ONYXKEYS.FORMS.SEARCH_ADVANCED_FILTERS_FORM); - const selectedTagsItems = searchAdvancedFiltersForm?.tag?.map((tag) => ({name: tag, value: tag})); + const selectedTagsItems = searchAdvancedFiltersForm?.tag?.map((tag) => { + if (tag === CONST.SEARCH.VALUE_NONE) { + return {name: translate('search.noTag'), value: tag}; + } + return {name: tag, value: tag}; + }); const policyID = searchAdvancedFiltersForm?.policyID ?? '-1'; const [allPoliciesTagsLists] = useOnyx(ONYXKEYS.COLLECTION.POLICY_TAGS); const singlePolicyTagsList: PolicyTagLists | undefined = allPoliciesTagsLists?.[`${ONYXKEYS.COLLECTION.POLICY_TAGS}${policyID}`]; From 84882b8a4f30adf9f9ade6b5e62449ee0404aa82 Mon Sep 17 00:00:00 2001 From: Carlos Martins Date: Thu, 10 Oct 2024 16:16:14 -0600 Subject: [PATCH 067/121] update sorting function --- .../Search/SearchMultipleSelectionPicker.tsx | 11 +++++++---- src/pages/Search/AdvancedSearchFilters.tsx | 14 ++++++++------ 2 files changed, 15 insertions(+), 10 deletions(-) diff --git a/src/components/Search/SearchMultipleSelectionPicker.tsx b/src/components/Search/SearchMultipleSelectionPicker.tsx index 8e733da24b0..b401effc814 100644 --- a/src/components/Search/SearchMultipleSelectionPicker.tsx +++ b/src/components/Search/SearchMultipleSelectionPicker.tsx @@ -29,9 +29,12 @@ function SearchMultipleSelectionPicker({items, initiallySelectedItems, pickerTit const [searchTerm, debouncedSearchTerm, setSearchTerm] = useDebouncedState(''); const [selectedItems, setSelectedItems] = useState(initiallySelectedItems ?? []); - const sortOptions = (a: SearchMultipleSelectionPickerItem, b: SearchMultipleSelectionPickerItem) => { + const sortOptionsWithNoneValue = (a: SearchMultipleSelectionPickerItem, b: SearchMultipleSelectionPickerItem) => { // Always show `No category` and `No tag` as the first option - if (a.value === CONST.SEARCH.VALUE_NONE || b.value === CONST.SEARCH.VALUE_NONE) { + if (a.value === CONST.SEARCH.VALUE_NONE) { + return -1; + } + if (b.value === CONST.SEARCH.VALUE_NONE) { return 1; } return localeCompare(a.name, b.name); @@ -44,7 +47,7 @@ function SearchMultipleSelectionPicker({items, initiallySelectedItems, pickerTit const {sections, noResultsFound} = useMemo(() => { const selectedItemsSection = selectedItems .filter((item) => item?.name.toLowerCase().includes(debouncedSearchTerm?.toLowerCase())) - .sort((a, b) => sortOptions(a, b)) + .sort((a, b) => sortOptionsWithNoneValue(a, b)) .map((item) => ({ text: item.name, keyForList: item.name, @@ -53,7 +56,7 @@ function SearchMultipleSelectionPicker({items, initiallySelectedItems, pickerTit })); const remainingItemsSection = items .filter((item) => selectedItems.some((selectedItem) => selectedItem.value === item.value) === false && item?.name.toLowerCase().includes(debouncedSearchTerm?.toLowerCase())) - .sort((a, b) => sortOptions(a, b)) + .sort((a, b) => sortOptionsWithNoneValue(a, b)) .map((item) => ({ text: item.name, keyForList: item.name, diff --git a/src/pages/Search/AdvancedSearchFilters.tsx b/src/pages/Search/AdvancedSearchFilters.tsx index 037c7d57de0..309030e9edd 100644 --- a/src/pages/Search/AdvancedSearchFilters.tsx +++ b/src/pages/Search/AdvancedSearchFilters.tsx @@ -138,14 +138,16 @@ function getFilterParticipantDisplayTitle(accountIDs: string[], personalDetails: .join(', '); } -function sortOptions(a: string, b: string) { - console.log('over here', {a,b}) +const sortOptionsWithNoneValue = (a, b) => { // Always show `No category` and `No tag` as the first option - if (a === CONST.SEARCH.VALUE_NONE || b === CONST.SEARCH.VALUE_NONE) { + if (a === CONST.SEARCH.VALUE_NONE) { + return -1; + } + if (b === CONST.SEARCH.VALUE_NONE) { return 1; } return localeCompare(a, b); -} +}; function getFilterDisplayTitle(filters: Partial, fieldName: AdvancedFiltersKeys, translate: LocaleContextProps['translate']) { if (fieldName === CONST.SEARCH.SYNTAX_FILTER_KEYS.DATE) { @@ -191,7 +193,7 @@ function getFilterDisplayTitle(filters: Partial, fiel if (fieldName === CONST.SEARCH.SYNTAX_FILTER_KEYS.CATEGORY && filters[fieldName]) { const filterArray = filters[fieldName] ?? []; return filterArray - .sort(sortOptions) + .sort(sortOptionsWithNoneValue) .map((value) => (value === CONST.SEARCH.VALUE_NONE ? translate('search.noCategory') : value)) .join(', '); } @@ -199,7 +201,7 @@ function getFilterDisplayTitle(filters: Partial, fiel if (fieldName === CONST.SEARCH.SYNTAX_FILTER_KEYS.TAG && filters[fieldName]) { const filterArray = filters[fieldName] ?? []; return filterArray - .sort(sortOptions) + .sort(sortOptionsWithNoneValue) .map((value) => (value === CONST.SEARCH.VALUE_NONE ? translate('search.noTag') : value)) .join(', '); } From 7169928819d864d137b38da2492886b14a2a748a Mon Sep 17 00:00:00 2001 From: Carlos Martins Date: Thu, 10 Oct 2024 16:22:57 -0600 Subject: [PATCH 068/121] rename const --- src/CONST.ts | 2 +- src/components/Search/SearchMultipleSelectionPicker.tsx | 4 ++-- src/pages/Search/AdvancedSearchFilters.tsx | 8 ++++---- .../SearchFiltersCategoryPage.tsx | 4 ++-- .../SearchAdvancedFiltersPage/SearchFiltersTagPage.tsx | 4 ++-- 5 files changed, 11 insertions(+), 11 deletions(-) diff --git a/src/CONST.ts b/src/CONST.ts index d058fda641b..823bae5d0b9 100755 --- a/src/CONST.ts +++ b/src/CONST.ts @@ -5610,7 +5610,7 @@ const CONST = { KEYWORD: 'keyword', IN: 'in', }, - VALUE_NONE: 'none', + EMPTY_VALUE: 'none', }, REFERRER: { diff --git a/src/components/Search/SearchMultipleSelectionPicker.tsx b/src/components/Search/SearchMultipleSelectionPicker.tsx index b401effc814..a6c7b2504f2 100644 --- a/src/components/Search/SearchMultipleSelectionPicker.tsx +++ b/src/components/Search/SearchMultipleSelectionPicker.tsx @@ -31,10 +31,10 @@ function SearchMultipleSelectionPicker({items, initiallySelectedItems, pickerTit const sortOptionsWithNoneValue = (a: SearchMultipleSelectionPickerItem, b: SearchMultipleSelectionPickerItem) => { // Always show `No category` and `No tag` as the first option - if (a.value === CONST.SEARCH.VALUE_NONE) { + if (a.value === CONST.SEARCH.EMPTY_VALUE) { return -1; } - if (b.value === CONST.SEARCH.VALUE_NONE) { + if (b.value === CONST.SEARCH.EMPTY_VALUE) { return 1; } return localeCompare(a.name, b.name); diff --git a/src/pages/Search/AdvancedSearchFilters.tsx b/src/pages/Search/AdvancedSearchFilters.tsx index 309030e9edd..0bd6207cfbe 100644 --- a/src/pages/Search/AdvancedSearchFilters.tsx +++ b/src/pages/Search/AdvancedSearchFilters.tsx @@ -140,10 +140,10 @@ function getFilterParticipantDisplayTitle(accountIDs: string[], personalDetails: const sortOptionsWithNoneValue = (a, b) => { // Always show `No category` and `No tag` as the first option - if (a === CONST.SEARCH.VALUE_NONE) { + if (a === CONST.SEARCH.EMPTY_VALUE) { return -1; } - if (b === CONST.SEARCH.VALUE_NONE) { + if (b === CONST.SEARCH.EMPTY_VALUE) { return 1; } return localeCompare(a, b); @@ -194,7 +194,7 @@ function getFilterDisplayTitle(filters: Partial, fiel const filterArray = filters[fieldName] ?? []; return filterArray .sort(sortOptionsWithNoneValue) - .map((value) => (value === CONST.SEARCH.VALUE_NONE ? translate('search.noCategory') : value)) + .map((value) => (value === CONST.SEARCH.EMPTY_VALUE ? translate('search.noCategory') : value)) .join(', '); } @@ -202,7 +202,7 @@ function getFilterDisplayTitle(filters: Partial, fiel const filterArray = filters[fieldName] ?? []; return filterArray .sort(sortOptionsWithNoneValue) - .map((value) => (value === CONST.SEARCH.VALUE_NONE ? translate('search.noTag') : value)) + .map((value) => (value === CONST.SEARCH.EMPTY_VALUE ? translate('search.noTag') : value)) .join(', '); } diff --git a/src/pages/Search/SearchAdvancedFiltersPage/SearchFiltersCategoryPage.tsx b/src/pages/Search/SearchAdvancedFiltersPage/SearchFiltersCategoryPage.tsx index 87d300487d9..b8f4163f3d4 100644 --- a/src/pages/Search/SearchAdvancedFiltersPage/SearchFiltersCategoryPage.tsx +++ b/src/pages/Search/SearchAdvancedFiltersPage/SearchFiltersCategoryPage.tsx @@ -19,7 +19,7 @@ function SearchFiltersCategoryPage() { const [searchAdvancedFiltersForm] = useOnyx(ONYXKEYS.FORMS.SEARCH_ADVANCED_FILTERS_FORM); const selectedCategoriesItems = searchAdvancedFiltersForm?.category?.map((category) => { - if (category === CONST.SEARCH.VALUE_NONE) { + if (category === CONST.SEARCH.EMPTY_VALUE) { return {name: translate('search.noCategory'), value: category}; } return {name: category, value: category}; @@ -29,7 +29,7 @@ function SearchFiltersCategoryPage() { const singlePolicyCategories = allPolicyIDCategories?.[`${ONYXKEYS.COLLECTION.POLICY_CATEGORIES}${policyID}`]; const categoryItems = useMemo(() => { - const items = [{name: translate('search.noCategory'), value: CONST.SEARCH.VALUE_NONE as string}]; + const items = [{name: translate('search.noCategory'), value: CONST.SEARCH.EMPTY_VALUE as string}]; if (!singlePolicyCategories) { const uniqueCategoryNames = new Set(); Object.values(allPolicyIDCategories ?? {}).map((policyCategories) => Object.values(policyCategories ?? {}).forEach((category) => uniqueCategoryNames.add(category.name))); diff --git a/src/pages/Search/SearchAdvancedFiltersPage/SearchFiltersTagPage.tsx b/src/pages/Search/SearchAdvancedFiltersPage/SearchFiltersTagPage.tsx index 9c808030b3a..85ed0da4330 100644 --- a/src/pages/Search/SearchAdvancedFiltersPage/SearchFiltersTagPage.tsx +++ b/src/pages/Search/SearchAdvancedFiltersPage/SearchFiltersTagPage.tsx @@ -20,7 +20,7 @@ function SearchFiltersTagPage() { const [searchAdvancedFiltersForm] = useOnyx(ONYXKEYS.FORMS.SEARCH_ADVANCED_FILTERS_FORM); const selectedTagsItems = searchAdvancedFiltersForm?.tag?.map((tag) => { - if (tag === CONST.SEARCH.VALUE_NONE) { + if (tag === CONST.SEARCH.EMPTY_VALUE) { return {name: translate('search.noTag'), value: tag}; } return {name: tag, value: tag}; @@ -30,7 +30,7 @@ function SearchFiltersTagPage() { const singlePolicyTagsList: PolicyTagLists | undefined = allPoliciesTagsLists?.[`${ONYXKEYS.COLLECTION.POLICY_TAGS}${policyID}`]; const tagItems = useMemo(() => { - const items = [{name: translate('search.noTag'), value: CONST.SEARCH.VALUE_NONE as string}]; + const items = [{name: translate('search.noTag'), value: CONST.SEARCH.EMPTY_VALUE as string}]; if (!singlePolicyTagsList) { const uniqueTagNames = new Set(); const tagListsUnpacked = Object.values(allPoliciesTagsLists ?? {}).filter((item) => !!item) as PolicyTagLists[]; From bfc0cda69642cb2ce3d6a0f7a15290b97aeb2e26 Mon Sep 17 00:00:00 2001 From: Carlos Martins Date: Thu, 10 Oct 2024 17:31:34 -0600 Subject: [PATCH 069/121] fix ts --- src/pages/Search/AdvancedSearchFilters.tsx | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/pages/Search/AdvancedSearchFilters.tsx b/src/pages/Search/AdvancedSearchFilters.tsx index 0bd6207cfbe..41e4cd485bc 100644 --- a/src/pages/Search/AdvancedSearchFilters.tsx +++ b/src/pages/Search/AdvancedSearchFilters.tsx @@ -138,7 +138,7 @@ function getFilterParticipantDisplayTitle(accountIDs: string[], personalDetails: .join(', '); } -const sortOptionsWithNoneValue = (a, b) => { +const sortOptionsWithNoneValue = (a: string, b: string) => { // Always show `No category` and `No tag` as the first option if (a === CONST.SEARCH.EMPTY_VALUE) { return -1; From 345a8abb1b9a64f1e9b3720c9c04dd62d36945db Mon Sep 17 00:00:00 2001 From: Carlos Martins Date: Thu, 10 Oct 2024 17:32:53 -0600 Subject: [PATCH 070/121] rename function --- src/components/Search/SearchMultipleSelectionPicker.tsx | 6 +++--- src/pages/Search/AdvancedSearchFilters.tsx | 6 +++--- 2 files changed, 6 insertions(+), 6 deletions(-) diff --git a/src/components/Search/SearchMultipleSelectionPicker.tsx b/src/components/Search/SearchMultipleSelectionPicker.tsx index a6c7b2504f2..d76f2e76ab0 100644 --- a/src/components/Search/SearchMultipleSelectionPicker.tsx +++ b/src/components/Search/SearchMultipleSelectionPicker.tsx @@ -29,7 +29,7 @@ function SearchMultipleSelectionPicker({items, initiallySelectedItems, pickerTit const [searchTerm, debouncedSearchTerm, setSearchTerm] = useDebouncedState(''); const [selectedItems, setSelectedItems] = useState(initiallySelectedItems ?? []); - const sortOptionsWithNoneValue = (a: SearchMultipleSelectionPickerItem, b: SearchMultipleSelectionPickerItem) => { + const sortOptionsWithEmptyValue = (a: SearchMultipleSelectionPickerItem, b: SearchMultipleSelectionPickerItem) => { // Always show `No category` and `No tag` as the first option if (a.value === CONST.SEARCH.EMPTY_VALUE) { return -1; @@ -47,7 +47,7 @@ function SearchMultipleSelectionPicker({items, initiallySelectedItems, pickerTit const {sections, noResultsFound} = useMemo(() => { const selectedItemsSection = selectedItems .filter((item) => item?.name.toLowerCase().includes(debouncedSearchTerm?.toLowerCase())) - .sort((a, b) => sortOptionsWithNoneValue(a, b)) + .sort((a, b) => sortOptionsWithEmptyValue(a, b)) .map((item) => ({ text: item.name, keyForList: item.name, @@ -56,7 +56,7 @@ function SearchMultipleSelectionPicker({items, initiallySelectedItems, pickerTit })); const remainingItemsSection = items .filter((item) => selectedItems.some((selectedItem) => selectedItem.value === item.value) === false && item?.name.toLowerCase().includes(debouncedSearchTerm?.toLowerCase())) - .sort((a, b) => sortOptionsWithNoneValue(a, b)) + .sort((a, b) => sortOptionsWithEmptyValue(a, b)) .map((item) => ({ text: item.name, keyForList: item.name, diff --git a/src/pages/Search/AdvancedSearchFilters.tsx b/src/pages/Search/AdvancedSearchFilters.tsx index 41e4cd485bc..c5dc2cf85a5 100644 --- a/src/pages/Search/AdvancedSearchFilters.tsx +++ b/src/pages/Search/AdvancedSearchFilters.tsx @@ -138,7 +138,7 @@ function getFilterParticipantDisplayTitle(accountIDs: string[], personalDetails: .join(', '); } -const sortOptionsWithNoneValue = (a: string, b: string) => { +const sortOptionsWithEmptyValue = (a: string, b: string) => { // Always show `No category` and `No tag` as the first option if (a === CONST.SEARCH.EMPTY_VALUE) { return -1; @@ -193,7 +193,7 @@ function getFilterDisplayTitle(filters: Partial, fiel if (fieldName === CONST.SEARCH.SYNTAX_FILTER_KEYS.CATEGORY && filters[fieldName]) { const filterArray = filters[fieldName] ?? []; return filterArray - .sort(sortOptionsWithNoneValue) + .sort(sortOptionsWithEmptyValue) .map((value) => (value === CONST.SEARCH.EMPTY_VALUE ? translate('search.noCategory') : value)) .join(', '); } @@ -201,7 +201,7 @@ function getFilterDisplayTitle(filters: Partial, fiel if (fieldName === CONST.SEARCH.SYNTAX_FILTER_KEYS.TAG && filters[fieldName]) { const filterArray = filters[fieldName] ?? []; return filterArray - .sort(sortOptionsWithNoneValue) + .sort(sortOptionsWithEmptyValue) .map((value) => (value === CONST.SEARCH.EMPTY_VALUE ? translate('search.noTag') : value)) .join(', '); } From f49e0785e8c46e126ff7cb8c32b1f84f06dfe14e Mon Sep 17 00:00:00 2001 From: Wildan Muhlis Date: Fri, 11 Oct 2024 09:44:19 +0700 Subject: [PATCH 071/121] WIP: Fix regression long loading --- .../Navigation/AppNavigator/AuthScreens.tsx | 38 +++++-------------- src/libs/Navigation/shouldOpenOnAdminRoom.ts | 6 +++ src/libs/ReportUtils.ts | 7 ---- .../BaseOnboardingPersonalDetails.tsx | 26 ++++++++++--- src/pages/signin/SignInModal.tsx | 34 +++++++++-------- 5 files changed, 53 insertions(+), 58 deletions(-) create mode 100644 src/libs/Navigation/shouldOpenOnAdminRoom.ts diff --git a/src/libs/Navigation/AppNavigator/AuthScreens.tsx b/src/libs/Navigation/AppNavigator/AuthScreens.tsx index 21bb35c7000..5fbf6a8cc07 100644 --- a/src/libs/Navigation/AppNavigator/AuthScreens.tsx +++ b/src/libs/Navigation/AppNavigator/AuthScreens.tsx @@ -1,10 +1,11 @@ import React, {memo, useEffect, useMemo, useRef, useState} from 'react'; import {View} from 'react-native'; import type {OnyxEntry} from 'react-native-onyx'; -import Onyx, {withOnyx} from 'react-native-onyx'; +import Onyx, {useOnyx} from 'react-native-onyx'; import type {ValueOf} from 'type-fest'; import ActiveGuidesEventListener from '@components/ActiveGuidesEventListener'; import ComposeProviders from '@components/ComposeProviders'; +import {useSession} from '@components/OnyxProvider'; import OptionsListContextProvider from '@components/OptionListContextProvider'; import {SearchContextProvider} from '@components/Search/SearchContext'; import SearchRouterModal from '@components/Search/SearchRouter/SearchRouterModal'; @@ -21,6 +22,7 @@ import Log from '@libs/Log'; import getCurrentUrl from '@libs/Navigation/currentUrl'; import getOnboardingModalScreenOptions from '@libs/Navigation/getOnboardingModalScreenOptions'; import Navigation from '@libs/Navigation/Navigation'; +import shouldOpenOnAdminRoom from '@libs/Navigation/shouldOpenOnAdminRoom'; import type {AuthScreensParamList, CentralPaneName, CentralPaneScreensParamList} from '@libs/Navigation/types'; import NetworkConnection from '@libs/NetworkConnection'; import onyxSubscribe from '@libs/onyxSubscribe'; @@ -66,17 +68,6 @@ import OnboardingModalNavigator from './Navigators/OnboardingModalNavigator'; import RightModalNavigator from './Navigators/RightModalNavigator'; import WelcomeVideoModalNavigator from './Navigators/WelcomeVideoModalNavigator'; -type AuthScreensProps = { - /** Session of currently logged in user */ - session: OnyxEntry; - - /** The report ID of the last opened public room as anonymous user */ - lastOpenedPublicRoomID: OnyxEntry; - - /** The last Onyx update ID was applied to the client */ - initialLastUpdateIDAppliedToClient: OnyxEntry; -}; - const loadReportAttachments = () => require('../../../pages/home/report/ReportAttachments').default; const loadValidateLoginPage = () => require('../../../pages/ValidateLoginPage').default; const loadLogOutPreviousUserPage = () => require('../../../pages/LogOutPreviousUserPage').default; @@ -89,11 +80,6 @@ const loadReportAvatar = () => require('../../../pages/Rep const loadReceiptView = () => require('../../../pages/TransactionReceiptPage').default; const loadWorkspaceJoinUser = () => require('@pages/workspace/WorkspaceJoinUserPage').default; -function shouldOpenOnAdminRoom() { - const url = getCurrentUrl(); - return url ? new URL(url).searchParams.get('openOnAdminRoom') === 'true' : false; -} - function getCentralPaneScreenInitialParams(screenName: CentralPaneName, initialReportID?: string): Partial> { if (screenName === SCREENS.SEARCH.CENTRAL_PANE) { // Generate default query string with buildSearchQueryString without argument. @@ -225,7 +211,7 @@ const modalScreenListenersWithCancelSearch = { }, }; -function AuthScreens({session, lastOpenedPublicRoomID, initialLastUpdateIDAppliedToClient}: AuthScreensProps) { +function AuthScreens() { const styles = useThemeStyles(); const StyleUtils = useStyleUtils(); const {shouldUseNarrowLayout, onboardingIsMediumOrLargerScreenWidth, isSmallScreenWidth} = useResponsiveLayout(); @@ -242,6 +228,10 @@ function AuthScreens({session, lastOpenedPublicRoomID, initialLastUpdateIDApplie const {isOnboardingCompleted} = useOnboardingFlowRouter(); let initialReportID: string | undefined; const isInitialRender = useRef(true); + const session = useSession(); + const [lastOpenedPublicRoomID] = useOnyx(ONYXKEYS.LAST_OPENED_PUBLIC_ROOM_ID); + const [initialLastUpdateIDAppliedToClient] = useOnyx(ONYXKEYS.ONYX_UPDATES_LAST_UPDATE_ID_APPLIED_TO_CLIENT); + if (isInitialRender.current) { Timing.start(CONST.TIMING.HOMEPAGE_INITIAL_RENDER); @@ -589,14 +579,4 @@ AuthScreens.displayName = 'AuthScreens'; const AuthScreensMemoized = memo(AuthScreens, () => true); -export default withOnyx({ - session: { - key: ONYXKEYS.SESSION, - }, - lastOpenedPublicRoomID: { - key: ONYXKEYS.LAST_OPENED_PUBLIC_ROOM_ID, - }, - initialLastUpdateIDAppliedToClient: { - key: ONYXKEYS.ONYX_UPDATES_LAST_UPDATE_ID_APPLIED_TO_CLIENT, - }, -})(AuthScreensMemoized); +export default AuthScreensMemoized; diff --git a/src/libs/Navigation/shouldOpenOnAdminRoom.ts b/src/libs/Navigation/shouldOpenOnAdminRoom.ts new file mode 100644 index 00000000000..a593e8c2276 --- /dev/null +++ b/src/libs/Navigation/shouldOpenOnAdminRoom.ts @@ -0,0 +1,6 @@ +import getCurrentUrl from './currentUrl'; + +export default function shouldOpenOnAdminRoom() { + const url = getCurrentUrl(); + return url ? new URL(url).searchParams.get('openOnAdminRoom') === 'true' : false; +} diff --git a/src/libs/ReportUtils.ts b/src/libs/ReportUtils.ts index b4c275e0346..3abf3b8b76c 100644 --- a/src/libs/ReportUtils.ts +++ b/src/libs/ReportUtils.ts @@ -65,7 +65,6 @@ import * as CurrencyUtils from './CurrencyUtils'; import DateUtils from './DateUtils'; import {hasValidDraftComment} from './DraftCommentUtils'; import getAttachmentDetails from './fileDownload/getAttachmentDetails'; -import getIsSmallScreenWidth from './getIsSmallScreenWidth'; import isReportMessageAttachment from './isReportMessageAttachment'; import localeCompare from './LocaleCompare'; import * as LocalePhoneNumber from './LocalePhoneNumber'; @@ -1360,12 +1359,6 @@ function findLastAccessedReport(ignoreDomainRooms: boolean, openOnAdminRoom = fa }); } - // if the user hasn't completed the onboarding flow, whether the user should be in the concierge chat or system chat - // should be consistent with what chat the user will land after onboarding flow - if (!getIsSmallScreenWidth() && !Array.isArray(onboarding) && !onboarding?.hasCompletedGuidedSetupFlow) { - return reportsValues.find(isChatUsedForOnboarding); - } - // eslint-disable-next-line @typescript-eslint/prefer-nullish-coalescing const shouldFilter = excludeReportID || ignoreDomainRooms; if (shouldFilter) { diff --git a/src/pages/OnboardingPersonalDetails/BaseOnboardingPersonalDetails.tsx b/src/pages/OnboardingPersonalDetails/BaseOnboardingPersonalDetails.tsx index eb6d61770be..3218cf9e1d1 100644 --- a/src/pages/OnboardingPersonalDetails/BaseOnboardingPersonalDetails.tsx +++ b/src/pages/OnboardingPersonalDetails/BaseOnboardingPersonalDetails.tsx @@ -10,13 +10,17 @@ import ScreenWrapper from '@components/ScreenWrapper'; import Text from '@components/Text'; import TextInput from '@components/TextInput'; import withCurrentUserPersonalDetails from '@components/withCurrentUserPersonalDetails'; +import useActiveWorkspace from '@hooks/useActiveWorkspace'; import useAutoFocusInput from '@hooks/useAutoFocusInput'; import useLocalize from '@hooks/useLocalize'; import useNetwork from '@hooks/useNetwork'; +import usePermissions from '@hooks/usePermissions'; import useResponsiveLayout from '@hooks/useResponsiveLayout'; import useThemeStyles from '@hooks/useThemeStyles'; import * as ErrorUtils from '@libs/ErrorUtils'; import Navigation from '@libs/Navigation/Navigation'; +import shouldOpenOnAdminRoom from '@libs/Navigation/shouldOpenOnAdminRoom'; +import * as ReportUtils from '@libs/ReportUtils'; import * as ValidationUtils from '@libs/ValidationUtils'; import * as PersonalDetails from '@userActions/PersonalDetails'; import * as Report from '@userActions/Report'; @@ -24,10 +28,11 @@ import * as Welcome from '@userActions/Welcome'; import * as OnboardingFlow from '@userActions/Welcome/OnboardingFlow'; import CONST from '@src/CONST'; import ONYXKEYS from '@src/ONYXKEYS'; +import ROUTES from '@src/ROUTES'; import INPUT_IDS from '@src/types/form/DisplayNameForm'; import type {BaseOnboardingPersonalDetailsProps} from './types'; -function BaseOnboardingPersonalDetails({currentUserPersonalDetails, shouldUseNativeStyles, route}: BaseOnboardingPersonalDetailsProps) { +function BaseOnboardingPersonalDetails({currentUserPersonalDetails, shouldUseNativeStyles}: BaseOnboardingPersonalDetailsProps) { const styles = useThemeStyles(); const {translate} = useLocalize(); const [onboardingPurposeSelected] = useOnyx(ONYXKEYS.ONBOARDING_PURPOSE_SELECTED); @@ -37,6 +42,8 @@ function BaseOnboardingPersonalDetails({currentUserPersonalDetails, shouldUseNat const {inputCallbackRef} = useAutoFocusInput(); const [shouldValidateOnChange, setShouldValidateOnChange] = useState(false); const {isOffline} = useNetwork(); + const {canUseDefaultRooms} = usePermissions(); + const {activeWorkspaceID} = useActiveWorkspace(); useEffect(() => { Welcome.setOnboardingErrorMessage(''); @@ -67,13 +74,20 @@ function BaseOnboardingPersonalDetails({currentUserPersonalDetails, shouldUseNat Navigation.dismissModal(); - // Only navigate to concierge chat when central pane is visible - // Otherwise stay on the chats screen. - if (!shouldUseNarrowLayout && !route.params?.backTo) { - Report.navigateToConciergeChat(); + // When hasCompletedGuidedSetupFlow is true, OnboardingModalNavigator in AuthScreen is removed from the navigation stack. + // On small screens, this removal redirects navigation to HOME. Dismissing the modal doesn't work properly, + // so we need to specifically navigate to the last accessed report. + if (!isSmallScreenWidth) { + return; + } + const lastAccessedReportID = ReportUtils.findLastAccessedReport(!canUseDefaultRooms, shouldOpenOnAdminRoom(), activeWorkspaceID)?.reportID; + if (!lastAccessedReportID) { + return; } + const lastAccessedReportRoute = ROUTES.REPORT_WITH_ID.getRoute(lastAccessedReportID ?? '-1'); + Navigation.navigate(lastAccessedReportRoute); }, - [onboardingPurposeSelected, onboardingAdminsChatReportID, onboardingPolicyID, shouldUseNarrowLayout, route.params?.backTo], + [onboardingPurposeSelected, onboardingAdminsChatReportID, onboardingPolicyID, activeWorkspaceID, canUseDefaultRooms, isSmallScreenWidth], ); const validate = (values: FormOnyxValues<'onboardingPersonalDetailsForm'>) => { diff --git a/src/pages/signin/SignInModal.tsx b/src/pages/signin/SignInModal.tsx index bad93b29f8a..ae4199d80bf 100644 --- a/src/pages/signin/SignInModal.tsx +++ b/src/pages/signin/SignInModal.tsx @@ -1,36 +1,40 @@ import React, {useEffect, useRef} from 'react'; -import type {OnyxEntry} from 'react-native-onyx'; -import {withOnyx} from 'react-native-onyx'; import HeaderWithBackButton from '@components/HeaderWithBackButton'; +import {useSession} from '@components/OnyxProvider'; import ScreenWrapper from '@components/ScreenWrapper'; import useStyleUtils from '@hooks/useStyleUtils'; import useTheme from '@hooks/useTheme'; import Navigation from '@libs/Navigation/Navigation'; +import {waitForIdle} from '@libs/Network/SequentialQueue'; import * as App from '@userActions/App'; import CONST from '@src/CONST'; -import ONYXKEYS from '@src/ONYXKEYS'; import SCREENS from '@src/SCREENS'; -import type {Session} from '@src/types/onyx'; import SignInPage from './SignInPage'; import type {SignInPageRef} from './SignInPage'; -type SignInModalOnyxProps = { - session: OnyxEntry; -}; - -type SignInModalProps = SignInModalOnyxProps; - -function SignInModal({session}: SignInModalProps) { +function SignInModal() { const theme = useTheme(); const StyleUtils = useStyleUtils(); const siginPageRef = useRef(null); + const session = useSession(); useEffect(() => { const isAnonymousUser = session?.authTokenType === CONST.AUTH_TOKEN_TYPES.ANONYMOUS; + console.log('[wildebug] isAnonymousUser:', isAnonymousUser); if (!isAnonymousUser) { // Signing in RHP is only for anonymous users - Navigation.isNavigationReady().then(() => Navigation.dismissModal()); - App.openApp(); + Navigation.isNavigationReady().then(() => { + console.log('[wildebug] Navigation is ready, dismissing modal'); + Navigation.dismissModal(); + }); + + // To prevent deadlock when OpenReport and OpenApp overlap, wait for the queue to be idle before calling openApp. + // This ensures that any communication gaps between the client and server during OpenReport processing do not cause the queue to pause, + // which would prevent us from processing or clearing the queue. + // waitForIdle().then(() => { + console.log('[wildebug] Opening app'); + App.openApp(); + // }); } }, [session?.authTokenType]); @@ -61,6 +65,4 @@ function SignInModal({session}: SignInModalProps) { SignInModal.displayName = 'SignInModal'; -export default withOnyx({ - session: {key: ONYXKEYS.SESSION}, -})(SignInModal); +export default SignInModal; From d0194203eb6225d34167fcd0529538548b04d696 Mon Sep 17 00:00:00 2001 From: daledah Date: Fri, 11 Oct 2024 11:06:56 +0700 Subject: [PATCH 072/121] fix: archive invoice room when deleting workspace --- src/libs/actions/Policy/Policy.ts | 4 +++- 1 file changed, 3 insertions(+), 1 deletion(-) diff --git a/src/libs/actions/Policy/Policy.ts b/src/libs/actions/Policy/Policy.ts index ec37b2c4e1f..788ee2ed5d6 100644 --- a/src/libs/actions/Policy/Policy.ts +++ b/src/libs/actions/Policy/Policy.ts @@ -310,7 +310,9 @@ function deleteWorkspace(policyID: string, policyName: string) { ]; const reportsToArchive = Object.values(ReportConnection.getAllReports() ?? {}).filter( - (report) => report?.policyID === policyID && (ReportUtils.isChatRoom(report) || ReportUtils.isPolicyExpenseChat(report) || ReportUtils.isTaskReport(report)), + (report) => + (report?.policyID === policyID || (report?.invoiceReceiver && 'policyID' in report.invoiceReceiver && report.invoiceReceiver.policyID === policyID)) && + (ReportUtils.isChatRoom(report) || ReportUtils.isPolicyExpenseChat(report) || ReportUtils.isTaskReport(report)), ); const finallyData: OnyxUpdate[] = []; const currentTime = DateUtils.getDBTime(); From a21116547e667d913356a219bdebb6681b56f7cd Mon Sep 17 00:00:00 2001 From: Abdelrahman Khattab <59809993+abzokhattab@users.noreply.github.com> Date: Fri, 11 Oct 2024 08:45:30 +0200 Subject: [PATCH 073/121] Fix the Spanish translation for "receiptNotSmartScanned" --- src/languages/es.ts | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/languages/es.ts b/src/languages/es.ts index 2a7a9864c17..14beea26cfa 100644 --- a/src/languages/es.ts +++ b/src/languages/es.ts @@ -5098,7 +5098,7 @@ const translations = { overLimit: ({formattedLimit}: ViolationsOverLimitParams) => `Importe supera el límite${formattedLimit ? ` de ${formattedLimit}/persona` : ''}`, overLimitAttendee: ({formattedLimit}: ViolationsOverLimitParams) => `Importe supera el límite${formattedLimit ? ` de ${formattedLimit}/persona` : ''}`, perDayLimit: ({formattedLimit}: ViolationsPerDayLimitParams) => `Importe supera el límite diario de la categoría${formattedLimit ? ` de ${formattedLimit}/persona` : ''}`, - receiptNotSmartScanned: 'Recibo no verificado. Por favor, confirma tu exactitud', + receiptNotSmartScanned: 'Recibo no verificado. Por favor, confirma la exactitud', receiptRequired: ({formattedLimit, category}: ViolationsReceiptRequiredParams) => { let message = 'Recibo obligatorio'; if (formattedLimit ?? category) { From e9d9e26fa5f1704edd8c4de8ef13e89677c1fed3 Mon Sep 17 00:00:00 2001 From: Wildan Muhlis Date: Fri, 11 Oct 2024 15:15:58 +0700 Subject: [PATCH 074/121] Revert AuthScreens withOnyx migration, it cause re-login in desktop from deeplinking --- .../Navigation/AppNavigator/AuthScreens.tsx | 34 ++++++++++++++----- 1 file changed, 25 insertions(+), 9 deletions(-) diff --git a/src/libs/Navigation/AppNavigator/AuthScreens.tsx b/src/libs/Navigation/AppNavigator/AuthScreens.tsx index 5fbf6a8cc07..a03752144a0 100644 --- a/src/libs/Navigation/AppNavigator/AuthScreens.tsx +++ b/src/libs/Navigation/AppNavigator/AuthScreens.tsx @@ -1,11 +1,10 @@ import React, {memo, useEffect, useMemo, useRef, useState} from 'react'; import {View} from 'react-native'; import type {OnyxEntry} from 'react-native-onyx'; -import Onyx, {useOnyx} from 'react-native-onyx'; +import Onyx, {withOnyx} from 'react-native-onyx'; import type {ValueOf} from 'type-fest'; import ActiveGuidesEventListener from '@components/ActiveGuidesEventListener'; import ComposeProviders from '@components/ComposeProviders'; -import {useSession} from '@components/OnyxProvider'; import OptionsListContextProvider from '@components/OptionListContextProvider'; import {SearchContextProvider} from '@components/Search/SearchContext'; import SearchRouterModal from '@components/Search/SearchRouter/SearchRouterModal'; @@ -22,7 +21,6 @@ import Log from '@libs/Log'; import getCurrentUrl from '@libs/Navigation/currentUrl'; import getOnboardingModalScreenOptions from '@libs/Navigation/getOnboardingModalScreenOptions'; import Navigation from '@libs/Navigation/Navigation'; -import shouldOpenOnAdminRoom from '@libs/Navigation/shouldOpenOnAdminRoom'; import type {AuthScreensParamList, CentralPaneName, CentralPaneScreensParamList} from '@libs/Navigation/types'; import NetworkConnection from '@libs/NetworkConnection'; import onyxSubscribe from '@libs/onyxSubscribe'; @@ -67,6 +65,18 @@ import LeftModalNavigator from './Navigators/LeftModalNavigator'; import OnboardingModalNavigator from './Navigators/OnboardingModalNavigator'; import RightModalNavigator from './Navigators/RightModalNavigator'; import WelcomeVideoModalNavigator from './Navigators/WelcomeVideoModalNavigator'; +import shouldOpenOnAdminRoom from '@libs/Navigation/shouldOpenOnAdminRoom'; + +type AuthScreensProps = { + /** Session of currently logged in user */ + session: OnyxEntry; + + /** The report ID of the last opened public room as anonymous user */ + lastOpenedPublicRoomID: OnyxEntry; + + /** The last Onyx update ID was applied to the client */ + initialLastUpdateIDAppliedToClient: OnyxEntry; +}; const loadReportAttachments = () => require('../../../pages/home/report/ReportAttachments').default; const loadValidateLoginPage = () => require('../../../pages/ValidateLoginPage').default; @@ -211,7 +221,7 @@ const modalScreenListenersWithCancelSearch = { }, }; -function AuthScreens() { +function AuthScreens({session, lastOpenedPublicRoomID, initialLastUpdateIDAppliedToClient}: AuthScreensProps) { const styles = useThemeStyles(); const StyleUtils = useStyleUtils(); const {shouldUseNarrowLayout, onboardingIsMediumOrLargerScreenWidth, isSmallScreenWidth} = useResponsiveLayout(); @@ -228,10 +238,6 @@ function AuthScreens() { const {isOnboardingCompleted} = useOnboardingFlowRouter(); let initialReportID: string | undefined; const isInitialRender = useRef(true); - const session = useSession(); - const [lastOpenedPublicRoomID] = useOnyx(ONYXKEYS.LAST_OPENED_PUBLIC_ROOM_ID); - const [initialLastUpdateIDAppliedToClient] = useOnyx(ONYXKEYS.ONYX_UPDATES_LAST_UPDATE_ID_APPLIED_TO_CLIENT); - if (isInitialRender.current) { Timing.start(CONST.TIMING.HOMEPAGE_INITIAL_RENDER); @@ -579,4 +585,14 @@ AuthScreens.displayName = 'AuthScreens'; const AuthScreensMemoized = memo(AuthScreens, () => true); -export default AuthScreensMemoized; +export default withOnyx({ + session: { + key: ONYXKEYS.SESSION, + }, + lastOpenedPublicRoomID: { + key: ONYXKEYS.LAST_OPENED_PUBLIC_ROOM_ID, + }, + initialLastUpdateIDAppliedToClient: { + key: ONYXKEYS.ONYX_UPDATES_LAST_UPDATE_ID_APPLIED_TO_CLIENT, + }, +})(AuthScreensMemoized); From 8db66d37dd4a64a163f15a44c4be91d8fc3d18ce Mon Sep 17 00:00:00 2001 From: Wildan Muhlis Date: Fri, 11 Oct 2024 15:41:17 +0700 Subject: [PATCH 075/121] prettier, lint --- src/libs/Navigation/AppNavigator/AuthScreens.tsx | 6 +++++- .../BaseOnboardingPersonalDetails.tsx | 2 +- src/pages/signin/SignInModal.tsx | 7 ++----- 3 files changed, 8 insertions(+), 7 deletions(-) diff --git a/src/libs/Navigation/AppNavigator/AuthScreens.tsx b/src/libs/Navigation/AppNavigator/AuthScreens.tsx index a03752144a0..4764696c823 100644 --- a/src/libs/Navigation/AppNavigator/AuthScreens.tsx +++ b/src/libs/Navigation/AppNavigator/AuthScreens.tsx @@ -21,6 +21,7 @@ import Log from '@libs/Log'; import getCurrentUrl from '@libs/Navigation/currentUrl'; import getOnboardingModalScreenOptions from '@libs/Navigation/getOnboardingModalScreenOptions'; import Navigation from '@libs/Navigation/Navigation'; +import shouldOpenOnAdminRoom from '@libs/Navigation/shouldOpenOnAdminRoom'; import type {AuthScreensParamList, CentralPaneName, CentralPaneScreensParamList} from '@libs/Navigation/types'; import NetworkConnection from '@libs/NetworkConnection'; import onyxSubscribe from '@libs/onyxSubscribe'; @@ -65,7 +66,6 @@ import LeftModalNavigator from './Navigators/LeftModalNavigator'; import OnboardingModalNavigator from './Navigators/OnboardingModalNavigator'; import RightModalNavigator from './Navigators/RightModalNavigator'; import WelcomeVideoModalNavigator from './Navigators/WelcomeVideoModalNavigator'; -import shouldOpenOnAdminRoom from '@libs/Navigation/shouldOpenOnAdminRoom'; type AuthScreensProps = { /** Session of currently logged in user */ @@ -585,6 +585,10 @@ AuthScreens.displayName = 'AuthScreens'; const AuthScreensMemoized = memo(AuthScreens, () => true); +// Migration to useOnyx cause re-login if logout from deeplinked report in desktop app +// Further analysis required and more details can be seen here: +// https://github.com/Expensify/App/issues/50560 +// eslint-disable-next-line export default withOnyx({ session: { key: ONYXKEYS.SESSION, diff --git a/src/pages/OnboardingPersonalDetails/BaseOnboardingPersonalDetails.tsx b/src/pages/OnboardingPersonalDetails/BaseOnboardingPersonalDetails.tsx index 3218cf9e1d1..91d80c865db 100644 --- a/src/pages/OnboardingPersonalDetails/BaseOnboardingPersonalDetails.tsx +++ b/src/pages/OnboardingPersonalDetails/BaseOnboardingPersonalDetails.tsx @@ -38,7 +38,7 @@ function BaseOnboardingPersonalDetails({currentUserPersonalDetails, shouldUseNat const [onboardingPurposeSelected] = useOnyx(ONYXKEYS.ONBOARDING_PURPOSE_SELECTED); const [onboardingPolicyID] = useOnyx(ONYXKEYS.ONBOARDING_POLICY_ID); const [onboardingAdminsChatReportID] = useOnyx(ONYXKEYS.ONBOARDING_ADMINS_CHAT_REPORT_ID); - const {shouldUseNarrowLayout, isSmallScreenWidth, onboardingIsMediumOrLargerScreenWidth} = useResponsiveLayout(); + const {isSmallScreenWidth, onboardingIsMediumOrLargerScreenWidth} = useResponsiveLayout(); const {inputCallbackRef} = useAutoFocusInput(); const [shouldValidateOnChange, setShouldValidateOnChange] = useState(false); const {isOffline} = useNetwork(); diff --git a/src/pages/signin/SignInModal.tsx b/src/pages/signin/SignInModal.tsx index ae4199d80bf..8cfa3ba3dcc 100644 --- a/src/pages/signin/SignInModal.tsx +++ b/src/pages/signin/SignInModal.tsx @@ -20,21 +20,18 @@ function SignInModal() { useEffect(() => { const isAnonymousUser = session?.authTokenType === CONST.AUTH_TOKEN_TYPES.ANONYMOUS; - console.log('[wildebug] isAnonymousUser:', isAnonymousUser); if (!isAnonymousUser) { // Signing in RHP is only for anonymous users Navigation.isNavigationReady().then(() => { - console.log('[wildebug] Navigation is ready, dismissing modal'); Navigation.dismissModal(); }); // To prevent deadlock when OpenReport and OpenApp overlap, wait for the queue to be idle before calling openApp. // This ensures that any communication gaps between the client and server during OpenReport processing do not cause the queue to pause, // which would prevent us from processing or clearing the queue. - // waitForIdle().then(() => { - console.log('[wildebug] Opening app'); + waitForIdle().then(() => { App.openApp(); - // }); + }); } }, [session?.authTokenType]); From 372c647e197921a1469826c0dede2b646858708f Mon Sep 17 00:00:00 2001 From: mkzie2 Date: Fri, 11 Oct 2024 16:17:06 +0700 Subject: [PATCH 076/121] fix: report RHP turns to central pane after editing category and sending a message --- src/pages/home/report/ReportActionsList.tsx | 5 ++++- 1 file changed, 4 insertions(+), 1 deletion(-) diff --git a/src/pages/home/report/ReportActionsList.tsx b/src/pages/home/report/ReportActionsList.tsx index ce925d4375a..96b19439f8c 100644 --- a/src/pages/home/report/ReportActionsList.tsx +++ b/src/pages/home/report/ReportActionsList.tsx @@ -159,7 +159,7 @@ function ReportActionsList({ const styles = useThemeStyles(); const {translate} = useLocalize(); const {windowHeight} = useWindowDimensions(); - const {shouldUseNarrowLayout} = useResponsiveLayout(); + const {isInNarrowPaneModal, shouldUseNarrowLayout} = useResponsiveLayout(); const {isOffline} = useNetwork(); const route = useRoute>(); @@ -346,6 +346,9 @@ function ReportActionsList({ return; } if (!hasNewestReportActionRef.current) { + if (isInNarrowPaneModal) { + return; + } Navigation.navigate(ROUTES.REPORT_WITH_ID.getRoute(report.reportID)); return; } From 67d9f71eb173a000bf856ad74297024ed95289e4 Mon Sep 17 00:00:00 2001 From: mkzie2 Date: Fri, 11 Oct 2024 16:20:48 +0700 Subject: [PATCH 077/121] fix lint --- src/pages/home/report/ReportActionsList.tsx | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/pages/home/report/ReportActionsList.tsx b/src/pages/home/report/ReportActionsList.tsx index 96b19439f8c..76ba6426929 100644 --- a/src/pages/home/report/ReportActionsList.tsx +++ b/src/pages/home/report/ReportActionsList.tsx @@ -354,7 +354,7 @@ function ReportActionsList({ } InteractionManager.runAfterInteractions(() => reportScrollManager.scrollToBottom()); }, - [reportScrollManager, report.reportID], + [isInNarrowPaneModal, reportScrollManager, report.reportID], ); useEffect(() => { // Why are we doing this, when in the cleanup of the useEffect we are already calling the unsubscribe function? From 949cd39e5ec92543d0f9f722f90dad7b2dbebc1d Mon Sep 17 00:00:00 2001 From: dominictb Date: Fri, 11 Oct 2024 16:25:48 +0700 Subject: [PATCH 078/121] fix: tap gesture does not work when keyboard is visible in default spend category page --- .../workspace/distanceRates/PolicyDistanceRatesSettingsPage.tsx | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/pages/workspace/distanceRates/PolicyDistanceRatesSettingsPage.tsx b/src/pages/workspace/distanceRates/PolicyDistanceRatesSettingsPage.tsx index 53c1fe14237..6bfda5f60e2 100644 --- a/src/pages/workspace/distanceRates/PolicyDistanceRatesSettingsPage.tsx +++ b/src/pages/workspace/distanceRates/PolicyDistanceRatesSettingsPage.tsx @@ -97,7 +97,7 @@ function PolicyDistanceRatesSettingsPage({policy, policyCategories, route}: Poli > - + {defaultUnit && ( Date: Fri, 11 Oct 2024 16:31:25 +0700 Subject: [PATCH 079/121] prettier --- .../distanceRates/PolicyDistanceRatesSettingsPage.tsx | 5 ++++- 1 file changed, 4 insertions(+), 1 deletion(-) diff --git a/src/pages/workspace/distanceRates/PolicyDistanceRatesSettingsPage.tsx b/src/pages/workspace/distanceRates/PolicyDistanceRatesSettingsPage.tsx index 6bfda5f60e2..124d505c51d 100644 --- a/src/pages/workspace/distanceRates/PolicyDistanceRatesSettingsPage.tsx +++ b/src/pages/workspace/distanceRates/PolicyDistanceRatesSettingsPage.tsx @@ -97,7 +97,10 @@ function PolicyDistanceRatesSettingsPage({policy, policyCategories, route}: Poli > - + {defaultUnit && ( Date: Fri, 11 Oct 2024 13:36:56 +0200 Subject: [PATCH 080/121] fix PR comments --- src/components/Search/SearchRouter/SearchRouter.tsx | 2 +- src/styles/variables.ts | 3 ++- 2 files changed, 3 insertions(+), 2 deletions(-) diff --git a/src/components/Search/SearchRouter/SearchRouter.tsx b/src/components/Search/SearchRouter/SearchRouter.tsx index 564e5f0feda..5c1f4e27c93 100644 --- a/src/components/Search/SearchRouter/SearchRouter.tsx +++ b/src/components/Search/SearchRouter/SearchRouter.tsx @@ -153,7 +153,7 @@ function SearchRouter({onRouterClose}: SearchRouterProps) { closeAndClearRouter(); }); - const modalWidth = isSmallScreenWidth ? styles.w100 : {width: variables.popoverWidth}; + const modalWidth = isSmallScreenWidth ? styles.w100 : {width: variables.searchRouterPopoverWidth}; return ( Date: Fri, 11 Oct 2024 18:33:46 +0100 Subject: [PATCH 081/121] fix(debug mode): is temporary focused reason shown instead of has RBR message --- src/libs/DebugUtils.ts | 12 +++++++++++- 1 file changed, 11 insertions(+), 1 deletion(-) diff --git a/src/libs/DebugUtils.ts b/src/libs/DebugUtils.ts index 3b6c1c77713..97469f5f6c7 100644 --- a/src/libs/DebugUtils.ts +++ b/src/libs/DebugUtils.ts @@ -7,6 +7,7 @@ import type {TranslationPaths} from '@src/languages/types'; import ONYXKEYS from '@src/ONYXKEYS'; import type {Beta, Policy, Report, ReportAction, ReportActions, TransactionViolation} from '@src/types/onyx'; import * as ReportUtils from './ReportUtils'; +import SidebarUtils from './SidebarUtils'; class NumberError extends SyntaxError { constructor() { @@ -127,6 +128,15 @@ Onyx.connect({ }, }); +let reportActions: OnyxCollection; +Onyx.connect({ + key: ONYXKEYS.COLLECTION.REPORT_ACTIONS, + waitForCollectionCallback: true, + callback: (value) => { + reportActions = value; + }, +}); + let transactionViolations: OnyxCollection; Onyx.connect({ key: ONYXKEYS.COLLECTION.TRANSACTION_VIOLATIONS, @@ -612,7 +622,7 @@ function getReasonForShowingRowInLHN(report: OnyxEntry): TranslationPath if ( !([CONST.REPORT_IN_LHN_REASONS.HAS_ADD_WORKSPACE_ROOM_ERRORS, CONST.REPORT_IN_LHN_REASONS.HAS_IOU_VIOLATIONS] as Array).includes(reason) && - ReportUtils.hasReportErrorsOtherThanFailedReceipt(report, doesReportHaveViolations, transactionViolations) + SidebarUtils.shouldShowRedBrickRoad(report, reportActions?.[`${ONYXKEYS.COLLECTION.REPORT_ACTIONS}${report?.reportID}`], doesReportHaveViolations, transactionViolations) ) { return `debug.reasonVisibleInLHN.hasRBR`; } From 6a8b6fd22ecf49d14013c7dff83c3dcba8c2c2a2 Mon Sep 17 00:00:00 2001 From: Pedro Guerreiro Date: Fri, 11 Oct 2024 18:38:15 +0100 Subject: [PATCH 082/121] chore: fix eslint errors --- src/libs/DebugUtils.ts | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/src/libs/DebugUtils.ts b/src/libs/DebugUtils.ts index 97469f5f6c7..261d2db208d 100644 --- a/src/libs/DebugUtils.ts +++ b/src/libs/DebugUtils.ts @@ -128,12 +128,12 @@ Onyx.connect({ }, }); -let reportActions: OnyxCollection; +let reportActionsCollection: OnyxCollection; Onyx.connect({ key: ONYXKEYS.COLLECTION.REPORT_ACTIONS, waitForCollectionCallback: true, callback: (value) => { - reportActions = value; + reportActionsCollection = value; }, }); @@ -622,7 +622,7 @@ function getReasonForShowingRowInLHN(report: OnyxEntry): TranslationPath if ( !([CONST.REPORT_IN_LHN_REASONS.HAS_ADD_WORKSPACE_ROOM_ERRORS, CONST.REPORT_IN_LHN_REASONS.HAS_IOU_VIOLATIONS] as Array).includes(reason) && - SidebarUtils.shouldShowRedBrickRoad(report, reportActions?.[`${ONYXKEYS.COLLECTION.REPORT_ACTIONS}${report?.reportID}`], doesReportHaveViolations, transactionViolations) + SidebarUtils.shouldShowRedBrickRoad(report, reportActionsCollection?.[`${ONYXKEYS.COLLECTION.REPORT_ACTIONS}${report?.reportID}`], doesReportHaveViolations, transactionViolations) ) { return `debug.reasonVisibleInLHN.hasRBR`; } From 237769e02b67785e2d629ccf12a8422ffdf49a6f Mon Sep 17 00:00:00 2001 From: Wildan Muhlis Date: Sat, 12 Oct 2024 10:01:52 +0700 Subject: [PATCH 083/121] Fix navigation after onboarding, apply to accounting, extract similar logic --- src/libs/navigateAfterOnboarding.ts | 30 +++++++++++++++++++ .../BaseOnboardingAccounting.tsx | 16 +++++----- .../BaseOnboardingPersonalDetails.tsx | 24 +++------------ 3 files changed, 41 insertions(+), 29 deletions(-) create mode 100644 src/libs/navigateAfterOnboarding.ts diff --git a/src/libs/navigateAfterOnboarding.ts b/src/libs/navigateAfterOnboarding.ts new file mode 100644 index 00000000000..c6751f39ba1 --- /dev/null +++ b/src/libs/navigateAfterOnboarding.ts @@ -0,0 +1,30 @@ +import shouldOpenOnAdminRoom from '@libs/Navigation/shouldOpenOnAdminRoom'; +import * as ReportUtils from '@libs/ReportUtils'; +import Navigation from '@navigation/Navigation'; +import * as Report from '@userActions/Report'; +import ROUTES from '@src/ROUTES'; + +const navigateAfterOnboarding = (isSmallScreenWidth: boolean, canUseDefaultRooms: boolean | undefined, onboardingPolicyID?: string, activeWorkspaceID?: string, backTo?: string) => { + Navigation.dismissModal(); + + // When hasCompletedGuidedSetupFlow is true, OnboardingModalNavigator in AuthScreen is removed from the navigation stack. + // On small screens, this removal redirects navigation to HOME. Dismissing the modal doesn't work properly, + // so we need to specifically navigate to the last accessed report. + if (!isSmallScreenWidth) { + return; + } + + const lastAccessedReport = ReportUtils.findLastAccessedReport(!canUseDefaultRooms, shouldOpenOnAdminRoom(), activeWorkspaceID); + const lastAccessedReportID = lastAccessedReport?.reportID; + if (!lastAccessedReportID || lastAccessedReport.policyID === onboardingPolicyID) { + if (!backTo) { + Report.navigateToConciergeChat(); + } + return; + } + + const lastAccessedReportRoute = ROUTES.REPORT_WITH_ID.getRoute(lastAccessedReportID ?? '-1'); + Navigation.navigate(lastAccessedReportRoute); +}; + +export default navigateAfterOnboarding; diff --git a/src/pages/OnboardingAccounting/BaseOnboardingAccounting.tsx b/src/pages/OnboardingAccounting/BaseOnboardingAccounting.tsx index 0530d618d66..537abfeb870 100644 --- a/src/pages/OnboardingAccounting/BaseOnboardingAccounting.tsx +++ b/src/pages/OnboardingAccounting/BaseOnboardingAccounting.tsx @@ -10,12 +10,14 @@ import SelectionList from '@components/SelectionList'; import RadioListItem from '@components/SelectionList/RadioListItem'; import type {ListItem} from '@components/SelectionList/types'; import Text from '@components/Text'; +import useActiveWorkspace from '@hooks/useActiveWorkspace'; import useLocalize from '@hooks/useLocalize'; +import usePermissions from '@hooks/usePermissions'; import useResponsiveLayout from '@hooks/useResponsiveLayout'; import useStyleUtils from '@hooks/useStyleUtils'; import useTheme from '@hooks/useTheme'; import useThemeStyles from '@hooks/useThemeStyles'; -import Navigation from '@libs/Navigation/Navigation'; +import navigateAfterOnboarding from '@libs/navigateAfterOnboarding'; import variables from '@styles/variables'; import * as Report from '@userActions/Report'; import * as Welcome from '@userActions/Welcome'; @@ -35,11 +37,13 @@ function BaseOnboardingAccounting({shouldUseNativeStyles, route}: BaseOnboarding const theme = useTheme(); const StyleUtils = useStyleUtils(); const {translate} = useLocalize(); - const {onboardingIsMediumOrLargerScreenWidth, shouldUseNarrowLayout} = useResponsiveLayout(); + const {onboardingIsMediumOrLargerScreenWidth, isSmallScreenWidth} = useResponsiveLayout(); const [onboardingPurposeSelected] = useOnyx(ONYXKEYS.ONBOARDING_PURPOSE_SELECTED); const [onboardingPolicyID] = useOnyx(ONYXKEYS.ONBOARDING_POLICY_ID); const [onboardingAdminsChatReportID] = useOnyx(ONYXKEYS.ONBOARDING_ADMINS_CHAT_REPORT_ID); const [onboardingCompanySize] = useOnyx(ONYXKEYS.ONBOARDING_COMPANY_SIZE); + const {canUseDefaultRooms} = usePermissions(); + const {activeWorkspaceID} = useActiveWorkspace(); const [userReportedIntegration, setUserReportedIntegration] = useState(undefined); const [error, setError] = useState(''); @@ -144,13 +148,7 @@ function BaseOnboardingAccounting({shouldUseNativeStyles, route}: BaseOnboarding Welcome.setOnboardingAdminsChatReportID(); Welcome.setOnboardingPolicyID(); - Navigation.dismissModal(); - - // Only navigate to concierge chat when central pane is visible - // Otherwise stay on the chats screen. - if (!shouldUseNarrowLayout && !route.params?.backTo) { - Report.navigateToConciergeChat(); - } + navigateAfterOnboarding(isSmallScreenWidth, canUseDefaultRooms, onboardingPolicyID, activeWorkspaceID, route.params?.backTo); }} pressOnEnter /> diff --git a/src/pages/OnboardingPersonalDetails/BaseOnboardingPersonalDetails.tsx b/src/pages/OnboardingPersonalDetails/BaseOnboardingPersonalDetails.tsx index 91d80c865db..72fa8d3eee3 100644 --- a/src/pages/OnboardingPersonalDetails/BaseOnboardingPersonalDetails.tsx +++ b/src/pages/OnboardingPersonalDetails/BaseOnboardingPersonalDetails.tsx @@ -18,9 +18,7 @@ import usePermissions from '@hooks/usePermissions'; import useResponsiveLayout from '@hooks/useResponsiveLayout'; import useThemeStyles from '@hooks/useThemeStyles'; import * as ErrorUtils from '@libs/ErrorUtils'; -import Navigation from '@libs/Navigation/Navigation'; -import shouldOpenOnAdminRoom from '@libs/Navigation/shouldOpenOnAdminRoom'; -import * as ReportUtils from '@libs/ReportUtils'; +import navigateAfterOnboarding from '@libs/navigateAfterOnboarding'; import * as ValidationUtils from '@libs/ValidationUtils'; import * as PersonalDetails from '@userActions/PersonalDetails'; import * as Report from '@userActions/Report'; @@ -28,11 +26,10 @@ import * as Welcome from '@userActions/Welcome'; import * as OnboardingFlow from '@userActions/Welcome/OnboardingFlow'; import CONST from '@src/CONST'; import ONYXKEYS from '@src/ONYXKEYS'; -import ROUTES from '@src/ROUTES'; import INPUT_IDS from '@src/types/form/DisplayNameForm'; import type {BaseOnboardingPersonalDetailsProps} from './types'; -function BaseOnboardingPersonalDetails({currentUserPersonalDetails, shouldUseNativeStyles}: BaseOnboardingPersonalDetailsProps) { +function BaseOnboardingPersonalDetails({currentUserPersonalDetails, shouldUseNativeStyles, route}: BaseOnboardingPersonalDetailsProps) { const styles = useThemeStyles(); const {translate} = useLocalize(); const [onboardingPurposeSelected] = useOnyx(ONYXKEYS.ONBOARDING_PURPOSE_SELECTED); @@ -72,22 +69,9 @@ function BaseOnboardingPersonalDetails({currentUserPersonalDetails, shouldUseNat Welcome.setOnboardingAdminsChatReportID(); Welcome.setOnboardingPolicyID(); - Navigation.dismissModal(); - - // When hasCompletedGuidedSetupFlow is true, OnboardingModalNavigator in AuthScreen is removed from the navigation stack. - // On small screens, this removal redirects navigation to HOME. Dismissing the modal doesn't work properly, - // so we need to specifically navigate to the last accessed report. - if (!isSmallScreenWidth) { - return; - } - const lastAccessedReportID = ReportUtils.findLastAccessedReport(!canUseDefaultRooms, shouldOpenOnAdminRoom(), activeWorkspaceID)?.reportID; - if (!lastAccessedReportID) { - return; - } - const lastAccessedReportRoute = ROUTES.REPORT_WITH_ID.getRoute(lastAccessedReportID ?? '-1'); - Navigation.navigate(lastAccessedReportRoute); + navigateAfterOnboarding(isSmallScreenWidth, canUseDefaultRooms, onboardingPolicyID, activeWorkspaceID, route.params?.backTo); }, - [onboardingPurposeSelected, onboardingAdminsChatReportID, onboardingPolicyID, activeWorkspaceID, canUseDefaultRooms, isSmallScreenWidth], + [onboardingPurposeSelected, onboardingAdminsChatReportID, onboardingPolicyID, route.params?.backTo, activeWorkspaceID, canUseDefaultRooms, isSmallScreenWidth], ); const validate = (values: FormOnyxValues<'onboardingPersonalDetailsForm'>) => { From 61bca5f26476276ab09f482b76d4623fc464de33 Mon Sep 17 00:00:00 2001 From: Wildan Muhlis Date: Sat, 12 Oct 2024 11:04:05 +0700 Subject: [PATCH 084/121] Keep previous navigation logic if we can't find desired last accessed report --- src/libs/navigateAfterOnboarding.ts | 22 ++++++++++++++----- .../BaseOnboardingAccounting.tsx | 4 ++-- .../BaseOnboardingPersonalDetails.tsx | 6 ++--- 3 files changed, 21 insertions(+), 11 deletions(-) diff --git a/src/libs/navigateAfterOnboarding.ts b/src/libs/navigateAfterOnboarding.ts index c6751f39ba1..d84927988b5 100644 --- a/src/libs/navigateAfterOnboarding.ts +++ b/src/libs/navigateAfterOnboarding.ts @@ -1,10 +1,17 @@ -import shouldOpenOnAdminRoom from '@libs/Navigation/shouldOpenOnAdminRoom'; -import * as ReportUtils from '@libs/ReportUtils'; -import Navigation from '@navigation/Navigation'; -import * as Report from '@userActions/Report'; import ROUTES from '@src/ROUTES'; +import * as Report from './actions/Report'; +import Navigation from './Navigation/Navigation'; +import shouldOpenOnAdminRoom from './Navigation/shouldOpenOnAdminRoom'; +import * as ReportUtils from './ReportUtils'; -const navigateAfterOnboarding = (isSmallScreenWidth: boolean, canUseDefaultRooms: boolean | undefined, onboardingPolicyID?: string, activeWorkspaceID?: string, backTo?: string) => { +const navigateAfterOnboarding = ( + isSmallScreenWidth: boolean, + shouldUseNarrowLayout: boolean, + canUseDefaultRooms: boolean | undefined, + onboardingPolicyID?: string, + activeWorkspaceID?: string, + backTo?: string, +) => { Navigation.dismissModal(); // When hasCompletedGuidedSetupFlow is true, OnboardingModalNavigator in AuthScreen is removed from the navigation stack. @@ -16,8 +23,11 @@ const navigateAfterOnboarding = (isSmallScreenWidth: boolean, canUseDefaultRooms const lastAccessedReport = ReportUtils.findLastAccessedReport(!canUseDefaultRooms, shouldOpenOnAdminRoom(), activeWorkspaceID); const lastAccessedReportID = lastAccessedReport?.reportID; + // we don't want to navigate to newly creaded workspace after onboarding completed. if (!lastAccessedReportID || lastAccessedReport.policyID === onboardingPolicyID) { - if (!backTo) { + // Only navigate to concierge chat when central pane is visible + // Otherwise stay on the chats screen. + if (!shouldUseNarrowLayout && !backTo) { Report.navigateToConciergeChat(); } return; diff --git a/src/pages/OnboardingAccounting/BaseOnboardingAccounting.tsx b/src/pages/OnboardingAccounting/BaseOnboardingAccounting.tsx index 537abfeb870..ad7e5d38698 100644 --- a/src/pages/OnboardingAccounting/BaseOnboardingAccounting.tsx +++ b/src/pages/OnboardingAccounting/BaseOnboardingAccounting.tsx @@ -37,7 +37,7 @@ function BaseOnboardingAccounting({shouldUseNativeStyles, route}: BaseOnboarding const theme = useTheme(); const StyleUtils = useStyleUtils(); const {translate} = useLocalize(); - const {onboardingIsMediumOrLargerScreenWidth, isSmallScreenWidth} = useResponsiveLayout(); + const {onboardingIsMediumOrLargerScreenWidth, isSmallScreenWidth, shouldUseNarrowLayout} = useResponsiveLayout(); const [onboardingPurposeSelected] = useOnyx(ONYXKEYS.ONBOARDING_PURPOSE_SELECTED); const [onboardingPolicyID] = useOnyx(ONYXKEYS.ONBOARDING_POLICY_ID); const [onboardingAdminsChatReportID] = useOnyx(ONYXKEYS.ONBOARDING_ADMINS_CHAT_REPORT_ID); @@ -148,7 +148,7 @@ function BaseOnboardingAccounting({shouldUseNativeStyles, route}: BaseOnboarding Welcome.setOnboardingAdminsChatReportID(); Welcome.setOnboardingPolicyID(); - navigateAfterOnboarding(isSmallScreenWidth, canUseDefaultRooms, onboardingPolicyID, activeWorkspaceID, route.params?.backTo); + navigateAfterOnboarding(isSmallScreenWidth, shouldUseNarrowLayout, canUseDefaultRooms, onboardingPolicyID, activeWorkspaceID, route.params?.backTo); }} pressOnEnter /> diff --git a/src/pages/OnboardingPersonalDetails/BaseOnboardingPersonalDetails.tsx b/src/pages/OnboardingPersonalDetails/BaseOnboardingPersonalDetails.tsx index 72fa8d3eee3..f1c79d7aa76 100644 --- a/src/pages/OnboardingPersonalDetails/BaseOnboardingPersonalDetails.tsx +++ b/src/pages/OnboardingPersonalDetails/BaseOnboardingPersonalDetails.tsx @@ -35,7 +35,7 @@ function BaseOnboardingPersonalDetails({currentUserPersonalDetails, shouldUseNat const [onboardingPurposeSelected] = useOnyx(ONYXKEYS.ONBOARDING_PURPOSE_SELECTED); const [onboardingPolicyID] = useOnyx(ONYXKEYS.ONBOARDING_POLICY_ID); const [onboardingAdminsChatReportID] = useOnyx(ONYXKEYS.ONBOARDING_ADMINS_CHAT_REPORT_ID); - const {isSmallScreenWidth, onboardingIsMediumOrLargerScreenWidth} = useResponsiveLayout(); + const {onboardingIsMediumOrLargerScreenWidth, isSmallScreenWidth, shouldUseNarrowLayout} = useResponsiveLayout(); const {inputCallbackRef} = useAutoFocusInput(); const [shouldValidateOnChange, setShouldValidateOnChange] = useState(false); const {isOffline} = useNetwork(); @@ -69,9 +69,9 @@ function BaseOnboardingPersonalDetails({currentUserPersonalDetails, shouldUseNat Welcome.setOnboardingAdminsChatReportID(); Welcome.setOnboardingPolicyID(); - navigateAfterOnboarding(isSmallScreenWidth, canUseDefaultRooms, onboardingPolicyID, activeWorkspaceID, route.params?.backTo); + navigateAfterOnboarding(isSmallScreenWidth, shouldUseNarrowLayout, canUseDefaultRooms, onboardingPolicyID, activeWorkspaceID, route.params?.backTo); }, - [onboardingPurposeSelected, onboardingAdminsChatReportID, onboardingPolicyID, route.params?.backTo, activeWorkspaceID, canUseDefaultRooms, isSmallScreenWidth], + [onboardingPurposeSelected, onboardingAdminsChatReportID, onboardingPolicyID, route.params?.backTo, activeWorkspaceID, canUseDefaultRooms, isSmallScreenWidth, shouldUseNarrowLayout], ); const validate = (values: FormOnyxValues<'onboardingPersonalDetailsForm'>) => { From d6c5c0186e413018e5c726b74b58f9c7331e4640 Mon Sep 17 00:00:00 2001 From: Bernhard Owen Josephus Date: Sat, 12 Oct 2024 21:29:28 +0800 Subject: [PATCH 085/121] fix the focus outline isn't circle --- src/components/RadioButton.tsx | 20 +++++++++----------- src/pages/NewChatPage.tsx | 7 +++++-- 2 files changed, 14 insertions(+), 13 deletions(-) diff --git a/src/components/RadioButton.tsx b/src/components/RadioButton.tsx index 2d18ccb480b..0bf7e370e48 100644 --- a/src/components/RadioButton.tsx +++ b/src/components/RadioButton.tsx @@ -1,5 +1,4 @@ import React from 'react'; -import {View} from 'react-native'; import useTheme from '@hooks/useTheme'; import useThemeStyles from '@hooks/useThemeStyles'; import CONST from '@src/CONST'; @@ -36,17 +35,16 @@ function RadioButton({isChecked, onPress, accessibilityLabel, hasError = false, pressDimmingValue={1} accessibilityLabel={accessibilityLabel} role={CONST.ROLE.RADIO} + style={[styles.radioButtonContainer, hasError && styles.borderColorDanger, disabled && styles.cursorDisabled]} > - - {isChecked && ( - - )} - + {isChecked && ( + + )} ); } diff --git a/src/pages/NewChatPage.tsx b/src/pages/NewChatPage.tsx index 527c33bd08e..c406f7f3058 100755 --- a/src/pages/NewChatPage.tsx +++ b/src/pages/NewChatPage.tsx @@ -259,9 +259,12 @@ function NewChatPage({isGroupChat}: NewChatPageProps) { disabled={item.isDisabled} role={CONST.ROLE.BUTTON} accessibilityLabel={CONST.ROLE.BUTTON} - style={[styles.flexRow, styles.alignItemsCenter, styles.ml3]} + style={[styles.flexRow, styles.alignItemsCenter, styles.ml5, styles.optionSelectCircle]} > - + ); } From 927a4009419837bf927f62d3fb06add2c49a03ef Mon Sep 17 00:00:00 2001 From: krishna2323 Date: Sun, 13 Oct 2024 15:44:01 +0530 Subject: [PATCH 086/121] minor update. Signed-off-by: krishna2323 --- src/pages/workspace/AccessOrNotFoundWrapper.tsx | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/pages/workspace/AccessOrNotFoundWrapper.tsx b/src/pages/workspace/AccessOrNotFoundWrapper.tsx index e5d42eb612d..b83c703fbe7 100644 --- a/src/pages/workspace/AccessOrNotFoundWrapper.tsx +++ b/src/pages/workspace/AccessOrNotFoundWrapper.tsx @@ -100,7 +100,7 @@ function PageNotFoundFallback({policyID, shouldShowFullScreenFallback, fullPageN }} // eslint-disable-next-line react/jsx-props-no-spreading {...fullPageNotFoundViewProps} - shouldShowBackButton={!shouldShowFullScreenFallback ? shouldUseNarrowLayout : undefined} + shouldShowBackButton={fullPageNotFoundViewProps?.shouldShowBackButton ?? (!shouldShowFullScreenFallback ? shouldUseNarrowLayout : undefined)} /> ); } From 4cba8173254fba4496e632ed74fa85555e04deea Mon Sep 17 00:00:00 2001 From: daledah Date: Mon, 14 Oct 2024 09:00:39 +0700 Subject: [PATCH 087/121] fix: move logics to util function --- src/libs/ReportUtils.ts | 11 +++++++++-- src/libs/actions/Policy/Policy.ts | 4 +--- 2 files changed, 10 insertions(+), 5 deletions(-) diff --git a/src/libs/ReportUtils.ts b/src/libs/ReportUtils.ts index e9108956cb1..e54bfbb3c7d 100644 --- a/src/libs/ReportUtils.ts +++ b/src/libs/ReportUtils.ts @@ -1251,13 +1251,19 @@ function findSelfDMReportID(): string | undefined { return selfDMReport?.reportID; } +function isPolicyRelatedReport(report: OnyxEntry, policyID?: string) { + return report?.policyID === policyID || !!(report?.invoiceReceiver && 'policyID' in report.invoiceReceiver && report.invoiceReceiver.policyID === policyID); +} + /** * Checks if the supplied report belongs to workspace based on the provided params. If the report's policyID is _FAKE_ or has no value, it means this report is a DM. * In this case report and workspace members must be compared to determine whether the report belongs to the workspace. */ function doesReportBelongToWorkspace(report: OnyxEntry, policyMemberAccountIDs: number[], policyID?: string) { - const isPolicyRelatedReport = report?.policyID === policyID || !!(report?.invoiceReceiver && 'policyID' in report.invoiceReceiver && report.invoiceReceiver.policyID === policyID); - return isConciergeChatReport(report) || (report?.policyID === CONST.POLICY.ID_FAKE || !report?.policyID ? hasParticipantInArray(report, policyMemberAccountIDs) : isPolicyRelatedReport); + return ( + isConciergeChatReport(report) || + (report?.policyID === CONST.POLICY.ID_FAKE || !report?.policyID ? hasParticipantInArray(report, policyMemberAccountIDs) : isPolicyRelatedReport(report, policyID)) + ); } /** @@ -8421,6 +8427,7 @@ export { hasMissingInvoiceBankAccount, reasonForReportToBeInOptionList, getReasonAndReportActionThatRequiresAttention, + isPolicyRelatedReport, }; export type { diff --git a/src/libs/actions/Policy/Policy.ts b/src/libs/actions/Policy/Policy.ts index 788ee2ed5d6..d13a2432305 100644 --- a/src/libs/actions/Policy/Policy.ts +++ b/src/libs/actions/Policy/Policy.ts @@ -310,9 +310,7 @@ function deleteWorkspace(policyID: string, policyName: string) { ]; const reportsToArchive = Object.values(ReportConnection.getAllReports() ?? {}).filter( - (report) => - (report?.policyID === policyID || (report?.invoiceReceiver && 'policyID' in report.invoiceReceiver && report.invoiceReceiver.policyID === policyID)) && - (ReportUtils.isChatRoom(report) || ReportUtils.isPolicyExpenseChat(report) || ReportUtils.isTaskReport(report)), + (report) => ReportUtils.isPolicyRelatedReport(report, policyID) && (ReportUtils.isChatRoom(report) || ReportUtils.isPolicyExpenseChat(report) || ReportUtils.isTaskReport(report)), ); const finallyData: OnyxUpdate[] = []; const currentTime = DateUtils.getDBTime(); From b10e7039e0de73257c8166ebb802172bff6b4eac Mon Sep 17 00:00:00 2001 From: daledah Date: Mon, 14 Oct 2024 10:55:28 +0700 Subject: [PATCH 088/121] feat: add description to function --- src/libs/ReportUtils.ts | 3 +++ 1 file changed, 3 insertions(+) diff --git a/src/libs/ReportUtils.ts b/src/libs/ReportUtils.ts index e54bfbb3c7d..a7b3392d667 100644 --- a/src/libs/ReportUtils.ts +++ b/src/libs/ReportUtils.ts @@ -1251,6 +1251,9 @@ function findSelfDMReportID(): string | undefined { return selfDMReport?.reportID; } +/** + * Checks if the supplied report is from a policy or is an invoice report from a policy + */ function isPolicyRelatedReport(report: OnyxEntry, policyID?: string) { return report?.policyID === policyID || !!(report?.invoiceReceiver && 'policyID' in report.invoiceReceiver && report.invoiceReceiver.policyID === policyID); } From 01a9daad5c2b56c5461cc6eaccd7bb6b6aaf7aef Mon Sep 17 00:00:00 2001 From: nkdengineer Date: Mon, 14 Oct 2024 14:46:50 +0700 Subject: [PATCH 089/121] fix: add default value to isFromReviewDuplicates --- src/ROUTES.ts | 2 +- src/components/ReportActionItem/MoneyRequestView.tsx | 2 +- src/components/ReportActionItem/ReportActionItemImage.tsx | 2 +- 3 files changed, 3 insertions(+), 3 deletions(-) diff --git a/src/ROUTES.ts b/src/ROUTES.ts index de675fa8b3b..c318ff8834d 100644 --- a/src/ROUTES.ts +++ b/src/ROUTES.ts @@ -1177,7 +1177,7 @@ const ROUTES = { TRANSACTION_RECEIPT: { route: 'r/:reportID/transaction/:transactionID/receipt', getRoute: (reportID: string, transactionID: string, readonly = false, isFromReviewDuplicates = false) => - `r/${reportID}/transaction/${transactionID}/receipt${readonly ? '?readonly=true' : ''}${isFromReviewDuplicates ? '&isFromReviewDuplicates=true' : ''}` as const, + `r/${reportID}/transaction/${transactionID}/receipt?readonly=${readonly}${isFromReviewDuplicates ? '&isFromReviewDuplicates=true' : ''}` as const, }, TRANSACTION_DUPLICATE_REVIEW_PAGE: { diff --git a/src/components/ReportActionItem/MoneyRequestView.tsx b/src/components/ReportActionItem/MoneyRequestView.tsx index 4af17b194d2..2d4f44c35c9 100644 --- a/src/components/ReportActionItem/MoneyRequestView.tsx +++ b/src/components/ReportActionItem/MoneyRequestView.tsx @@ -77,7 +77,7 @@ const getTransactionID = (report: OnyxEntry, parentReportActio return originalMessage?.IOUTransactionID ?? -1; }; -function MoneyRequestView({report, shouldShowAnimatedBackground, readonly = false, updatedTransaction, isFromReviewDuplicates}: MoneyRequestViewProps) { +function MoneyRequestView({report, shouldShowAnimatedBackground, readonly = false, updatedTransaction, isFromReviewDuplicates = false}: MoneyRequestViewProps) { const theme = useTheme(); const styles = useThemeStyles(); const session = useSession(); diff --git a/src/components/ReportActionItem/ReportActionItemImage.tsx b/src/components/ReportActionItem/ReportActionItemImage.tsx index c140222517c..794902b6785 100644 --- a/src/components/ReportActionItem/ReportActionItemImage.tsx +++ b/src/components/ReportActionItem/ReportActionItemImage.tsx @@ -77,7 +77,7 @@ function ReportActionItemImage({ isSingleImage = true, readonly = false, shouldMapHaveBorderRadius, - isFromReviewDuplicates, + isFromReviewDuplicates = false, }: ReportActionItemImageProps) { const styles = useThemeStyles(); const {translate} = useLocalize(); From 96f59359c35f3d5f2e4cf7f237ee64328e76db20 Mon Sep 17 00:00:00 2001 From: Pedro Guerreiro Date: Mon, 14 Oct 2024 10:34:46 +0100 Subject: [PATCH 090/121] refactor: apply suggestions --- src/libs/DebugUtils.ts | 17 ++--------------- src/pages/Debug/Report/DebugReportPage.tsx | 6 +++--- 2 files changed, 5 insertions(+), 18 deletions(-) diff --git a/src/libs/DebugUtils.ts b/src/libs/DebugUtils.ts index 261d2db208d..fee35c50e0d 100644 --- a/src/libs/DebugUtils.ts +++ b/src/libs/DebugUtils.ts @@ -7,7 +7,6 @@ import type {TranslationPaths} from '@src/languages/types'; import ONYXKEYS from '@src/ONYXKEYS'; import type {Beta, Policy, Report, ReportAction, ReportActions, TransactionViolation} from '@src/types/onyx'; import * as ReportUtils from './ReportUtils'; -import SidebarUtils from './SidebarUtils'; class NumberError extends SyntaxError { constructor() { @@ -128,15 +127,6 @@ Onyx.connect({ }, }); -let reportActionsCollection: OnyxCollection; -Onyx.connect({ - key: ONYXKEYS.COLLECTION.REPORT_ACTIONS, - waitForCollectionCallback: true, - callback: (value) => { - reportActionsCollection = value; - }, -}); - let transactionViolations: OnyxCollection; Onyx.connect({ key: ONYXKEYS.COLLECTION.TRANSACTION_VIOLATIONS, @@ -601,7 +591,7 @@ function validateReportActionJSON(json: string) { /** * Gets the reason for showing LHN row */ -function getReasonForShowingRowInLHN(report: OnyxEntry): TranslationPaths | null { +function getReasonForShowingRowInLHN(report: OnyxEntry, hasRBR: boolean): TranslationPaths | null { if (!report) { return null; } @@ -620,10 +610,7 @@ function getReasonForShowingRowInLHN(report: OnyxEntry): TranslationPath includeSelfDM: true, }); - if ( - !([CONST.REPORT_IN_LHN_REASONS.HAS_ADD_WORKSPACE_ROOM_ERRORS, CONST.REPORT_IN_LHN_REASONS.HAS_IOU_VIOLATIONS] as Array).includes(reason) && - SidebarUtils.shouldShowRedBrickRoad(report, reportActionsCollection?.[`${ONYXKEYS.COLLECTION.REPORT_ACTIONS}${report?.reportID}`], doesReportHaveViolations, transactionViolations) - ) { + if (!([CONST.REPORT_IN_LHN_REASONS.HAS_ADD_WORKSPACE_ROOM_ERRORS, CONST.REPORT_IN_LHN_REASONS.HAS_IOU_VIOLATIONS] as Array).includes(reason) && hasRBR) { return `debug.reasonVisibleInLHN.hasRBR`; } diff --git a/src/pages/Debug/Report/DebugReportPage.tsx b/src/pages/Debug/Report/DebugReportPage.tsx index 530b4b5f4ae..28f4ddf3dc3 100644 --- a/src/pages/Debug/Report/DebugReportPage.tsx +++ b/src/pages/Debug/Report/DebugReportPage.tsx @@ -59,12 +59,12 @@ function DebugReportPage({ return []; } - const reasonLHN = DebugUtils.getReasonForShowingRowInLHN(report); - const {reason: reasonGBR, reportAction: reportActionGBR} = DebugUtils.getReasonAndReportActionForGBRInLHNRow(report) ?? {}; - const reportActionRBR = DebugUtils.getRBRReportAction(report, reportActions); const shouldDisplayViolations = ReportUtils.shouldDisplayTransactionThreadViolations(report, transactionViolations, parentReportAction); const shouldDisplayReportViolations = ReportUtils.isReportOwner(report) && ReportUtils.hasReportViolations(reportID); const hasRBR = SidebarUtils.shouldShowRedBrickRoad(report, reportActions, !!shouldDisplayViolations || shouldDisplayReportViolations, transactionViolations); + const reasonLHN = DebugUtils.getReasonForShowingRowInLHN(report, hasRBR); + const {reason: reasonGBR, reportAction: reportActionGBR} = DebugUtils.getReasonAndReportActionForGBRInLHNRow(report) ?? {}; + const reportActionRBR = DebugUtils.getRBRReportAction(report, reportActions); const hasGBR = !hasRBR && !!reasonGBR; return [ From 212f461eeba8e82d7256a7c15b133f7b1b199f37 Mon Sep 17 00:00:00 2001 From: Pedro Guerreiro Date: Mon, 14 Oct 2024 10:52:04 +0100 Subject: [PATCH 091/121] fix: debug utils hasRBR tests --- src/libs/DebugUtils.ts | 2 +- tests/unit/DebugUtilsTest.ts | 17 ++++++++++------- 2 files changed, 11 insertions(+), 8 deletions(-) diff --git a/src/libs/DebugUtils.ts b/src/libs/DebugUtils.ts index fee35c50e0d..e7ad6346778 100644 --- a/src/libs/DebugUtils.ts +++ b/src/libs/DebugUtils.ts @@ -591,7 +591,7 @@ function validateReportActionJSON(json: string) { /** * Gets the reason for showing LHN row */ -function getReasonForShowingRowInLHN(report: OnyxEntry, hasRBR: boolean): TranslationPaths | null { +function getReasonForShowingRowInLHN(report: OnyxEntry, hasRBR = false): TranslationPaths | null { if (!report) { return null; } diff --git a/tests/unit/DebugUtilsTest.ts b/tests/unit/DebugUtilsTest.ts index a87648d08ee..fa44b8972cf 100644 --- a/tests/unit/DebugUtilsTest.ts +++ b/tests/unit/DebugUtilsTest.ts @@ -838,7 +838,7 @@ describe('DebugUtils', () => { }, ], }); - const reason = DebugUtils.getReasonForShowingRowInLHN(MOCK_TRANSACTION_REPORT); + const reason = DebugUtils.getReasonForShowingRowInLHN(MOCK_TRANSACTION_REPORT, true); expect(reason).toBe('debug.reasonVisibleInLHN.hasRBR'); }); it('returns correct reason when report has violations', async () => { @@ -896,16 +896,19 @@ describe('DebugUtils', () => { }, ], }); - const reason = DebugUtils.getReasonForShowingRowInLHN(MOCK_EXPENSE_REPORT); + const reason = DebugUtils.getReasonForShowingRowInLHN(MOCK_EXPENSE_REPORT, true); expect(reason).toBe('debug.reasonVisibleInLHN.hasRBR'); }); it('returns correct reason when report has errors', () => { - const reason = DebugUtils.getReasonForShowingRowInLHN({ - ...baseReport, - errors: { - error: 'Something went wrong', + const reason = DebugUtils.getReasonForShowingRowInLHN( + { + ...baseReport, + errors: { + error: 'Something went wrong', + }, }, - }); + true, + ); expect(reason).toBe('debug.reasonVisibleInLHN.hasRBR'); }); }); From 1d0811f96a28e507bc9b48ed468251e538ede386 Mon Sep 17 00:00:00 2001 From: Jakub Szymczak Date: Mon, 14 Oct 2024 15:29:08 +0200 Subject: [PATCH 092/121] fix style bugs --- src/components/Search/SearchRouter/SearchRouterInput.tsx | 2 +- src/components/Search/SearchRouter/SearchRouterList.tsx | 4 ++-- 2 files changed, 3 insertions(+), 3 deletions(-) diff --git a/src/components/Search/SearchRouter/SearchRouterInput.tsx b/src/components/Search/SearchRouter/SearchRouterInput.tsx index e28775c3686..ee236494fcc 100644 --- a/src/components/Search/SearchRouter/SearchRouterInput.tsx +++ b/src/components/Search/SearchRouter/SearchRouterInput.tsx @@ -88,7 +88,7 @@ function SearchRouterInput({ disabled={disabled} onSubmitEditing={onSubmit} shouldUseDisabledStyles={false} - textInputContainerStyles={styles.borderNone} + textInputContainerStyles={[styles.borderNone, styles.pb0]} inputStyle={[styles.searchInputStyle, inputWidth, styles.pl3, styles.pr3]} onFocus={() => { setIsFocused(true); diff --git a/src/components/Search/SearchRouter/SearchRouterList.tsx b/src/components/Search/SearchRouter/SearchRouterList.tsx index d05588afbe9..8485d0c2eb5 100644 --- a/src/components/Search/SearchRouter/SearchRouterList.tsx +++ b/src/components/Search/SearchRouter/SearchRouterList.tsx @@ -80,7 +80,6 @@ function SearchRouterItem(props: UserListItemProps | SearchQueryList return ( @@ -145,7 +144,7 @@ function SearchRouterList( sections.push({title: translate('search.recentSearches'), data: recentSearchesData}); } - const styledRecentReports = recentReports.map((item) => ({...item, pressableStyle: styles.br2})); + const styledRecentReports = recentReports.map((item) => ({...item, pressableStyle: styles.br2, wrapperStyle: [styles.pr3, styles.pl3]})); sections.push({title: translate('search.recentChats'), data: styledRecentReports}); const onSelectRow = useCallback( @@ -182,6 +181,7 @@ function SearchRouterList( ListItem={SearchRouterItem} containerStyle={[styles.mh100]} sectionListStyle={[isSmallScreenWidth ? styles.ph5 : styles.ph2, styles.pb2]} + listItemWrapperStyle={[styles.pr3, styles.pl3]} onLayout={setPerformanceTimersEnd} ref={ref} showScrollIndicator={!isSmallScreenWidth} From 935067ed56dd45bd24edd3ce834edc2dd112bc7d Mon Sep 17 00:00:00 2001 From: Jakub Szymczak Date: Mon, 14 Oct 2024 16:03:53 +0200 Subject: [PATCH 093/121] add offline message --- src/CONST.ts | 2 +- .../Search/SearchRouter/SearchButton.tsx | 4 +- .../Search/SearchRouter/SearchRouter.tsx | 9 ++- .../Search/SearchRouter/SearchRouterInput.tsx | 77 ++++++++++++------- .../Search/SearchRouter/SearchRouterList.tsx | 5 +- .../E2E/tests/openSearchRouterTest.e2e.ts | 2 +- 6 files changed, 64 insertions(+), 35 deletions(-) diff --git a/src/CONST.ts b/src/CONST.ts index ab6d057393e..51da86fbcb7 100755 --- a/src/CONST.ts +++ b/src/CONST.ts @@ -1102,7 +1102,7 @@ const CONST = { }, TIMING: { CALCULATE_MOST_RECENT_LAST_MODIFIED_ACTION: 'calc_most_recent_last_modified_action', - SEARCH_ROUTER_OPEN: 'search_router_render', + SEARCH_ROUTER_RENDER: 'search_router_render', CHAT_RENDER: 'chat_render', OPEN_REPORT: 'open_report', HOMEPAGE_INITIAL_RENDER: 'homepage_initial_render', diff --git a/src/components/Search/SearchRouter/SearchButton.tsx b/src/components/Search/SearchRouter/SearchButton.tsx index ae755fbf2e2..7ed22ec8162 100644 --- a/src/components/Search/SearchRouter/SearchButton.tsx +++ b/src/components/Search/SearchRouter/SearchButton.tsx @@ -27,8 +27,8 @@ function SearchButton({style}: SearchButtonProps) { accessibilityLabel={translate('common.search')} style={[styles.flexRow, styles.touchableButtonImage, style]} onPress={Session.checkIfActionIsAllowed(() => { - Timing.start(CONST.TIMING.SEARCH_ROUTER_OPEN); - Performance.markStart(CONST.TIMING.SEARCH_ROUTER_OPEN); + Timing.start(CONST.TIMING.SEARCH_ROUTER_RENDER); + Performance.markStart(CONST.TIMING.SEARCH_ROUTER_RENDER); openSearchRouter(); })} diff --git a/src/components/Search/SearchRouter/SearchRouter.tsx b/src/components/Search/SearchRouter/SearchRouter.tsx index 1e6d8ff16ba..e8d613e0b6f 100644 --- a/src/components/Search/SearchRouter/SearchRouter.tsx +++ b/src/components/Search/SearchRouter/SearchRouter.tsx @@ -3,6 +3,7 @@ import debounce from 'lodash/debounce'; import React, {useCallback, useEffect, useMemo, useRef, useState} from 'react'; import {View} from 'react-native'; import {useOnyx} from 'react-native-onyx'; +import FormHelpMessage from '@components/FormHelpMessage'; import HeaderWithBackButton from '@components/HeaderWithBackButton'; import {useOptionsList} from '@components/OptionListContextProvider'; import type {SearchQueryJSON} from '@components/Search/types'; @@ -10,6 +11,7 @@ import type {SelectionListHandle} from '@components/SelectionList/types'; import useDebouncedState from '@hooks/useDebouncedState'; import useKeyboardShortcut from '@hooks/useKeyboardShortcut'; import useLocalize from '@hooks/useLocalize'; +import useNetwork from '@hooks/useNetwork'; import useResponsiveLayout from '@hooks/useResponsiveLayout'; import useThemeStyles from '@hooks/useThemeStyles'; import Log from '@libs/Log'; @@ -176,8 +178,13 @@ function SearchRouter({onRouterClose}: SearchRouterProps) { setValue={setTextInputValue} isFullWidth={isSmallScreenWidth} updateSearch={onSearchChange} + onSubmit={() => { + onSearchSubmit(SearchUtils.buildSearchQueryJSON(textInputValue)); + }} routerListRef={listRef} - wrapperStyle={[isSmallScreenWidth ? styles.mv3 : styles.mv2, isSmallScreenWidth ? styles.mh5 : styles.mh2, styles.border]} + shouldShowOfflineMessage + wrapperStyle={[styles.border, styles.alignItemsCenter]} + outerWrapperStyle={[isSmallScreenWidth ? styles.mv3 : styles.mv2, isSmallScreenWidth ? styles.mh5 : styles.mh2]} wrapperFocusedStyle={[styles.borderColorFocus]} isSearchingForReports={isSearchingForReports} /> diff --git a/src/components/Search/SearchRouter/SearchRouterInput.tsx b/src/components/Search/SearchRouter/SearchRouterInput.tsx index ee236494fcc..84ebf2edc0b 100644 --- a/src/components/Search/SearchRouter/SearchRouterInput.tsx +++ b/src/components/Search/SearchRouter/SearchRouterInput.tsx @@ -2,9 +2,11 @@ import React, {useState} from 'react'; import type {ReactNode, RefObject} from 'react'; import {View} from 'react-native'; import type {StyleProp, ViewStyle} from 'react-native'; +import FormHelpMessage from '@components/FormHelpMessage'; import type {SelectionListHandle} from '@components/SelectionList/types'; import TextInput from '@components/TextInput'; import useLocalize from '@hooks/useLocalize'; +import useNetwork from '@hooks/useNetwork'; import useThemeStyles from '@hooks/useThemeStyles'; import variables from '@styles/variables'; import CONST from '@src/CONST'; @@ -31,6 +33,9 @@ type SearchRouterInputProps = { /** Whether the input is disabled */ disabled?: boolean; + /** Whether the offline message should be shown */ + shouldShowOfflineMessage?: boolean; + /** Whether the input should be focused */ autoFocus?: boolean; @@ -40,6 +45,9 @@ type SearchRouterInputProps = { /** Any additional styles to apply when input is focused */ wrapperFocusedStyle?: StyleProp; + /** Any additional styles to apply to text input along with FormHelperMessage */ + outerWrapperStyle?: StyleProp; + /** Component to be displayed on the right */ rightComponent?: ReactNode; @@ -55,15 +63,19 @@ function SearchRouterInput({ routerListRef, isFullWidth, disabled = false, + shouldShowOfflineMessage = false, autoFocus = true, wrapperStyle, wrapperFocusedStyle, + outerWrapperStyle, rightComponent, isSearchingForReports, }: SearchRouterInputProps) { const styles = useThemeStyles(); const {translate} = useLocalize(); const [isFocused, setIsFocused] = useState(false); + const {isOffline} = useNetwork(); + const offlineMessage: string = isOffline && shouldShowOfflineMessage ? `${translate('common.youAppearToBeOffline')} ${translate('search.resultsAreLimited')}` : ''; const onChangeText = (text: string) => { setValue(text); @@ -73,35 +85,44 @@ function SearchRouterInput({ const inputWidth = isFullWidth ? styles.w100 : {width: variables.popoverWidth}; return ( - - - { - setIsFocused(true); - routerListRef?.current?.updateExternalTextInputFocus(true); - }} - onBlur={() => { - setIsFocused(false); - routerListRef?.current?.updateExternalTextInputFocus(false); - }} - isLoading={!!isSearchingForReports} - /> + + + + { + setIsFocused(true); + routerListRef?.current?.updateExternalTextInputFocus(true); + }} + onBlur={() => { + setIsFocused(false); + routerListRef?.current?.updateExternalTextInputFocus(false); + }} + isLoading={!!isSearchingForReports} + /> + + {rightComponent && {rightComponent}} - {rightComponent && {rightComponent}} + ); } diff --git a/src/components/Search/SearchRouter/SearchRouterList.tsx b/src/components/Search/SearchRouter/SearchRouterList.tsx index 8485d0c2eb5..84c300bfc89 100644 --- a/src/components/Search/SearchRouter/SearchRouterList.tsx +++ b/src/components/Search/SearchRouter/SearchRouterList.tsx @@ -51,8 +51,8 @@ type SearchRouterListProps = { }; const setPerformanceTimersEnd = () => { - Timing.end(CONST.TIMING.SEARCH_ROUTER_OPEN); - Performance.markEnd(CONST.TIMING.SEARCH_ROUTER_OPEN); + Timing.end(CONST.TIMING.SEARCH_ROUTER_RENDER); + Performance.markEnd(CONST.TIMING.SEARCH_ROUTER_RENDER); }; function isSearchQueryItem(item: OptionData | SearchQueryItem): item is SearchQueryItem { @@ -186,6 +186,7 @@ function SearchRouterList( ref={ref} showScrollIndicator={!isSmallScreenWidth} sectionTitleStyles={styles.mhn2} + shouldSingleExecuteRowSelect /> ); } diff --git a/src/libs/E2E/tests/openSearchRouterTest.e2e.ts b/src/libs/E2E/tests/openSearchRouterTest.e2e.ts index 6c4646b09fb..840af5acc2c 100644 --- a/src/libs/E2E/tests/openSearchRouterTest.e2e.ts +++ b/src/libs/E2E/tests/openSearchRouterTest.e2e.ts @@ -32,7 +32,7 @@ const test = () => { Performance.subscribeToMeasurements((entry) => { console.debug(`[E2E] Entry: ${JSON.stringify(entry)}`); - if (entry.name === CONST.TIMING.SEARCH_ROUTER_OPEN) { + if (entry.name === CONST.TIMING.SEARCH_ROUTER_RENDER) { E2EClient.submitTestResults({ branch: Config.E2E_BRANCH, name: 'Open Search Router TTI', From a6a74c5562f34291ebd5361297dd3048950966c4 Mon Sep 17 00:00:00 2001 From: Jakub Szymczak Date: Mon, 14 Oct 2024 16:33:04 +0200 Subject: [PATCH 094/121] fix linter --- src/components/Search/SearchRouter/SearchRouter.tsx | 2 -- src/components/Search/SearchRouter/SearchRouterInput.tsx | 1 + 2 files changed, 1 insertion(+), 2 deletions(-) diff --git a/src/components/Search/SearchRouter/SearchRouter.tsx b/src/components/Search/SearchRouter/SearchRouter.tsx index e8d613e0b6f..a957806ee4f 100644 --- a/src/components/Search/SearchRouter/SearchRouter.tsx +++ b/src/components/Search/SearchRouter/SearchRouter.tsx @@ -3,7 +3,6 @@ import debounce from 'lodash/debounce'; import React, {useCallback, useEffect, useMemo, useRef, useState} from 'react'; import {View} from 'react-native'; import {useOnyx} from 'react-native-onyx'; -import FormHelpMessage from '@components/FormHelpMessage'; import HeaderWithBackButton from '@components/HeaderWithBackButton'; import {useOptionsList} from '@components/OptionListContextProvider'; import type {SearchQueryJSON} from '@components/Search/types'; @@ -11,7 +10,6 @@ import type {SelectionListHandle} from '@components/SelectionList/types'; import useDebouncedState from '@hooks/useDebouncedState'; import useKeyboardShortcut from '@hooks/useKeyboardShortcut'; import useLocalize from '@hooks/useLocalize'; -import useNetwork from '@hooks/useNetwork'; import useResponsiveLayout from '@hooks/useResponsiveLayout'; import useThemeStyles from '@hooks/useThemeStyles'; import Log from '@libs/Log'; diff --git a/src/components/Search/SearchRouter/SearchRouterInput.tsx b/src/components/Search/SearchRouter/SearchRouterInput.tsx index 84ebf2edc0b..ef6963152c4 100644 --- a/src/components/Search/SearchRouter/SearchRouterInput.tsx +++ b/src/components/Search/SearchRouter/SearchRouterInput.tsx @@ -120,6 +120,7 @@ function SearchRouterInput({ {rightComponent && {rightComponent}} From ef00285e769cea5002463e4d676b9cbf8d6397ad Mon Sep 17 00:00:00 2001 From: nkdengineer Date: Tue, 15 Oct 2024 11:17:10 +0700 Subject: [PATCH 095/121] add comment --- src/components/ReportActionItem/MoneyRequestView.tsx | 1 + src/components/ReportActionItem/ReportActionItemImage.tsx | 1 + 2 files changed, 2 insertions(+) diff --git a/src/components/ReportActionItem/MoneyRequestView.tsx b/src/components/ReportActionItem/MoneyRequestView.tsx index 2d4f44c35c9..5dc56caa0ff 100644 --- a/src/components/ReportActionItem/MoneyRequestView.tsx +++ b/src/components/ReportActionItem/MoneyRequestView.tsx @@ -56,6 +56,7 @@ type MoneyRequestViewProps = { /** Whether we should show Money Request with disabled all fields */ readonly?: boolean; + /** whether or not this report is from review duplicates */ isFromReviewDuplicates?: boolean; /** Updated transaction to show in duplicate transaction flow */ diff --git a/src/components/ReportActionItem/ReportActionItemImage.tsx b/src/components/ReportActionItem/ReportActionItemImage.tsx index 794902b6785..668338440f7 100644 --- a/src/components/ReportActionItem/ReportActionItemImage.tsx +++ b/src/components/ReportActionItem/ReportActionItemImage.tsx @@ -56,6 +56,7 @@ type ReportActionItemImageProps = { /** Whether the receipt is not editable */ readonly?: boolean; + /** whether or not this report is from review duplicates */ isFromReviewDuplicates?: boolean; }; From ce6861c21765eaf94fecbb9cd849632f2bd77dac Mon Sep 17 00:00:00 2001 From: Bernhard Owen Josephus Date: Tue, 15 Oct 2024 13:21:59 +0800 Subject: [PATCH 096/121] fix page flickers --- src/libs/Navigation/OnyxTabNavigator.tsx | 8 ++++++-- 1 file changed, 6 insertions(+), 2 deletions(-) diff --git a/src/libs/Navigation/OnyxTabNavigator.tsx b/src/libs/Navigation/OnyxTabNavigator.tsx index a6a77403f14..0e7dfd4a0a0 100644 --- a/src/libs/Navigation/OnyxTabNavigator.tsx +++ b/src/libs/Navigation/OnyxTabNavigator.tsx @@ -123,8 +123,12 @@ function OnyxTabNavigator({ const state = event.data.state; const index = state.index; const routeNames = state.routeNames; - Tab.setSelectedTab(id, routeNames.at(index) as SelectedTabRequest); - onTabSelected(routeNames.at(index) as IOURequestType); + const newSelectedTab = routeNames.at(index); + if (selectedTab === newSelectedTab) { + return; + } + Tab.setSelectedTab(id, newSelectedTab as SelectedTabRequest); + onTabSelected(newSelectedTab as IOURequestType); }, ...(screenListeners ?? {}), }} From 28cf9ad4e5738762185137877be52608f1c2754d Mon Sep 17 00:00:00 2001 From: HezekielT Date: Tue, 15 Oct 2024 11:10:34 +0300 Subject: [PATCH 097/121] added a comment --- src/components/Attachments/AttachmentView/index.tsx | 1 + 1 file changed, 1 insertion(+) diff --git a/src/components/Attachments/AttachmentView/index.tsx b/src/components/Attachments/AttachmentView/index.tsx index 132f0affc23..080e0ec589e 100644 --- a/src/components/Attachments/AttachmentView/index.tsx +++ b/src/components/Attachments/AttachmentView/index.tsx @@ -36,6 +36,7 @@ type AttachmentViewProps = Attachment & { /** Function for handle on press */ onPress?: (e?: GestureResponderEvent | KeyboardEvent) => void; + /** Whether the attachment is used in attachment modal */ isUsedInAttachmentModal?: boolean; /** Flag to show/hide download icon */ From 303c6acb4e86ed192f3f618e6a0dc3296f218e84 Mon Sep 17 00:00:00 2001 From: Tsaqif Date: Tue, 15 Oct 2024 16:35:48 +0700 Subject: [PATCH 098/121] Fix some text inputs are unaligned if there are prefix characters Signed-off-by: Tsaqif --- src/components/TextInput/BaseTextInput/index.native.tsx | 1 + src/components/TextInput/BaseTextInput/index.tsx | 1 + src/styles/index.ts | 3 +++ 3 files changed, 5 insertions(+) diff --git a/src/components/TextInput/BaseTextInput/index.native.tsx b/src/components/TextInput/BaseTextInput/index.native.tsx index 871ab8b25ac..e0c7f1c9da9 100644 --- a/src/components/TextInput/BaseTextInput/index.native.tsx +++ b/src/components/TextInput/BaseTextInput/index.native.tsx @@ -259,6 +259,7 @@ function BaseTextInput( !hideFocusedState && isFocused && styles.borderColorFocus, (!!hasError || !!errorText) && styles.borderColorDanger, autoGrowHeight && {scrollPaddingTop: typeof maxAutoGrowHeight === 'number' ? 2 * maxAutoGrowHeight : undefined}, + isAutoGrowHeightMarkdown && styles.autoGrowMarkdwonTextInputPaddingBottom, ]); const inputPaddingLeft = !!prefixCharacter && StyleUtils.getPaddingLeft(StyleUtils.getCharacterPadding(prefixCharacter) + styles.pl1.paddingLeft); diff --git a/src/components/TextInput/BaseTextInput/index.tsx b/src/components/TextInput/BaseTextInput/index.tsx index 09b644c8e76..43f2e53c29f 100644 --- a/src/components/TextInput/BaseTextInput/index.tsx +++ b/src/components/TextInput/BaseTextInput/index.tsx @@ -261,6 +261,7 @@ function BaseTextInput( !hideFocusedState && isFocused && styles.borderColorFocus, (!!hasError || !!errorText) && styles.borderColorDanger, autoGrowHeight && {scrollPaddingTop: typeof maxAutoGrowHeight === 'number' ? 2 * maxAutoGrowHeight : undefined}, + isAutoGrowHeightMarkdown && styles.autoGrowMarkdwonTextInputPaddingBottom, ]); const isMultiline = multiline || autoGrowHeight; diff --git a/src/styles/index.ts b/src/styles/index.ts index 6998b7698c1..0e6c8f0df42 100644 --- a/src/styles/index.ts +++ b/src/styles/index.ts @@ -1181,6 +1181,9 @@ const styles = (theme: ThemeColors) => overflow: 'hidden', borderBottomWidth: 2, borderColor: theme.border, + }, + + autoGrowMarkdwonTextInputPaddingBottom: { paddingBottom: 8, }, From 9447d668c5f753e596eb78ea2d49dd4a4d4d7b45 Mon Sep 17 00:00:00 2001 From: maddylewis <38016013+maddylewis@users.noreply.github.com> Date: Tue, 15 Oct 2024 11:45:13 -0400 Subject: [PATCH 099/121] Update Upgrade-to-the-new-Expensify-Card-from-Visa.md https://github.com/Expensify/Expensify/issues/432780 updated broken link + added deadline + fixed some formatting --- ...ade-to-the-new-Expensify-Card-from-Visa.md | 30 +++++++++++-------- 1 file changed, 17 insertions(+), 13 deletions(-) diff --git a/docs/articles/new-expensify/expensify-card/Upgrade-to-the-new-Expensify-Card-from-Visa.md b/docs/articles/new-expensify/expensify-card/Upgrade-to-the-new-Expensify-Card-from-Visa.md index 56e456eb125..c8baa9a7a53 100644 --- a/docs/articles/new-expensify/expensify-card/Upgrade-to-the-new-Expensify-Card-from-Visa.md +++ b/docs/articles/new-expensify/expensify-card/Upgrade-to-the-new-Expensify-Card-from-Visa.md @@ -4,24 +4,28 @@ description: Get the new Expensify Visa® Commercial Card ---
-If your company is already using Expensify Cards, you can upgrade your cards for free to the new Expensify Visa® Commercial Card to get even more tools to manage employee spending, including: -- Unlimited virtual cards +When you upgrade the Expensify Cards to the new program, you'll have access to even more tools to manage employee spending, including: +- Unlimited [virtual cards](https://use.expensify.com/unlimited-virtual-cards) - Controlled spending amounts on virtual cards to manage subscriptions - Tighter controls for managing spend across employees and merchants -- Fixed or monthly spend limits for each card +- Fixed or monthly spending limits for each card - Unique naming for each virtual card for simplified expense categorization -# Upgrade your company’s Expensify Cards - {% include info.html %} -This process must be completed by a Domain Admin. Although the process is available for all Domain Admins, only one admin needs to complete these steps. +The Expensify Card upgrade must be completed by November 1, 2024. +{% include end-info.html %} -Before completing this process, you’ll want to: +# Upgrade your company’s Expensify Card program -- Have your employees update their address if needed so that they receive their new Expensify Card in the mail before completing the steps below. -- Ensure that existing cardholders have a limit greater than $0 if you want them to receive a new Expensify Card. If their limit is $0, increase the limit. +{% include info.html %} +This process must be completed by a Domain Admin. Any domain Admin can complete the upgrade, but only one admin needs to complete these steps. {% include end-info.html %} +**Before updating the card program:** +- Make sure your employees' address is up-to-date in their Expensify account +- Confirm the employees who should be receiving a new Expensify Card have a card limit set that's greater than $0 + +## Steps to upgrade the Expensify Cards 1. On your Home page, click the task titled “Upgrade to the new and improved Expensify Card.” 2. Review and agree to the Terms of Service. 3. Click **Get the new card**. All existing cardholders with a limit greater than $0 will be automatically mailed a new physical card to the address they have on file. Virtual cards will be automatically issued and available for immediate use. @@ -36,19 +40,19 @@ Cards won’t be issued to any employees who don’t currently have them. In thi {% include faq-begin.md %} -**Why don’t I see the task to agree to new terms on my Home page?** +## Why don’t I see the task to agree to new terms on my Home page? There are a few reasons why you might not see the task on your Home page: - You may not be a Domain Admin - Another domain admin has already accepted the terms - The task may be hidden. To find hidden tasks, scroll to the bottom of the Home page and click **Show Hidden Tasks** to see all of your available tasks. -**Will this affect the continuous reconciliation process?** +## Will this affect the continuous reconciliation process? No. During the transition period, you may have some employees with old cards and some with new cards, so you’ll have two different debits (settlements) made to your settlement account for each settlement period. Once all spending has transitioned to the new cards, you’ll only see one debit/settlement. -**Do I have to upgrade to the new Expensify Visa® Commercial Card?** +## Do I have to upgrade to the new Expensify Visa® Commercial Card? -Yes. We’ll provide a deadline soon. But don’t worry—you’ll have plenty of time to upgrade. +Yes, the Expensify Cards will not work on the old program. This must be completed by November 1, 2024. {% include faq-end.md %}
From 725feb1b6bb316624c46509b303a597efb57553d Mon Sep 17 00:00:00 2001 From: maddylewis <38016013+maddylewis@users.noreply.github.com> Date: Tue, 15 Oct 2024 11:49:51 -0400 Subject: [PATCH 100/121] Update Upgrade-to-the-new-Expensify-Card-from-Visa.md removed one of the info box things --- .../Upgrade-to-the-new-Expensify-Card-from-Visa.md | 3 --- 1 file changed, 3 deletions(-) diff --git a/docs/articles/new-expensify/expensify-card/Upgrade-to-the-new-Expensify-Card-from-Visa.md b/docs/articles/new-expensify/expensify-card/Upgrade-to-the-new-Expensify-Card-from-Visa.md index c8baa9a7a53..141c5ffa630 100644 --- a/docs/articles/new-expensify/expensify-card/Upgrade-to-the-new-Expensify-Card-from-Visa.md +++ b/docs/articles/new-expensify/expensify-card/Upgrade-to-the-new-Expensify-Card-from-Visa.md @@ -16,10 +16,7 @@ The Expensify Card upgrade must be completed by November 1, 2024. {% include end-info.html %} # Upgrade your company’s Expensify Card program - -{% include info.html %} This process must be completed by a Domain Admin. Any domain Admin can complete the upgrade, but only one admin needs to complete these steps. -{% include end-info.html %} **Before updating the card program:** - Make sure your employees' address is up-to-date in their Expensify account From 392e0e3f2dd6f57cf81a4b4d8cb7e634c40cca36 Mon Sep 17 00:00:00 2001 From: maddylewis <38016013+maddylewis@users.noreply.github.com> Date: Tue, 15 Oct 2024 11:53:19 -0400 Subject: [PATCH 101/121] Update and rename Pay-Bills.md to Create-and-Pay-Bills.md https://github.com/Expensify/Expensify/issues/433121 updating title of article / will also create redirect --- .../payments/{Pay-Bills.md => Create-and-Pay-Bills.md} | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) rename docs/articles/expensify-classic/bank-accounts-and-payments/payments/{Pay-Bills.md => Create-and-Pay-Bills.md} (99%) diff --git a/docs/articles/expensify-classic/bank-accounts-and-payments/payments/Pay-Bills.md b/docs/articles/expensify-classic/bank-accounts-and-payments/payments/Create-and-Pay-Bills.md similarity index 99% rename from docs/articles/expensify-classic/bank-accounts-and-payments/payments/Pay-Bills.md rename to docs/articles/expensify-classic/bank-accounts-and-payments/payments/Create-and-Pay-Bills.md index 465f6742eae..721baded62b 100644 --- a/docs/articles/expensify-classic/bank-accounts-and-payments/payments/Pay-Bills.md +++ b/docs/articles/expensify-classic/bank-accounts-and-payments/payments/Create-and-Pay-Bills.md @@ -1,5 +1,5 @@ --- -title: Pay Bills +title: Create and Pay Bills description: Expensify bill management and payment methods. --- Streamline your operations by receiving and paying vendor or supplier bills directly in Expensify. Vendors can send bills even if they don't have an Expensify account, and you can manage payments seamlessly. From 63311c29e567c69d13fc077c401d167ef5df36c0 Mon Sep 17 00:00:00 2001 From: maddylewis <38016013+maddylewis@users.noreply.github.com> Date: Tue, 15 Oct 2024 11:55:17 -0400 Subject: [PATCH 102/121] Update redirects.csv adding redirect --- docs/redirects.csv | 1 + 1 file changed, 1 insertion(+) diff --git a/docs/redirects.csv b/docs/redirects.csv index 628fa25fce0..90baeff5926 100644 --- a/docs/redirects.csv +++ b/docs/redirects.csv @@ -586,3 +586,4 @@ https://community.expensify.com/discussion/9000/how-to-integrate-with-deel,https https://community.expensify.com/categories/expensify-classroom,https://use.expensify.com https://help.expensify.com/articles/expensify-classic/articles/expensify-classic/expenses/Send-Receive-for-Invoices,https://help.expensify.com/articles/expensify-classic/articles/expensify-classic/expenses/Send-and-Receive-Payment-for-Invoices.md https://help.expensify.com/articles/expensify-classic/articles/expensify-classic/expenses/Bulk-Upload-Multiple-Invoices,https://help.expensify.com/articles/expensify-classic/articles/expensify-classic/expenses/Add-Invoices-in-Bulk +https://help.expensify.com/articles/expensify-classic/bank-accounts-and-payments/payments/Pay-Bills,https://help.expensify.com/articles/expensify-classic/bank-accounts-and-payments/payments/Create-and-Pay-Bills From aee476a560b2fa2ac6da9af966a4f90f4f61eabd Mon Sep 17 00:00:00 2001 From: maddylewis <38016013+maddylewis@users.noreply.github.com> Date: Tue, 15 Oct 2024 11:57:08 -0400 Subject: [PATCH 103/121] Update Create-and-Pay-Bills.md fixed headers --- .../payments/Create-and-Pay-Bills.md | 14 +++++++------- 1 file changed, 7 insertions(+), 7 deletions(-) diff --git a/docs/articles/expensify-classic/bank-accounts-and-payments/payments/Create-and-Pay-Bills.md b/docs/articles/expensify-classic/bank-accounts-and-payments/payments/Create-and-Pay-Bills.md index 721baded62b..aff11c059d8 100644 --- a/docs/articles/expensify-classic/bank-accounts-and-payments/payments/Create-and-Pay-Bills.md +++ b/docs/articles/expensify-classic/bank-accounts-and-payments/payments/Create-and-Pay-Bills.md @@ -4,23 +4,23 @@ description: Expensify bill management and payment methods. --- Streamline your operations by receiving and paying vendor or supplier bills directly in Expensify. Vendors can send bills even if they don't have an Expensify account, and you can manage payments seamlessly. -## Receive Bills in Expensify +# Receive Bills in Expensify You can receive bills in three ways: - Directly from Vendors: Provide your Expensify billing email to vendors. - Forwarding Emails: Forward bills received in your email to Expensify. - Manual Upload: For physical bills, create a Bill in Expensify from the Reports page. -## Bill Pay Workflow +# Bill Pay Workflow 1. When a vendor or supplier sends a bill to Expensify, the document is automatically SmartScanned, and a Bill is created. This Bill is managed by the primary domain contact, who can view it on the Reports page within their default group policy. 2. Once the Bill is ready for processing, it follows the established approval workflow. As each person approves it, the Bill appears in the next approver’s Inbox. The final approver will pay the Bill using one of the available payment methods. 3. During this process, the Bill is coded with the appropriate GL codes from your connected accounting software. After completing the approval workflow, the Bill can be exported back to your accounting system. -## Payment Methods +# Payment Methods There are multiple ways to pay Bills in Expensify. Let’s go over each method below. -### ACH bank-to-bank transfer +## ACH bank-to-bank transfer To use this payment method, you must have a [business bank account connected to your Expensify account](https://help.expensify.com/articles/expensify-classic/bank-accounts-and-payments/bank-accounts/Connect-US-Business-Bank-Account). @@ -32,7 +32,7 @@ To use this payment method, you must have a [business bank account connected to **Fees:** None -### Credit or Debit Card +## Credit or Debit Card This option is available to all US and International customers receiving a bill from a US vendor with a US business bank account. **To pay with a credit or debit card:** @@ -43,13 +43,13 @@ This option is available to all US and International customers receiving a bill **Fees:** 2.9% of the total amount paid. -### Venmo +## Venmo If both you and the vendor must have Venmo connected to Expensify, you can pay the bill by following the steps outlined [here](https://help.expensify.com/articles/expensify-classic/bank-accounts-and-payments/Third-Party-Payments#setting-up-third-party-payments). **Fees:** Venmo charges a 3% sender’s fee. -### Pay outside of Expensify +## Pay outside of Expensify If you are unable to pay using one of the above methods, you can still mark the Bill as paid. This will update its status to indicate that the payment was made outside Expensify. **To mark a Bill as paid outside of Expensify:** From ddbb8757419ca476a8bd54c35e31643734a1ed3f Mon Sep 17 00:00:00 2001 From: maddylewis <38016013+maddylewis@users.noreply.github.com> Date: Tue, 15 Oct 2024 12:08:21 -0400 Subject: [PATCH 104/121] Update Upgrade-to-the-new-Expensify-Card-from-Visa.md minor update to see if preview shows changes --- .../Upgrade-to-the-new-Expensify-Card-from-Visa.md | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/docs/articles/new-expensify/expensify-card/Upgrade-to-the-new-Expensify-Card-from-Visa.md b/docs/articles/new-expensify/expensify-card/Upgrade-to-the-new-Expensify-Card-from-Visa.md index 141c5ffa630..8fffec75e74 100644 --- a/docs/articles/new-expensify/expensify-card/Upgrade-to-the-new-Expensify-Card-from-Visa.md +++ b/docs/articles/new-expensify/expensify-card/Upgrade-to-the-new-Expensify-Card-from-Visa.md @@ -23,7 +23,7 @@ This process must be completed by a Domain Admin. Any domain Admin can complete - Confirm the employees who should be receiving a new Expensify Card have a card limit set that's greater than $0 ## Steps to upgrade the Expensify Cards -1. On your Home page, click the task titled “Upgrade to the new and improved Expensify Card.” +1. On your Home page, click the task titled _Upgrade to the new and improved Expensify Card._ 2. Review and agree to the Terms of Service. 3. Click **Get the new card**. All existing cardholders with a limit greater than $0 will be automatically mailed a new physical card to the address they have on file. Virtual cards will be automatically issued and available for immediate use. 4. If you have Positive Pay enabled for your settlement account, contact your bank as soon as possible to whitelist the new ACH ID: 2270239450. From 01af655ff1684c597ba53dd5a1ab6376b80a605b Mon Sep 17 00:00:00 2001 From: Carlos Martins Date: Tue, 15 Oct 2024 11:12:29 -0600 Subject: [PATCH 105/121] fix router input styles --- src/styles/index.ts | 1 - 1 file changed, 1 deletion(-) diff --git a/src/styles/index.ts b/src/styles/index.ts index 6998b7698c1..343912a1139 100644 --- a/src/styles/index.ts +++ b/src/styles/index.ts @@ -3615,7 +3615,6 @@ const styles = (theme: ThemeColors) => }, searchInputStyle: { - color: theme.textSupporting, fontSize: 13, lineHeight: 16, }, From 13d132a90224f60a37745a2faf089fca13aa9b45 Mon Sep 17 00:00:00 2001 From: Carlos Martins Date: Tue, 15 Oct 2024 11:16:49 -0600 Subject: [PATCH 106/121] update filter button text color --- src/components/Search/SearchPageHeader.tsx | 1 - 1 file changed, 1 deletion(-) diff --git a/src/components/Search/SearchPageHeader.tsx b/src/components/Search/SearchPageHeader.tsx index ec43c976223..4c383021645 100644 --- a/src/components/Search/SearchPageHeader.tsx +++ b/src/components/Search/SearchPageHeader.tsx @@ -373,7 +373,6 @@ function SearchPageHeader({queryJSON, hash}: SearchPageHeaderProps) {