From a162fe71045c28c4d5f1075e27b84e90f3143185 Mon Sep 17 00:00:00 2001 From: NJ-2020 Date: Tue, 5 Nov 2024 15:07:15 -0800 Subject: [PATCH 001/161] fix wallet phone validation page --- src/libs/GetPhysicalCardUtils.ts | 6 +++++- .../settings/Wallet/Card/GetPhysicalCardPhone.tsx | 15 +++++++++++---- 2 files changed, 16 insertions(+), 5 deletions(-) diff --git a/src/libs/GetPhysicalCardUtils.ts b/src/libs/GetPhysicalCardUtils.ts index 8dc46204db3c..ff0c02aff54c 100644 --- a/src/libs/GetPhysicalCardUtils.ts +++ b/src/libs/GetPhysicalCardUtils.ts @@ -1,4 +1,6 @@ +import {Str} from 'expensify-common'; import type {OnyxEntry} from 'react-native-onyx'; +import * as PhoneNumberUtils from '@libs/PhoneNumber'; import ROUTES from '@src/ROUTES'; import type {Route} from '@src/ROUTES'; import type {GetPhysicalCardForm} from '@src/types/form'; @@ -11,11 +13,13 @@ import * as UserUtils from './UserUtils'; function getCurrentRoute(domain: string, privatePersonalDetails: OnyxEntry): Route { const {legalFirstName, legalLastName, phoneNumber} = privatePersonalDetails ?? {}; const address = PersonalDetailsUtils.getCurrentAddress(privatePersonalDetails); + const phoneNumberWithCountryCode = LoginUtils.appendCountryCode(phoneNumber ?? ''); + const parsedPhoneNumber = PhoneNumberUtils.parsePhoneNumber(phoneNumberWithCountryCode); if (!legalFirstName && !legalLastName) { return ROUTES.SETTINGS_WALLET_CARD_GET_PHYSICAL_NAME.getRoute(domain); } - if (!phoneNumber || !LoginUtils.validateNumber(phoneNumber)) { + if (!phoneNumber || !parsedPhoneNumber.possible || !Str.isValidE164Phone(phoneNumberWithCountryCode.slice(0))) { return ROUTES.SETTINGS_WALLET_CARD_GET_PHYSICAL_PHONE.getRoute(domain); } if (!(address?.street && address?.city && address?.state && address?.country && address?.zip)) { diff --git a/src/pages/settings/Wallet/Card/GetPhysicalCardPhone.tsx b/src/pages/settings/Wallet/Card/GetPhysicalCardPhone.tsx index 56d5a29a3203..ad04d9f3fb9c 100644 --- a/src/pages/settings/Wallet/Card/GetPhysicalCardPhone.tsx +++ b/src/pages/settings/Wallet/Card/GetPhysicalCardPhone.tsx @@ -1,4 +1,5 @@ import type {StackScreenProps} from '@react-navigation/stack'; +import {Str} from 'expensify-common'; import React from 'react'; import {withOnyx} from 'react-native-onyx'; import type {OnyxEntry} from 'react-native-onyx'; @@ -6,6 +7,8 @@ import InputWrapper from '@components/Form/InputWrapper'; import TextInput from '@components/TextInput'; import useLocalize from '@hooks/useLocalize'; import * as LoginUtils from '@libs/LoginUtils'; +import * as PhoneNumberUtils from '@libs/PhoneNumber'; +import * as ValidationUtils from '@libs/ValidationUtils'; import type {SettingsNavigatorParamList} from '@navigation/types'; import CONST from '@src/CONST'; import ONYXKEYS from '@src/ONYXKEYS'; @@ -40,13 +43,17 @@ function GetPhysicalCardPhone({ const {phoneNumber: phoneNumberToValidate = ''} = values ?? {}; const errors: OnValidateResult = {}; - - if (!LoginUtils.validateNumber(phoneNumberToValidate)) { - errors.phoneNumber = translate('common.error.phoneNumber'); - } else if (!phoneNumberToValidate) { + if (!ValidationUtils.isRequiredFulfilled(phoneNumberToValidate)) { errors.phoneNumber = translate('common.error.fieldRequired'); } + const phoneNumberWithCountryCode = LoginUtils.appendCountryCode(phoneNumberToValidate); + const parsedPhoneNumber = PhoneNumberUtils.parsePhoneNumber(phoneNumberWithCountryCode); + + if (!parsedPhoneNumber.possible || !Str.isValidE164Phone(phoneNumberWithCountryCode.slice(0))) { + errors.phoneNumber = translate('bankAccount.error.phoneNumber'); + } + return errors; }; From 8e3babe9a7e599b8f1d18a52d3bfdc77c4d0c464 Mon Sep 17 00:00:00 2001 From: NJ-2020 Date: Wed, 6 Nov 2024 22:39:18 -0800 Subject: [PATCH 002/161] fix eslint warning and unnecessary changes --- src/libs/GetPhysicalCardUtils.ts | 2 +- src/pages/settings/Wallet/Card/GetPhysicalCardPhone.tsx | 1 + 2 files changed, 2 insertions(+), 1 deletion(-) diff --git a/src/libs/GetPhysicalCardUtils.ts b/src/libs/GetPhysicalCardUtils.ts index ff0c02aff54c..9ed192b09233 100644 --- a/src/libs/GetPhysicalCardUtils.ts +++ b/src/libs/GetPhysicalCardUtils.ts @@ -1,6 +1,5 @@ import {Str} from 'expensify-common'; import type {OnyxEntry} from 'react-native-onyx'; -import * as PhoneNumberUtils from '@libs/PhoneNumber'; import ROUTES from '@src/ROUTES'; import type {Route} from '@src/ROUTES'; import type {GetPhysicalCardForm} from '@src/types/form'; @@ -8,6 +7,7 @@ import type {LoginList, PrivatePersonalDetails} from '@src/types/onyx'; import * as LoginUtils from './LoginUtils'; import Navigation from './Navigation/Navigation'; import * as PersonalDetailsUtils from './PersonalDetailsUtils'; +import * as PhoneNumberUtils from './PhoneNumber'; import * as UserUtils from './UserUtils'; function getCurrentRoute(domain: string, privatePersonalDetails: OnyxEntry): Route { diff --git a/src/pages/settings/Wallet/Card/GetPhysicalCardPhone.tsx b/src/pages/settings/Wallet/Card/GetPhysicalCardPhone.tsx index ad04d9f3fb9c..ce50a224c20d 100644 --- a/src/pages/settings/Wallet/Card/GetPhysicalCardPhone.tsx +++ b/src/pages/settings/Wallet/Card/GetPhysicalCardPhone.tsx @@ -43,6 +43,7 @@ function GetPhysicalCardPhone({ const {phoneNumber: phoneNumberToValidate = ''} = values ?? {}; const errors: OnValidateResult = {}; + if (!ValidationUtils.isRequiredFulfilled(phoneNumberToValidate)) { errors.phoneNumber = translate('common.error.fieldRequired'); } From b4c8b46bc8e4a51eae99310499d946937d85cfc4 Mon Sep 17 00:00:00 2001 From: Bernhard Owen Josephus Date: Sat, 30 Nov 2024 17:16:29 +0800 Subject: [PATCH 003/161] apply tax rule when selecting category --- src/libs/actions/IOU.ts | 8 ++++ .../request/step/IOURequestStepCategory.tsx | 39 +++++++++++++++++-- 2 files changed, 44 insertions(+), 3 deletions(-) diff --git a/src/libs/actions/IOU.ts b/src/libs/actions/IOU.ts index 90719ffeed55..97aa5bb4c717 100644 --- a/src/libs/actions/IOU.ts +++ b/src/libs/actions/IOU.ts @@ -3203,6 +3203,8 @@ function updateMoneyRequestCategory( transactionID: string, transactionThreadReportID: string, category: string, + categoryTaxCode: string | undefined, + categoryTaxAmount: number | undefined, policy: OnyxEntry, policyTagList: OnyxEntry, policyCategories: OnyxEntry, @@ -3210,6 +3212,12 @@ function updateMoneyRequestCategory( const transactionChanges: TransactionChanges = { category, }; + + if (categoryTaxCode && categoryTaxAmount !== undefined) { + transactionChanges['taxCode'] = categoryTaxCode; + transactionChanges['taxAmount'] = categoryTaxAmount; + } + const {params, onyxData} = getUpdateMoneyRequestParams(transactionID, transactionThreadReportID, transactionChanges, policy, policyTagList, policyCategories); API.write(WRITE_COMMANDS.UPDATE_MONEY_REQUEST_CATEGORY, params, onyxData); } diff --git a/src/pages/iou/request/step/IOURequestStepCategory.tsx b/src/pages/iou/request/step/IOURequestStepCategory.tsx index 6f81d6ea3443..7b478cff75ec 100644 --- a/src/pages/iou/request/step/IOURequestStepCategory.tsx +++ b/src/pages/iou/request/step/IOURequestStepCategory.tsx @@ -14,6 +14,8 @@ import useLocalize from '@hooks/useLocalize'; import useNetwork from '@hooks/useNetwork'; import useTheme from '@hooks/useTheme'; import useThemeStyles from '@hooks/useThemeStyles'; +import * as CategoryUtils from '@libs/CategoryUtils'; +import * as CurrencyUtils from '@libs/CurrencyUtils'; import Navigation from '@libs/Navigation/Navigation'; import * as OptionsListUtils from '@libs/OptionsListUtils'; import * as PolicyUtils from '@libs/PolicyUtils'; @@ -68,7 +70,8 @@ function IOURequestStepCategory({ const {translate} = useLocalize(); const isEditing = action === CONST.IOU.ACTION.EDIT; const isEditingSplitBill = isEditing && iouType === CONST.IOU.TYPE.SPLIT; - const transactionCategory = ReportUtils.getTransactionDetails(isEditingSplitBill && !lodashIsEmpty(splitDraftTransaction) ? splitDraftTransaction : transaction)?.category; + const currentTransaction = isEditingSplitBill && !lodashIsEmpty(splitDraftTransaction) ? splitDraftTransaction : transaction; + const transactionCategory = ReportUtils.getTransactionDetails(currentTransaction)?.category; // eslint-disable-next-line @typescript-eslint/prefer-nullish-coalescing const reportAction = reportActions?.[report?.parentReportActionID || reportActionID] ?? null; @@ -109,17 +112,42 @@ function IOURequestStepCategory({ const categorySearchText = category.searchText ?? ''; const isSelectedCategory = categorySearchText === transactionCategory; const updatedCategory = isSelectedCategory ? '' : categorySearchText; + const categoryTaxCode = CategoryUtils.getCategoryDefaultTaxRate(policy?.rules?.expenseRules ?? [], updatedCategory, policy?.taxRates?.defaultExternalID); + let categoryTaxPercentage; + let categoryTaxAmount; + + if (categoryTaxCode) { + categoryTaxPercentage = TransactionUtils.getTaxValue(policy, currentTransaction, categoryTaxCode); + + if (categoryTaxPercentage) { + const isFromExpenseReport = ReportUtils.isExpenseReport(report) || ReportUtils.isPolicyExpenseChat(report); + categoryTaxAmount = CurrencyUtils.convertToBackendAmount( + TransactionUtils.calculateTaxAmount( + categoryTaxPercentage, + TransactionUtils.getAmount(currentTransaction, isFromExpenseReport), + TransactionUtils.getCurrency(transaction), + ), + ); + } + } + + console.log(categoryTaxPercentage, categoryTaxAmount); if (transaction) { // In the split flow, when editing we use SPLIT_TRANSACTION_DRAFT to save draft value if (isEditingSplitBill) { - IOU.setDraftSplitTransaction(transaction.transactionID, {category: updatedCategory}); + const transactionChanges: TransactionUtils.TransactionChanges = {category: updatedCategory}; + if (categoryTaxCode && categoryTaxAmount !== undefined) { + transactionChanges.taxCode = categoryTaxCode; + transactionChanges.taxAmount = categoryTaxAmount; + } + IOU.setDraftSplitTransaction(transaction.transactionID, transactionChanges); navigateBack(); return; } if (isEditing && report) { - IOU.updateMoneyRequestCategory(transaction.transactionID, report.reportID, updatedCategory, policy, policyTags, policyCategories); + IOU.updateMoneyRequestCategory(transaction.transactionID, report.reportID, updatedCategory, categoryTaxCode, categoryTaxAmount, policy, policyTags, policyCategories); navigateBack(); return; } @@ -127,6 +155,11 @@ function IOURequestStepCategory({ IOU.setMoneyRequestCategory(transactionID, updatedCategory); + if (categoryTaxCode && categoryTaxAmount !== undefined) { + IOU.setMoneyRequestTaxRate(transactionID, categoryTaxCode); + IOU.setMoneyRequestTaxAmount(transactionID, categoryTaxAmount); + } + if (action === CONST.IOU.ACTION.CATEGORIZE) { Navigation.closeAndNavigate(ROUTES.MONEY_REQUEST_STEP_CONFIRMATION.getRoute(action, iouType, transactionID, report?.reportID ?? '-1')); return; From 622fe8bb19e9cb99f1a316d39c6981a4e15ed39b Mon Sep 17 00:00:00 2001 From: Bernhard Owen Josephus Date: Sat, 30 Nov 2024 17:37:41 +0800 Subject: [PATCH 004/161] remove log --- src/pages/iou/request/step/IOURequestStepCategory.tsx | 2 -- 1 file changed, 2 deletions(-) diff --git a/src/pages/iou/request/step/IOURequestStepCategory.tsx b/src/pages/iou/request/step/IOURequestStepCategory.tsx index 7b478cff75ec..e148b527f19a 100644 --- a/src/pages/iou/request/step/IOURequestStepCategory.tsx +++ b/src/pages/iou/request/step/IOURequestStepCategory.tsx @@ -131,8 +131,6 @@ function IOURequestStepCategory({ } } - console.log(categoryTaxPercentage, categoryTaxAmount); - if (transaction) { // In the split flow, when editing we use SPLIT_TRANSACTION_DRAFT to save draft value if (isEditingSplitBill) { From 438ad047da5893d05fe4f193ae27631e453f632c Mon Sep 17 00:00:00 2001 From: Bernhard Owen Josephus Date: Sat, 30 Nov 2024 17:43:34 +0800 Subject: [PATCH 005/161] lint --- src/libs/actions/IOU.ts | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/src/libs/actions/IOU.ts b/src/libs/actions/IOU.ts index 97aa5bb4c717..3c50ee3f32d4 100644 --- a/src/libs/actions/IOU.ts +++ b/src/libs/actions/IOU.ts @@ -3214,8 +3214,8 @@ function updateMoneyRequestCategory( }; if (categoryTaxCode && categoryTaxAmount !== undefined) { - transactionChanges['taxCode'] = categoryTaxCode; - transactionChanges['taxAmount'] = categoryTaxAmount; + transactionChanges.taxCode = categoryTaxCode; + transactionChanges.taxAmount = categoryTaxAmount; } const {params, onyxData} = getUpdateMoneyRequestParams(transactionID, transactionThreadReportID, transactionChanges, policy, policyTagList, policyCategories); From 8733cd4b4dc48e25cb33bffccb97409083a72143 Mon Sep 17 00:00:00 2001 From: truph01 Date: Wed, 4 Dec 2024 11:32:24 +0700 Subject: [PATCH 006/161] fix: when selecting categories, the selected categories get reset --- src/hooks/useCleanupSelectedOptions/index.ts | 24 +++++++++++++++++++ .../categories/WorkspaceCategoriesPage.tsx | 13 +++++----- .../workspace/tags/WorkspaceTagsPage.tsx | 13 +++++----- .../workspace/taxes/WorkspaceTaxesPage.tsx | 15 ++++++------ 4 files changed, 46 insertions(+), 19 deletions(-) create mode 100644 src/hooks/useCleanupSelectedOptions/index.ts diff --git a/src/hooks/useCleanupSelectedOptions/index.ts b/src/hooks/useCleanupSelectedOptions/index.ts new file mode 100644 index 000000000000..a920e2ea07cf --- /dev/null +++ b/src/hooks/useCleanupSelectedOptions/index.ts @@ -0,0 +1,24 @@ +import {NavigationContainerRefContext, useIsFocused} from '@react-navigation/native'; +import {useContext, useEffect} from 'react'; +import NAVIGATORS from '@src/NAVIGATORS'; + +let shouldCleanupSelectedOptions = false; + +const useCleanupSelectedOptions = (cleanupFunction?: () => void) => { + const navigationContainerRef = useContext(NavigationContainerRefContext); + const state = navigationContainerRef?.getState(); + const lastRoute = state?.routes.at(-1); + const isRightModalOpening = lastRoute?.name === NAVIGATORS.RIGHT_MODAL_NAVIGATOR; + + const isFocused = useIsFocused(); + + useEffect(() => { + if (isFocused || isRightModalOpening) { + return; + } + shouldCleanupSelectedOptions = false; + cleanupFunction?.(); + }, [isFocused, cleanupFunction, isRightModalOpening]); +}; + +export {useCleanupSelectedOptions}; diff --git a/src/pages/workspace/categories/WorkspaceCategoriesPage.tsx b/src/pages/workspace/categories/WorkspaceCategoriesPage.tsx index 56ba8a5440b8..c73e9753ed66 100644 --- a/src/pages/workspace/categories/WorkspaceCategoriesPage.tsx +++ b/src/pages/workspace/categories/WorkspaceCategoriesPage.tsx @@ -23,6 +23,7 @@ import TableListItemSkeleton from '@components/Skeletons/TableRowSkeleton'; import Text from '@components/Text'; import TextLink from '@components/TextLink'; import useAutoTurnSelectionModeOffWhenHasNoActiveOption from '@hooks/useAutoTurnSelectionModeOffWhenHasNoActiveOption'; +import {useCleanupSelectedOptions} from '@hooks/useCleanupSelectedOptions'; import useEnvironment from '@hooks/useEnvironment'; import useLocalize from '@hooks/useLocalize'; import useMobileSelectionMode from '@hooks/useMobileSelectionMode'; @@ -98,12 +99,8 @@ function WorkspaceCategoriesPage({route}: WorkspaceCategoriesPageProps) { }, [fetchCategories]), ); - useEffect(() => { - if (isFocused) { - return; - } - setSelectedCategories({}); - }, [isFocused]); + const cleanupSelectedOption = useCallback(() => setSelectedCategories({}), []); + useCleanupSelectedOptions(cleanupSelectedOption); const categoryList = useMemo( () => @@ -151,6 +148,10 @@ function WorkspaceCategoriesPage({route}: WorkspaceCategoriesPageProps) { }; const navigateToCategorySettings = (category: PolicyOption) => { + if (isSmallScreenWidth && selectionMode?.isEnabled) { + toggleCategory(category); + return; + } Navigation.navigate( isQuickSettingsFlow ? ROUTES.SETTINGS_CATEGORY_SETTINGS.getRoute(policyId, category.keyForList, backTo) diff --git a/src/pages/workspace/tags/WorkspaceTagsPage.tsx b/src/pages/workspace/tags/WorkspaceTagsPage.tsx index d5c72048f8a4..9962113f44c4 100644 --- a/src/pages/workspace/tags/WorkspaceTagsPage.tsx +++ b/src/pages/workspace/tags/WorkspaceTagsPage.tsx @@ -22,6 +22,7 @@ import CustomListHeader from '@components/SelectionListWithModal/CustomListHeade import TableListItemSkeleton from '@components/Skeletons/TableRowSkeleton'; import Text from '@components/Text'; import TextLink from '@components/TextLink'; +import {useCleanupSelectedOptions} from '@hooks/useCleanupSelectedOptions'; import useEnvironment from '@hooks/useEnvironment'; import useLocalize from '@hooks/useLocalize'; import useMobileSelectionMode from '@hooks/useMobileSelectionMode'; @@ -87,12 +88,8 @@ function WorkspaceTagsPage({route}: WorkspaceTagsPageProps) { useFocusEffect(fetchTags); - useEffect(() => { - if (isFocused) { - return; - } - setSelectedTags({}); - }, [isFocused]); + const cleanupSelectedOption = useCallback(() => setSelectedTags({}), []); + useCleanupSelectedOptions(cleanupSelectedOption); const getPendingAction = (policyTagList: PolicyTagList): PendingAction | undefined => { if (!policyTagList) { @@ -176,6 +173,10 @@ function WorkspaceTagsPage({route}: WorkspaceTagsPageProps) { }; const navigateToTagSettings = (tag: TagListItem) => { + if (isSmallScreenWidth && selectionMode?.isEnabled) { + toggleTag(tag); + return; + } if (tag.orderWeight !== undefined) { Navigation.navigate( isQuickSettingsFlow ? ROUTES.SETTINGS_TAG_LIST_VIEW.getRoute(policyID, tag.orderWeight, backTo) : ROUTES.WORKSPACE_TAG_LIST_VIEW.getRoute(policyID, tag.orderWeight), diff --git a/src/pages/workspace/taxes/WorkspaceTaxesPage.tsx b/src/pages/workspace/taxes/WorkspaceTaxesPage.tsx index 9dbe739ae1db..9694d43df144 100644 --- a/src/pages/workspace/taxes/WorkspaceTaxesPage.tsx +++ b/src/pages/workspace/taxes/WorkspaceTaxesPage.tsx @@ -17,6 +17,7 @@ import SelectionListWithModal from '@components/SelectionListWithModal'; import CustomListHeader from '@components/SelectionListWithModal/CustomListHeader'; import Text from '@components/Text'; import TextLink from '@components/TextLink'; +import {useCleanupSelectedOptions} from '@hooks/useCleanupSelectedOptions'; import useEnvironment from '@hooks/useEnvironment'; import useLocalize from '@hooks/useLocalize'; import useMobileSelectionMode from '@hooks/useMobileSelectionMode'; @@ -51,7 +52,7 @@ function WorkspaceTaxesPage({ params: {policyID}, }, }: WorkspaceTaxesPageProps) { - const {shouldUseNarrowLayout} = useResponsiveLayout(); + const {shouldUseNarrowLayout, isSmallScreenWidth} = useResponsiveLayout(); const styles = useThemeStyles(); const theme = useTheme(); const {translate} = useLocalize(); @@ -86,12 +87,8 @@ function WorkspaceTaxesPage({ }, [fetchTaxes]), ); - useEffect(() => { - if (isFocused) { - return; - } - setSelectedTaxesIDs([]); - }, [isFocused]); + const cleanupSelectedOption = useCallback(() => setSelectedTaxesIDs([]), []); + useCleanupSelectedOptions(cleanupSelectedOption); const textForDefault = useCallback( (taxID: string, taxRate: TaxRate): string => { @@ -192,6 +189,10 @@ function WorkspaceTaxesPage({ if (!taxRate.keyForList) { return; } + if (isSmallScreenWidth && selectionMode?.isEnabled) { + toggleTax(taxRate); + return; + } Navigation.navigate(ROUTES.WORKSPACE_TAX_EDIT.getRoute(policyID, taxRate.keyForList)); }; From cbd2efe9acd650e9ffa7c9ba52003ddd4e434d9c Mon Sep 17 00:00:00 2001 From: truph01 Date: Wed, 4 Dec 2024 14:00:24 +0700 Subject: [PATCH 007/161] fix: lint --- src/hooks/useCleanupSelectedOptions/index.ts | 2 +- .../workspace/categories/WorkspaceCategoriesPage.tsx | 5 ++--- src/pages/workspace/tags/WorkspaceTagsPage.tsx | 7 +++---- src/pages/workspace/taxes/WorkspaceTaxesPage.tsx | 8 ++++---- 4 files changed, 10 insertions(+), 12 deletions(-) diff --git a/src/hooks/useCleanupSelectedOptions/index.ts b/src/hooks/useCleanupSelectedOptions/index.ts index a920e2ea07cf..afcc0346afb9 100644 --- a/src/hooks/useCleanupSelectedOptions/index.ts +++ b/src/hooks/useCleanupSelectedOptions/index.ts @@ -21,4 +21,4 @@ const useCleanupSelectedOptions = (cleanupFunction?: () => void) => { }, [isFocused, cleanupFunction, isRightModalOpening]); }; -export {useCleanupSelectedOptions}; +export default useCleanupSelectedOptions; diff --git a/src/pages/workspace/categories/WorkspaceCategoriesPage.tsx b/src/pages/workspace/categories/WorkspaceCategoriesPage.tsx index c73e9753ed66..3a588176ef4d 100644 --- a/src/pages/workspace/categories/WorkspaceCategoriesPage.tsx +++ b/src/pages/workspace/categories/WorkspaceCategoriesPage.tsx @@ -1,4 +1,4 @@ -import {useFocusEffect, useIsFocused} from '@react-navigation/native'; +import {useFocusEffect} from '@react-navigation/native'; import lodashSortBy from 'lodash/sortBy'; import React, {useCallback, useEffect, useMemo, useState} from 'react'; import {ActivityIndicator, View} from 'react-native'; @@ -23,7 +23,7 @@ import TableListItemSkeleton from '@components/Skeletons/TableRowSkeleton'; import Text from '@components/Text'; import TextLink from '@components/TextLink'; import useAutoTurnSelectionModeOffWhenHasNoActiveOption from '@hooks/useAutoTurnSelectionModeOffWhenHasNoActiveOption'; -import {useCleanupSelectedOptions} from '@hooks/useCleanupSelectedOptions'; +import useCleanupSelectedOptions from '@hooks/useCleanupSelectedOptions'; import useEnvironment from '@hooks/useEnvironment'; import useLocalize from '@hooks/useLocalize'; import useMobileSelectionMode from '@hooks/useMobileSelectionMode'; @@ -71,7 +71,6 @@ function WorkspaceCategoriesPage({route}: WorkspaceCategoriesPageProps) { const [selectedCategories, setSelectedCategories] = useState>({}); const [isDownloadFailureModalVisible, setIsDownloadFailureModalVisible] = useState(false); const [deleteCategoriesConfirmModalVisible, setDeleteCategoriesConfirmModalVisible] = useState(false); - const isFocused = useIsFocused(); const {environmentURL} = useEnvironment(); const policyId = route.params.policyID ?? '-1'; const backTo = route.params?.backTo; diff --git a/src/pages/workspace/tags/WorkspaceTagsPage.tsx b/src/pages/workspace/tags/WorkspaceTagsPage.tsx index 9962113f44c4..756f402b9182 100644 --- a/src/pages/workspace/tags/WorkspaceTagsPage.tsx +++ b/src/pages/workspace/tags/WorkspaceTagsPage.tsx @@ -1,6 +1,6 @@ -import {useFocusEffect, useIsFocused} from '@react-navigation/native'; +import {useFocusEffect} from '@react-navigation/native'; import lodashSortBy from 'lodash/sortBy'; -import React, {useCallback, useEffect, useMemo, useState} from 'react'; +import React, {useCallback, useMemo, useState} from 'react'; import {ActivityIndicator, View} from 'react-native'; import {useOnyx} from 'react-native-onyx'; import Button from '@components/Button'; @@ -22,7 +22,7 @@ import CustomListHeader from '@components/SelectionListWithModal/CustomListHeade import TableListItemSkeleton from '@components/Skeletons/TableRowSkeleton'; import Text from '@components/Text'; import TextLink from '@components/TextLink'; -import {useCleanupSelectedOptions} from '@hooks/useCleanupSelectedOptions'; +import useCleanupSelectedOptions from '@hooks/useCleanupSelectedOptions'; import useEnvironment from '@hooks/useEnvironment'; import useLocalize from '@hooks/useLocalize'; import useMobileSelectionMode from '@hooks/useMobileSelectionMode'; @@ -65,7 +65,6 @@ function WorkspaceTagsPage({route}: WorkspaceTagsPageProps) { const [isDownloadFailureModalVisible, setIsDownloadFailureModalVisible] = useState(false); const [isDeleteTagsConfirmModalVisible, setIsDeleteTagsConfirmModalVisible] = useState(false); const [isOfflineModalVisible, setIsOfflineModalVisible] = useState(false); - const isFocused = useIsFocused(); const policyID = route.params.policyID ?? '-1'; const backTo = route.params.backTo; const policy = usePolicy(policyID); diff --git a/src/pages/workspace/taxes/WorkspaceTaxesPage.tsx b/src/pages/workspace/taxes/WorkspaceTaxesPage.tsx index 9694d43df144..94831f8dc8f5 100644 --- a/src/pages/workspace/taxes/WorkspaceTaxesPage.tsx +++ b/src/pages/workspace/taxes/WorkspaceTaxesPage.tsx @@ -1,5 +1,5 @@ -import {useFocusEffect, useIsFocused} from '@react-navigation/native'; -import React, {useCallback, useEffect, useMemo, useState} from 'react'; +import {useFocusEffect} from '@react-navigation/native'; +import React, {useCallback, useMemo, useState} from 'react'; import {ActivityIndicator, View} from 'react-native'; import {useOnyx} from 'react-native-onyx'; import Button from '@components/Button'; @@ -17,7 +17,7 @@ import SelectionListWithModal from '@components/SelectionListWithModal'; import CustomListHeader from '@components/SelectionListWithModal/CustomListHeader'; import Text from '@components/Text'; import TextLink from '@components/TextLink'; -import {useCleanupSelectedOptions} from '@hooks/useCleanupSelectedOptions'; +import useCleanupSelectedOptions from '@hooks/useCleanupSelectedOptions'; import useEnvironment from '@hooks/useEnvironment'; import useLocalize from '@hooks/useLocalize'; import useMobileSelectionMode from '@hooks/useMobileSelectionMode'; @@ -52,6 +52,7 @@ function WorkspaceTaxesPage({ params: {policyID}, }, }: WorkspaceTaxesPageProps) { + // eslint-disable-next-line rulesdir/prefer-shouldUseNarrowLayout-instead-of-isSmallScreenWidth const {shouldUseNarrowLayout, isSmallScreenWidth} = useResponsiveLayout(); const styles = useThemeStyles(); const theme = useTheme(); @@ -62,7 +63,6 @@ function WorkspaceTaxesPage({ const {selectionMode} = useMobileSelectionMode(); const defaultExternalID = policy?.taxRates?.defaultExternalID; const foreignTaxDefault = policy?.taxRates?.foreignTaxDefault; - const isFocused = useIsFocused(); const hasAccountingConnections = PolicyUtils.hasAccountingConnections(policy); const [connectionSyncProgress] = useOnyx(`${ONYXKEYS.COLLECTION.POLICY_CONNECTION_SYNC_PROGRESS}${policy?.id}`); const isSyncInProgress = isConnectionInProgress(connectionSyncProgress, policy); From 195aaeea0c3cd8f68c86d31d949ae3cb74a3312e Mon Sep 17 00:00:00 2001 From: truph01 Date: Wed, 4 Dec 2024 14:09:53 +0700 Subject: [PATCH 008/161] fix: lint --- src/hooks/useCleanupSelectedOptions/index.ts | 3 --- 1 file changed, 3 deletions(-) diff --git a/src/hooks/useCleanupSelectedOptions/index.ts b/src/hooks/useCleanupSelectedOptions/index.ts index afcc0346afb9..7451e85aef23 100644 --- a/src/hooks/useCleanupSelectedOptions/index.ts +++ b/src/hooks/useCleanupSelectedOptions/index.ts @@ -2,8 +2,6 @@ import {NavigationContainerRefContext, useIsFocused} from '@react-navigation/nat import {useContext, useEffect} from 'react'; import NAVIGATORS from '@src/NAVIGATORS'; -let shouldCleanupSelectedOptions = false; - const useCleanupSelectedOptions = (cleanupFunction?: () => void) => { const navigationContainerRef = useContext(NavigationContainerRefContext); const state = navigationContainerRef?.getState(); @@ -16,7 +14,6 @@ const useCleanupSelectedOptions = (cleanupFunction?: () => void) => { if (isFocused || isRightModalOpening) { return; } - shouldCleanupSelectedOptions = false; cleanupFunction?.(); }, [isFocused, cleanupFunction, isRightModalOpening]); }; From 3a8d4302acef3fb9b2f7f187bdde7c2d36d7ba63 Mon Sep 17 00:00:00 2001 From: daledah Date: Wed, 4 Dec 2024 17:41:42 +0700 Subject: [PATCH 009/161] fix: some member appear as Hidden --- src/libs/PersonalDetailsUtils.ts | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/libs/PersonalDetailsUtils.ts b/src/libs/PersonalDetailsUtils.ts index 3d9aed117ca3..31d344facb32 100644 --- a/src/libs/PersonalDetailsUtils.ts +++ b/src/libs/PersonalDetailsUtils.ts @@ -75,7 +75,7 @@ function getDisplayNameOrDefault(passedPersonalDetails?: Partial Date: Thu, 5 Dec 2024 14:26:23 +0530 Subject: [PATCH 010/161] Implemented Edit/Delete Per diem rates --- src/ONYXKEYS.ts | 3 + src/ROUTES.ts | 20 ++ src/SCREENS.ts | 5 + src/components/AmountWithoutCurrencyForm.tsx | 13 +- src/languages/en.ts | 4 + src/languages/es.ts | 4 + src/languages/params.ts | 5 + .../UpdateWorkspaceCustomUnitParams.ts | 6 + src/libs/API/parameters/index.ts | 1 + src/libs/API/types.ts | 2 + src/libs/MoneyRequestUtils.ts | 12 +- .../ModalStackNavigators/index.tsx | 5 + .../FULL_SCREEN_TO_RHP_MAPPING.ts | 11 +- src/libs/Navigation/linkingConfig/config.ts | 15 ++ src/libs/Navigation/types.ts | 25 ++ src/libs/actions/Policy/PerDiem.ts | 215 +++++++++++++++++- .../perDiem/EditPerDiemAmountPage.tsx | 116 ++++++++++ .../perDiem/EditPerDiemCurrencyPage.tsx | 80 +++++++ .../perDiem/EditPerDiemDestinationPage.tsx | 115 ++++++++++ .../perDiem/EditPerDiemSubratePage.tsx | 109 +++++++++ .../perDiem/WorkspacePerDiemDetailsPage.tsx | 128 +++++++++++ .../perDiem/WorkspacePerDiemPage.tsx | 11 +- src/types/form/WorkspacePerDiemForm.ts | 22 ++ src/types/form/index.ts | 1 + 24 files changed, 906 insertions(+), 22 deletions(-) create mode 100644 src/libs/API/parameters/UpdateWorkspaceCustomUnitParams.ts create mode 100644 src/pages/workspace/perDiem/EditPerDiemAmountPage.tsx create mode 100644 src/pages/workspace/perDiem/EditPerDiemCurrencyPage.tsx create mode 100644 src/pages/workspace/perDiem/EditPerDiemDestinationPage.tsx create mode 100644 src/pages/workspace/perDiem/EditPerDiemSubratePage.tsx create mode 100644 src/pages/workspace/perDiem/WorkspacePerDiemDetailsPage.tsx create mode 100644 src/types/form/WorkspacePerDiemForm.ts diff --git a/src/ONYXKEYS.ts b/src/ONYXKEYS.ts index 3c3812774380..9251f6c368b4 100755 --- a/src/ONYXKEYS.ts +++ b/src/ONYXKEYS.ts @@ -721,6 +721,8 @@ const ONYXKEYS = { RULES_MAX_EXPENSE_AGE_FORM_DRAFT: 'rulesMaxExpenseAgeFormDraft', DEBUG_DETAILS_FORM: 'debugDetailsForm', DEBUG_DETAILS_FORM_DRAFT: 'debugDetailsFormDraft', + WORKSPACE_PER_DIEM_FORM: 'workspacePerDiemForm', + WORKSPACE_PER_DIEM_FORM_DRAFT: 'workspacePerDiemFormDraft', }, } as const; @@ -814,6 +816,7 @@ type OnyxFormValuesMapping = { [ONYXKEYS.FORMS.RULES_MAX_EXPENSE_AGE_FORM]: FormTypes.RulesMaxExpenseAgeForm; [ONYXKEYS.FORMS.SEARCH_SAVED_SEARCH_RENAME_FORM]: FormTypes.SearchSavedSearchRenameForm; [ONYXKEYS.FORMS.DEBUG_DETAILS_FORM]: FormTypes.DebugReportForm | FormTypes.DebugReportActionForm | FormTypes.DebugTransactionForm | FormTypes.DebugTransactionViolationForm; + [ONYXKEYS.FORMS.WORKSPACE_PER_DIEM_FORM]: FormTypes.WorkspacePerDiemForm; }; type OnyxFormDraftValuesMapping = { diff --git a/src/ROUTES.ts b/src/ROUTES.ts index a6eb3c1166df..bddfc9756c08 100644 --- a/src/ROUTES.ts +++ b/src/ROUTES.ts @@ -1316,6 +1316,26 @@ const ROUTES = { route: 'settings/workspaces/:policyID/per-diem/settings', getRoute: (policyID: string) => `settings/workspaces/${policyID}/per-diem/settings` as const, }, + WORKSPACE_PER_DIEM_DETAILS: { + route: 'settings/workspaces/:policyID/per-diem/details/:rateID/:subRateID', + getRoute: (policyID: string, rateID: string, subRateID: string) => `settings/workspaces/${policyID}/per-diem/details/${rateID}/${subRateID}` as const, + }, + WORKSPACE_PER_DIEM_EDIT_DESTINATION: { + route: 'settings/workspaces/:policyID/per-diem/edit/destination/:rateID/:subRateID', + getRoute: (policyID: string, rateID: string, subRateID: string) => `settings/workspaces/${policyID}/per-diem/edit/destination/${rateID}/${subRateID}` as const, + }, + WORKSPACE_PER_DIEM_EDIT_SUBRATE: { + route: 'settings/workspaces/:policyID/per-diem/edit/subrate/:rateID/:subRateID', + getRoute: (policyID: string, rateID: string, subRateID: string) => `settings/workspaces/${policyID}/per-diem/edit/subrate/${rateID}/${subRateID}` as const, + }, + WORKSPACE_PER_DIEM_EDIT_AMOUNT: { + route: 'settings/workspaces/:policyID/per-diem/edit/amount/:rateID/:subRateID', + getRoute: (policyID: string, rateID: string, subRateID: string) => `settings/workspaces/${policyID}/per-diem/edit/amount/${rateID}/${subRateID}` as const, + }, + WORKSPACE_PER_DIEM_EDIT_CURRENCY: { + route: 'settings/workspaces/:policyID/per-diem/edit/currency/:rateID/:subRateID', + getRoute: (policyID: string, rateID: string, subRateID: string) => `settings/workspaces/${policyID}/per-diem/edit/currency/${rateID}/${subRateID}` as const, + }, RULES_CUSTOM_NAME: { route: 'settings/workspaces/:policyID/rules/name', getRoute: (policyID: string) => `settings/workspaces/${policyID}/rules/name` as const, diff --git a/src/SCREENS.ts b/src/SCREENS.ts index e4fa03bf4815..1e0e4d1e5fde 100644 --- a/src/SCREENS.ts +++ b/src/SCREENS.ts @@ -550,6 +550,11 @@ const SCREENS = { PER_DIEM_IMPORT: 'Per_Diem_Import', PER_DIEM_IMPORTED: 'Per_Diem_Imported', PER_DIEM_SETTINGS: 'Per_Diem_Settings', + PER_DIEM_DETAILS: 'Per_Diem_Details', + PER_DIEM_EDIT_DESTINATION: 'Per_Diem_Edit_Destination', + PER_DIEM_EDIT_SUBRATE: 'Per_Diem_Edit_Subrate', + PER_DIEM_EDIT_AMOUNT: 'Per_Diem_Edit_Amount', + PER_DIEM_EDIT_CURRENCY: 'Per_Diem_Edit_Currency', }, EDIT_REQUEST: { diff --git a/src/components/AmountWithoutCurrencyForm.tsx b/src/components/AmountWithoutCurrencyForm.tsx index 78b7c84ecb54..f94a2eb84f20 100644 --- a/src/components/AmountWithoutCurrencyForm.tsx +++ b/src/components/AmountWithoutCurrencyForm.tsx @@ -12,10 +12,13 @@ type AmountFormProps = { /** Callback to update the amount in the FormProvider */ onInputChange?: (value: string) => void; + + /** Should we allow negative number as valid input */ + shouldAllowNegative?: boolean; } & Partial; function AmountWithoutCurrencyForm( - {value: amount, onInputChange, inputID, name, defaultValue, accessibilityLabel, role, label, ...rest}: AmountFormProps, + {value: amount, onInputChange, shouldAllowNegative = false, inputID, name, defaultValue, accessibilityLabel, role, label, ...rest}: AmountFormProps, ref: ForwardedRef, ) { const {toLocaleDigit} = useLocalize(); @@ -32,13 +35,13 @@ function AmountWithoutCurrencyForm( // More info: https://github.com/Expensify/App/issues/16974 const newAmountWithoutSpaces = stripSpacesFromAmount(newAmount); const replacedCommasAmount = replaceCommasWithPeriod(newAmountWithoutSpaces); - const withLeadingZero = addLeadingZero(replacedCommasAmount); - if (!validateAmount(withLeadingZero, 2)) { + const withLeadingZero = addLeadingZero(replacedCommasAmount, shouldAllowNegative); + if (!validateAmount(withLeadingZero, 2, CONST.IOU.AMOUNT_MAX_LENGTH, shouldAllowNegative)) { return; } onInputChange?.(withLeadingZero); }, - [onInputChange], + [onInputChange, shouldAllowNegative], ); const formattedAmount = replaceAllDigits(currentAmount, toLocaleDigit); @@ -54,7 +57,7 @@ function AmountWithoutCurrencyForm( accessibilityLabel={accessibilityLabel} role={role} ref={ref} - keyboardType={CONST.KEYBOARD_TYPE.DECIMAL_PAD} + keyboardType={!shouldAllowNegative ? CONST.KEYBOARD_TYPE.DECIMAL_PAD : undefined} // eslint-disable-next-line react/jsx-props-no-spreading {...rest} /> diff --git a/src/languages/en.ts b/src/languages/en.ts index d79695ed8b48..eca91dbd5f95 100755 --- a/src/languages/en.ts +++ b/src/languages/en.ts @@ -61,6 +61,7 @@ import type { DeleteConfirmationParams, DidSplitAmountMessageParams, EditActionParams, + EditDestinationSubtitleParams, ElectronicFundsParams, EnterMagicCodeParams, ExportAgainModalDescriptionParams, @@ -2569,6 +2570,9 @@ const translations = { existingRateError: ({rate}: CustomUnitRateParams) => `A rate with value ${rate} already exists.`, }, importPerDiemRates: 'Import per diem rates', + editPerDiemRate: 'Edit per diem rate', + editDestinationSubtitle: ({destination}: EditDestinationSubtitleParams) => `Updating this destination will change it for all ${destination} per diem subrates.`, + editCurrencySubtitle: ({destination}: EditDestinationSubtitleParams) => `Updating this currency will change it for all ${destination} per diem subrates.`, }, qbd: { exportOutOfPocketExpensesDescription: 'Set how out-of-pocket expenses export to QuickBooks Desktop.', diff --git a/src/languages/es.ts b/src/languages/es.ts index 5ce47db18d35..574aed309385 100644 --- a/src/languages/es.ts +++ b/src/languages/es.ts @@ -60,6 +60,7 @@ import type { DeleteConfirmationParams, DidSplitAmountMessageParams, EditActionParams, + EditDestinationSubtitleParams, ElectronicFundsParams, EnterMagicCodeParams, ExportAgainModalDescriptionParams, @@ -2593,6 +2594,9 @@ const translations = { existingRateError: ({rate}: CustomUnitRateParams) => `Ya existe una tasa con el valor ${rate}.`, }, importPerDiemRates: 'Importar tasas de per diem', + editPerDiemRate: 'Edit per diem rate', + editDestinationSubtitle: ({destination}: EditDestinationSubtitleParams) => `Updating this destination will change it for all ${destination} per diem subrates.`, + editCurrencySubtitle: ({destination}: EditDestinationSubtitleParams) => `Updating this currency will change it for all ${destination} per diem subrates.`, }, qbd: { exportOutOfPocketExpensesDescription: 'Establezca cómo se exportan los gastos de bolsillo a QuickBooks Desktop.', diff --git a/src/languages/params.ts b/src/languages/params.ts index 3088b99e753b..4ca58a17346b 100644 --- a/src/languages/params.ts +++ b/src/languages/params.ts @@ -571,6 +571,10 @@ type ChatWithAccountManagerParams = { accountManagerDisplayName: string; }; +type EditDestinationSubtitleParams = { + destination: string; +}; + export type { AuthenticationErrorParams, ImportMembersSuccessfullDescriptionParams, @@ -776,4 +780,5 @@ export type { CompanyNameParams, CustomUnitRateParams, ChatWithAccountManagerParams, + EditDestinationSubtitleParams, }; diff --git a/src/libs/API/parameters/UpdateWorkspaceCustomUnitParams.ts b/src/libs/API/parameters/UpdateWorkspaceCustomUnitParams.ts new file mode 100644 index 000000000000..8ff704c4a0dd --- /dev/null +++ b/src/libs/API/parameters/UpdateWorkspaceCustomUnitParams.ts @@ -0,0 +1,6 @@ +type UpdateWorkspaceCustomUnitParams = { + policyID: string; + customUnitData: string; +}; + +export default UpdateWorkspaceCustomUnitParams; diff --git a/src/libs/API/parameters/index.ts b/src/libs/API/parameters/index.ts index 6a510d074f98..5ae3af578542 100644 --- a/src/libs/API/parameters/index.ts +++ b/src/libs/API/parameters/index.ts @@ -352,3 +352,4 @@ export type {default as OpenPolicyPerDiemRatesPageParams} from './OpenPolicyPerD export type {default as TogglePlatformMuteParams} from './TogglePlatformMuteParams'; export type {default as ImportPerDiemRatesParams} from './ImportPerDiemRatesParams'; export type {default as ExportPerDiemCSVParams} from './ExportPerDiemCSVParams'; +export type {default as UpdateWorkspaceCustomUnitParams} from './UpdateWorkspaceCustomUnitParams'; diff --git a/src/libs/API/types.ts b/src/libs/API/types.ts index 892bad17928e..3cb6dccbd87e 100644 --- a/src/libs/API/types.ts +++ b/src/libs/API/types.ts @@ -438,6 +438,7 @@ const WRITE_COMMANDS = { SELF_TOUR_VIEWED: 'SelfTourViewed', UPDATE_INVOICE_COMPANY_NAME: 'UpdateInvoiceCompanyName', UPDATE_INVOICE_COMPANY_WEBSITE: 'UpdateInvoiceCompanyWebsite', + UPDATE_WORKSPACE_CUSTOM_UNIT: 'UpdateWorkspaceCustomUnit', } as const; type WriteCommand = ValueOf; @@ -762,6 +763,7 @@ type WriteCommandParameters = { [WRITE_COMMANDS.UPDATE_SUBSCRIPTION_ADD_NEW_USERS_AUTOMATICALLY]: Parameters.UpdateSubscriptionAddNewUsersAutomaticallyParams; [WRITE_COMMANDS.UPDATE_SUBSCRIPTION_SIZE]: Parameters.UpdateSubscriptionSizeParams; [WRITE_COMMANDS.REQUEST_TAX_EXEMPTION]: null; + [WRITE_COMMANDS.UPDATE_WORKSPACE_CUSTOM_UNIT]: Parameters.UpdateWorkspaceCustomUnitParams; [WRITE_COMMANDS.DELETE_MONEY_REQUEST_ON_SEARCH]: Parameters.DeleteMoneyRequestOnSearchParams; [WRITE_COMMANDS.HOLD_MONEY_REQUEST_ON_SEARCH]: Parameters.HoldMoneyRequestOnSearchParams; diff --git a/src/libs/MoneyRequestUtils.ts b/src/libs/MoneyRequestUtils.ts index 206bb8509af6..d76c9325cc0e 100644 --- a/src/libs/MoneyRequestUtils.ts +++ b/src/libs/MoneyRequestUtils.ts @@ -32,19 +32,23 @@ function stripDecimalsFromAmount(amount: string): string { * Adds a leading zero to the amount if user entered just the decimal separator * * @param amount - Changed amount from user input + * @param shouldAllowNegative - Should allow negative numbers */ -function addLeadingZero(amount: string): string { +function addLeadingZero(amount: string, shouldAllowNegative = false): string { + if (shouldAllowNegative && amount.startsWith('-.')) { + return `-0${amount}`; + } return amount.startsWith('.') ? `0${amount}` : amount; } /** * Check if amount is a decimal up to 3 digits */ -function validateAmount(amount: string, decimals: number, amountMaxLength: number = CONST.IOU.AMOUNT_MAX_LENGTH): boolean { +function validateAmount(amount: string, decimals: number, amountMaxLength: number = CONST.IOU.AMOUNT_MAX_LENGTH, shouldAllowNegative = false): boolean { const regexString = decimals === 0 - ? `^\\d{1,${amountMaxLength}}$` // Don't allow decimal point if decimals === 0 - : `^\\d{1,${amountMaxLength}}(\\.\\d{0,${decimals}})?$`; // Allow the decimal point and the desired number of digits after the point + ? `^${shouldAllowNegative ? '-?' : ''}\\d{1,${amountMaxLength}}$` // Don't allow decimal point if decimals === 0 + : `^${shouldAllowNegative ? '-?' : ''}\\d{1,${amountMaxLength}}(\\.\\d{0,${decimals}})?$`; // Allow the decimal point and the desired number of digits after the point const decimalNumberRegex = new RegExp(regexString, 'i'); return amount === '' || decimalNumberRegex.test(amount); } diff --git a/src/libs/Navigation/AppNavigator/ModalStackNavigators/index.tsx b/src/libs/Navigation/AppNavigator/ModalStackNavigators/index.tsx index 9822c60faaa8..d85433fda14a 100644 --- a/src/libs/Navigation/AppNavigator/ModalStackNavigators/index.tsx +++ b/src/libs/Navigation/AppNavigator/ModalStackNavigators/index.tsx @@ -573,6 +573,11 @@ const SettingsModalStackNavigator = createModalStackNavigator require('../../../../pages/workspace/perDiem/ImportPerDiemPage').default, [SCREENS.WORKSPACE.PER_DIEM_IMPORTED]: () => require('../../../../pages/workspace/perDiem/ImportedPerDiemPage').default, [SCREENS.WORKSPACE.PER_DIEM_SETTINGS]: () => require('../../../../pages/workspace/perDiem/WorkspacePerDiemSettingsPage').default, + [SCREENS.WORKSPACE.PER_DIEM_DETAILS]: () => require('../../../../pages/workspace/perDiem/WorkspacePerDiemDetailsPage').default, + [SCREENS.WORKSPACE.PER_DIEM_EDIT_DESTINATION]: () => require('../../../../pages/workspace/perDiem/EditPerDiemDestinationPage').default, + [SCREENS.WORKSPACE.PER_DIEM_EDIT_SUBRATE]: () => require('../../../../pages/workspace/perDiem/EditPerDiemSubratePage').default, + [SCREENS.WORKSPACE.PER_DIEM_EDIT_AMOUNT]: () => require('../../../../pages/workspace/perDiem/EditPerDiemAmountPage').default, + [SCREENS.WORKSPACE.PER_DIEM_EDIT_CURRENCY]: () => require('../../../../pages/workspace/perDiem/EditPerDiemCurrencyPage').default, }); const EnablePaymentsStackNavigator = createModalStackNavigator({ diff --git a/src/libs/Navigation/linkingConfig/FULL_SCREEN_TO_RHP_MAPPING.ts b/src/libs/Navigation/linkingConfig/FULL_SCREEN_TO_RHP_MAPPING.ts index f36f154819f5..bb3cfc3353de 100755 --- a/src/libs/Navigation/linkingConfig/FULL_SCREEN_TO_RHP_MAPPING.ts +++ b/src/libs/Navigation/linkingConfig/FULL_SCREEN_TO_RHP_MAPPING.ts @@ -245,7 +245,16 @@ const FULL_SCREEN_TO_RHP_MAPPING: Partial> = { SCREENS.WORKSPACE.RULES_MAX_EXPENSE_AGE, SCREENS.WORKSPACE.RULES_BILLABLE_DEFAULT, ], - [SCREENS.WORKSPACE.PER_DIEM]: [SCREENS.WORKSPACE.PER_DIEM_IMPORT, SCREENS.WORKSPACE.PER_DIEM_IMPORTED, SCREENS.WORKSPACE.PER_DIEM_SETTINGS], + [SCREENS.WORKSPACE.PER_DIEM]: [ + SCREENS.WORKSPACE.PER_DIEM_IMPORT, + SCREENS.WORKSPACE.PER_DIEM_IMPORTED, + SCREENS.WORKSPACE.PER_DIEM_SETTINGS, + SCREENS.WORKSPACE.PER_DIEM_DETAILS, + SCREENS.WORKSPACE.PER_DIEM_EDIT_DESTINATION, + SCREENS.WORKSPACE.PER_DIEM_EDIT_SUBRATE, + SCREENS.WORKSPACE.PER_DIEM_EDIT_AMOUNT, + SCREENS.WORKSPACE.PER_DIEM_EDIT_CURRENCY, + ], }; export default FULL_SCREEN_TO_RHP_MAPPING; diff --git a/src/libs/Navigation/linkingConfig/config.ts b/src/libs/Navigation/linkingConfig/config.ts index 02f7f6950a0d..0b894c5810ce 100644 --- a/src/libs/Navigation/linkingConfig/config.ts +++ b/src/libs/Navigation/linkingConfig/config.ts @@ -953,6 +953,21 @@ const config: LinkingOptions['config'] = { [SCREENS.WORKSPACE.PER_DIEM_SETTINGS]: { path: ROUTES.WORKSPACE_PER_DIEM_SETTINGS.route, }, + [SCREENS.WORKSPACE.PER_DIEM_DETAILS]: { + path: ROUTES.WORKSPACE_PER_DIEM_DETAILS.route, + }, + [SCREENS.WORKSPACE.PER_DIEM_EDIT_DESTINATION]: { + path: ROUTES.WORKSPACE_PER_DIEM_EDIT_DESTINATION.route, + }, + [SCREENS.WORKSPACE.PER_DIEM_EDIT_SUBRATE]: { + path: ROUTES.WORKSPACE_PER_DIEM_EDIT_SUBRATE.route, + }, + [SCREENS.WORKSPACE.PER_DIEM_EDIT_AMOUNT]: { + path: ROUTES.WORKSPACE_PER_DIEM_EDIT_AMOUNT.route, + }, + [SCREENS.WORKSPACE.PER_DIEM_EDIT_CURRENCY]: { + path: ROUTES.WORKSPACE_PER_DIEM_EDIT_CURRENCY.route, + }, }, }, [SCREENS.RIGHT_MODAL.PRIVATE_NOTES]: { diff --git a/src/libs/Navigation/types.ts b/src/libs/Navigation/types.ts index 3674a24a907b..012526f801bd 100644 --- a/src/libs/Navigation/types.ts +++ b/src/libs/Navigation/types.ts @@ -908,6 +908,31 @@ type SettingsNavigatorParamList = { [SCREENS.WORKSPACE.PER_DIEM_SETTINGS]: { policyID: string; }; + [SCREENS.WORKSPACE.PER_DIEM_DETAILS]: { + policyID: string; + rateID: string; + subRateID: string; + }; + [SCREENS.WORKSPACE.PER_DIEM_EDIT_DESTINATION]: { + policyID: string; + rateID: string; + subRateID: string; + }; + [SCREENS.WORKSPACE.PER_DIEM_EDIT_SUBRATE]: { + policyID: string; + rateID: string; + subRateID: string; + }; + [SCREENS.WORKSPACE.PER_DIEM_EDIT_AMOUNT]: { + policyID: string; + rateID: string; + subRateID: string; + }; + [SCREENS.WORKSPACE.PER_DIEM_EDIT_CURRENCY]: { + policyID: string; + rateID: string; + subRateID: string; + }; } & ReimbursementAccountNavigatorParamList; type NewChatNavigatorParamList = { diff --git a/src/libs/actions/Policy/PerDiem.ts b/src/libs/actions/Policy/PerDiem.ts index 81898dfb34e0..5f11eeff0b97 100644 --- a/src/libs/actions/Policy/PerDiem.ts +++ b/src/libs/actions/Policy/PerDiem.ts @@ -1,3 +1,4 @@ +import lodashDeepClone from 'lodash/cloneDeep'; import type {NullishDeep, OnyxCollection} from 'react-native-onyx'; import Onyx from 'react-native-onyx'; import * as API from '@libs/API'; @@ -13,9 +14,10 @@ import * as ReportUtils from '@libs/ReportUtils'; import CONST from '@src/CONST'; import ONYXKEYS from '@src/ONYXKEYS'; import type {Policy, Report} from '@src/types/onyx'; -import type {ErrorFields} from '@src/types/onyx/OnyxCommon'; -import type {Rate} from '@src/types/onyx/Policy'; +import type {ErrorFields, PendingAction} from '@src/types/onyx/OnyxCommon'; +import type {CustomUnit, Rate} from '@src/types/onyx/Policy'; import type {OnyxData} from '@src/types/onyx/Request'; +import {isEmptyObject} from '@src/types/utils/EmptyObject'; const allPolicies: OnyxCollection = {}; Onyx.connect({ @@ -50,6 +52,16 @@ Onyx.connect({ }, }); +type SubRateData = { + pendingAction?: PendingAction; + destination: string; + subRateName: string; + rate: number; + currency: string; + rateID: string; + subRateID: string; +}; + /** * Returns a client generated 13 character hexadecimal value for a custom unit ID */ @@ -193,4 +205,201 @@ function clearPolicyPerDiemRatesErrorFields(policyID: string, customUnitID: stri }); } -export {generateCustomUnitID, enablePerDiem, openPolicyPerDiemPage, importPerDiemRates, downloadPerDiemCSV, clearPolicyPerDiemRatesErrorFields}; +function prepareNewCustomUnit(customUnit: CustomUnit, subRatesToBeDeleted: SubRateData[]) { + const mappedDeletedSubRatesToRate = subRatesToBeDeleted.reduce((acc, subRate) => { + if (subRate.rateID in acc) { + acc[subRate.rateID].push(subRate); + } else { + acc[subRate.rateID] = [subRate]; + } + return acc; + }, {} as Record); + + // Copy the custom unit and remove the sub rates that are to be deleted + const newCustomUnit: CustomUnit = lodashDeepClone(customUnit); + for (const rateID in mappedDeletedSubRatesToRate) { + if (!(rateID in newCustomUnit.rates)) { + // eslint-disable-next-line no-continue + continue; + } + const subRates = mappedDeletedSubRatesToRate[rateID]; + if (subRates.length === newCustomUnit.rates[rateID].subRates?.length) { + delete newCustomUnit.rates[rateID]; + } else { + const newSubRates = newCustomUnit.rates[rateID].subRates?.filter((subRate) => !subRates.some((subRateToBeDeleted) => subRateToBeDeleted.subRateID === subRate.id)); + newCustomUnit.rates[rateID].subRates = newSubRates; + } + } + return newCustomUnit; +} + +function deleteWorkspacePerDiemRates(policyID: string, customUnit: CustomUnit | undefined, subRatesToBeDeleted: SubRateData[]) { + if (!policyID || isEmptyObject(customUnit) || !subRatesToBeDeleted.length) { + return; + } + const newCustomUnit = prepareNewCustomUnit(customUnit, subRatesToBeDeleted); + const onyxData: OnyxData = { + optimisticData: [ + { + onyxMethod: Onyx.METHOD.MERGE, + key: `${ONYXKEYS.COLLECTION.POLICY}${policyID}`, + value: { + customUnits: { + [customUnit.customUnitID]: newCustomUnit, + }, + }, + }, + ], + }; + + const parameters = { + policyID, + customUnitData: JSON.stringify(newCustomUnit), + }; + + API.write(WRITE_COMMANDS.UPDATE_WORKSPACE_CUSTOM_UNIT, parameters, onyxData); +} + +function editPerDiemRateDestination(policyID: string, rateID: string, customUnit: CustomUnit | undefined, newDestination: string) { + if (!policyID || !rateID || isEmptyObject(customUnit) || !newDestination) { + return; + } + + const newCustomUnit: CustomUnit = lodashDeepClone(customUnit); + newCustomUnit.rates[rateID].name = newDestination; + + const onyxData: OnyxData = { + optimisticData: [ + { + onyxMethod: Onyx.METHOD.MERGE, + key: `${ONYXKEYS.COLLECTION.POLICY}${policyID}`, + value: { + customUnits: { + [customUnit.customUnitID]: newCustomUnit, + }, + }, + }, + ], + }; + + const parameters = { + policyID, + customUnitData: JSON.stringify(newCustomUnit), + }; + + API.write(WRITE_COMMANDS.UPDATE_WORKSPACE_CUSTOM_UNIT, parameters, onyxData); +} + +function editPerDiemRateSubrate(policyID: string, rateID: string, subRateID: string, customUnit: CustomUnit | undefined, newSubrate: string) { + if (!policyID || !rateID || isEmptyObject(customUnit) || !newSubrate) { + return; + } + + const newCustomUnit: CustomUnit = lodashDeepClone(customUnit); + newCustomUnit.rates[rateID].subRates = newCustomUnit.rates[rateID].subRates?.map((subRate) => { + if (subRate.id === subRateID) { + return {...subRate, name: newSubrate}; + } + return subRate; + }); + + const onyxData: OnyxData = { + optimisticData: [ + { + onyxMethod: Onyx.METHOD.MERGE, + key: `${ONYXKEYS.COLLECTION.POLICY}${policyID}`, + value: { + customUnits: { + [customUnit.customUnitID]: newCustomUnit, + }, + }, + }, + ], + }; + + const parameters = { + policyID, + customUnitData: JSON.stringify(newCustomUnit), + }; + + API.write(WRITE_COMMANDS.UPDATE_WORKSPACE_CUSTOM_UNIT, parameters, onyxData); +} + +function editPerDiemRateAmount(policyID: string, rateID: string, subRateID: string, customUnit: CustomUnit | undefined, newAmount: number) { + if (!policyID || !rateID || isEmptyObject(customUnit) || !newAmount) { + return; + } + + const newCustomUnit: CustomUnit = lodashDeepClone(customUnit); + newCustomUnit.rates[rateID].subRates = newCustomUnit.rates[rateID].subRates?.map((subRate) => { + if (subRate.id === subRateID) { + return {...subRate, rate: newAmount}; + } + return subRate; + }); + + const onyxData: OnyxData = { + optimisticData: [ + { + onyxMethod: Onyx.METHOD.MERGE, + key: `${ONYXKEYS.COLLECTION.POLICY}${policyID}`, + value: { + customUnits: { + [customUnit.customUnitID]: newCustomUnit, + }, + }, + }, + ], + }; + + const parameters = { + policyID, + customUnitData: JSON.stringify(newCustomUnit), + }; + + API.write(WRITE_COMMANDS.UPDATE_WORKSPACE_CUSTOM_UNIT, parameters, onyxData); +} + +function editPerDiemRateCurrency(policyID: string, rateID: string, customUnit: CustomUnit | undefined, newCurrency: string) { + if (!policyID || !rateID || isEmptyObject(customUnit) || !newCurrency) { + return; + } + + const newCustomUnit: CustomUnit = lodashDeepClone(customUnit); + newCustomUnit.rates[rateID].currency = newCurrency; + + const onyxData: OnyxData = { + optimisticData: [ + { + onyxMethod: Onyx.METHOD.MERGE, + key: `${ONYXKEYS.COLLECTION.POLICY}${policyID}`, + value: { + customUnits: { + [customUnit.customUnitID]: newCustomUnit, + }, + }, + }, + ], + }; + + const parameters = { + policyID, + customUnitData: JSON.stringify(newCustomUnit), + }; + + API.write(WRITE_COMMANDS.UPDATE_WORKSPACE_CUSTOM_UNIT, parameters, onyxData); +} + +export { + generateCustomUnitID, + enablePerDiem, + openPolicyPerDiemPage, + importPerDiemRates, + downloadPerDiemCSV, + clearPolicyPerDiemRatesErrorFields, + deleteWorkspacePerDiemRates, + editPerDiemRateDestination, + editPerDiemRateSubrate, + editPerDiemRateAmount, + editPerDiemRateCurrency, +}; diff --git a/src/pages/workspace/perDiem/EditPerDiemAmountPage.tsx b/src/pages/workspace/perDiem/EditPerDiemAmountPage.tsx new file mode 100644 index 000000000000..686620d58a20 --- /dev/null +++ b/src/pages/workspace/perDiem/EditPerDiemAmountPage.tsx @@ -0,0 +1,116 @@ +import React, {useCallback} from 'react'; +import {useOnyx} from 'react-native-onyx'; +import AmountWithoutCurrencyForm from '@components/AmountWithoutCurrencyForm'; +import FormProvider from '@components/Form/FormProvider'; +import InputWrapper from '@components/Form/InputWrapper'; +import type {FormInputErrors, FormOnyxValues} from '@components/Form/types'; +import HeaderWithBackButton from '@components/HeaderWithBackButton'; +import ScreenWrapper from '@components/ScreenWrapper'; +import useAutoFocusInput from '@hooks/useAutoFocusInput'; +import useLocalize from '@hooks/useLocalize'; +import useThemeStyles from '@hooks/useThemeStyles'; +import {convertToBackendAmount, convertToFrontendAmountAsString} from '@libs/CurrencyUtils'; +import Navigation from '@libs/Navigation/Navigation'; +import type {PlatformStackScreenProps} from '@libs/Navigation/PlatformStackNavigation/types'; +import {getPerDiemCustomUnit} from '@libs/PolicyUtils'; +import type {SettingsNavigatorParamList} from '@navigation/types'; +import AccessOrNotFoundWrapper from '@pages/workspace/AccessOrNotFoundWrapper'; +import * as PerDiem from '@userActions/Policy/PerDiem'; +import CONST from '@src/CONST'; +import ONYXKEYS from '@src/ONYXKEYS'; +import ROUTES from '@src/ROUTES'; +import type SCREENS from '@src/SCREENS'; +import INPUT_IDS from '@src/types/form/WorkspacePerDiemForm'; +import {isEmptyObject} from '@src/types/utils/EmptyObject'; + +type EditPerDiemAmountPageProps = PlatformStackScreenProps; + +function EditPerDiemAmountPage({route}: EditPerDiemAmountPageProps) { + const styles = useThemeStyles(); + const {translate} = useLocalize(); + const policyID = route.params.policyID ?? '-1'; + const rateID = route.params.rateID; + const subRateID = route.params.subRateID; + const [policy] = useOnyx(`${ONYXKEYS.COLLECTION.POLICY}${policyID}`); + + const customUnit = getPerDiemCustomUnit(policy); + + const selectedRate = customUnit?.rates?.[rateID]; + const selectedSubrate = selectedRate?.subRates?.find((subRate) => subRate.id === subRateID); + + const defaultAmount = selectedSubrate?.rate ? convertToFrontendAmountAsString(Number(selectedSubrate.rate)) : undefined; + + const {inputCallbackRef} = useAutoFocusInput(); + + const validate = useCallback( + (values: FormOnyxValues): FormInputErrors => { + const errors: FormInputErrors = {}; + + const newAmount = values.amount.trim(); + const backendAmount = newAmount ? convertToBackendAmount(Number(newAmount)) : 0; + + if (backendAmount === 0) { + errors.amount = translate('common.error.fieldRequired'); + } + + return errors; + }, + [translate], + ); + + const editAmount = useCallback( + (values: FormOnyxValues) => { + const newAmount = values.amount.trim(); + const backendAmount = newAmount ? convertToBackendAmount(Number(newAmount)) : 0; + if (backendAmount !== Number(selectedSubrate?.rate)) { + PerDiem.editPerDiemRateAmount(policyID, rateID, subRateID, customUnit, backendAmount); + } + Navigation.goBack(ROUTES.WORKSPACE_PER_DIEM_DETAILS.getRoute(policyID, rateID, subRateID)); + }, + [selectedSubrate?.rate, policyID, rateID, subRateID, customUnit], + ); + + return ( + + + Navigation.goBack(ROUTES.WORKSPACE_PER_DIEM_DETAILS.getRoute(policyID, rateID, subRateID))} + /> + + + + + + ); +} + +EditPerDiemAmountPage.displayName = 'EditPerDiemAmountPage'; + +export default EditPerDiemAmountPage; diff --git a/src/pages/workspace/perDiem/EditPerDiemCurrencyPage.tsx b/src/pages/workspace/perDiem/EditPerDiemCurrencyPage.tsx new file mode 100644 index 000000000000..b142372c2411 --- /dev/null +++ b/src/pages/workspace/perDiem/EditPerDiemCurrencyPage.tsx @@ -0,0 +1,80 @@ +import React, {useCallback} from 'react'; +import {View} from 'react-native'; +import {useOnyx} from 'react-native-onyx'; +import CurrencySelectionList from '@components/CurrencySelectionList'; +import type {CurrencyListItem} from '@components/CurrencySelectionList/types'; +import HeaderWithBackButton from '@components/HeaderWithBackButton'; +import ScreenWrapper from '@components/ScreenWrapper'; +import Text from '@components/Text'; +import useLocalize from '@hooks/useLocalize'; +import useThemeStyles from '@hooks/useThemeStyles'; +import Navigation from '@libs/Navigation/Navigation'; +import type {PlatformStackScreenProps} from '@libs/Navigation/PlatformStackNavigation/types'; +import {getPerDiemCustomUnit} from '@libs/PolicyUtils'; +import type {SettingsNavigatorParamList} from '@navigation/types'; +import AccessOrNotFoundWrapper from '@pages/workspace/AccessOrNotFoundWrapper'; +import * as PerDiem from '@userActions/Policy/PerDiem'; +import CONST from '@src/CONST'; +import ONYXKEYS from '@src/ONYXKEYS'; +import ROUTES from '@src/ROUTES'; +import type SCREENS from '@src/SCREENS'; +import {isEmptyObject} from '@src/types/utils/EmptyObject'; + +type EditPerDiemCurrencyPageProps = PlatformStackScreenProps; + +function EditPerDiemCurrencyPage({route}: EditPerDiemCurrencyPageProps) { + const styles = useThemeStyles(); + const {translate} = useLocalize(); + const policyID = route.params.policyID ?? '-1'; + const rateID = route.params.rateID; + const subRateID = route.params.subRateID; + const [policy] = useOnyx(`${ONYXKEYS.COLLECTION.POLICY}${policyID}`); + + const customUnit = getPerDiemCustomUnit(policy); + + const selectedRate = customUnit?.rates?.[rateID]; + + const editCurrency = useCallback( + (item: CurrencyListItem) => { + const newCurrency = item.currencyCode; + if (newCurrency !== selectedRate?.currency) { + PerDiem.editPerDiemRateCurrency(policyID, rateID, customUnit, newCurrency); + } + Navigation.goBack(ROUTES.WORKSPACE_PER_DIEM_DETAILS.getRoute(policyID, rateID, subRateID)); + }, + [selectedRate?.currency, policyID, rateID, subRateID, customUnit], + ); + + return ( + + + Navigation.goBack(ROUTES.WORKSPACE_PER_DIEM_DETAILS.getRoute(policyID, rateID, subRateID))} + /> + + {translate('workspace.perDiem.editCurrencySubtitle', {destination: selectedRate?.name ?? ''})} + + + + + ); +} + +EditPerDiemCurrencyPage.displayName = 'EditPerDiemCurrencyPage'; + +export default EditPerDiemCurrencyPage; diff --git a/src/pages/workspace/perDiem/EditPerDiemDestinationPage.tsx b/src/pages/workspace/perDiem/EditPerDiemDestinationPage.tsx new file mode 100644 index 000000000000..cca12b1f21d2 --- /dev/null +++ b/src/pages/workspace/perDiem/EditPerDiemDestinationPage.tsx @@ -0,0 +1,115 @@ +import React, {useCallback} from 'react'; +import {View} from 'react-native'; +import {useOnyx} from 'react-native-onyx'; +import FormProvider from '@components/Form/FormProvider'; +import InputWrapper from '@components/Form/InputWrapper'; +import type {FormInputErrors, FormOnyxValues} from '@components/Form/types'; +import HeaderWithBackButton from '@components/HeaderWithBackButton'; +import ScreenWrapper from '@components/ScreenWrapper'; +import Text from '@components/Text'; +import TextInput from '@components/TextInput'; +import useAutoFocusInput from '@hooks/useAutoFocusInput'; +import useLocalize from '@hooks/useLocalize'; +import useThemeStyles from '@hooks/useThemeStyles'; +import Navigation from '@libs/Navigation/Navigation'; +import type {PlatformStackScreenProps} from '@libs/Navigation/PlatformStackNavigation/types'; +import {getPerDiemCustomUnit} from '@libs/PolicyUtils'; +import type {SettingsNavigatorParamList} from '@navigation/types'; +import AccessOrNotFoundWrapper from '@pages/workspace/AccessOrNotFoundWrapper'; +import * as PerDiem from '@userActions/Policy/PerDiem'; +import CONST from '@src/CONST'; +import ONYXKEYS from '@src/ONYXKEYS'; +import ROUTES from '@src/ROUTES'; +import type SCREENS from '@src/SCREENS'; +import INPUT_IDS from '@src/types/form/WorkspacePerDiemForm'; +import {isEmptyObject} from '@src/types/utils/EmptyObject'; + +type EditPerDiemDestinationPageProps = PlatformStackScreenProps; + +function EditPerDiemDestinationPage({route}: EditPerDiemDestinationPageProps) { + const styles = useThemeStyles(); + const {translate} = useLocalize(); + const policyID = route.params.policyID ?? '-1'; + const rateID = route.params.rateID; + const subRateID = route.params.subRateID; + const [policy] = useOnyx(`${ONYXKEYS.COLLECTION.POLICY}${policyID}`); + + const customUnit = getPerDiemCustomUnit(policy); + + const selectedRate = customUnit?.rates?.[rateID]; + + const {inputCallbackRef} = useAutoFocusInput(); + + const validate = useCallback( + (values: FormOnyxValues): FormInputErrors => { + const errors: FormInputErrors = {}; + + if (!values.destination.trim()) { + errors.destination = translate('common.error.fieldRequired'); + } + + return errors; + }, + [translate], + ); + + const editDestination = useCallback( + (values: FormOnyxValues) => { + const newDestination = values.destination.trim(); + if (newDestination !== selectedRate?.name) { + PerDiem.editPerDiemRateDestination(policyID, rateID, customUnit, newDestination); + } + Navigation.goBack(ROUTES.WORKSPACE_PER_DIEM_DETAILS.getRoute(policyID, rateID, subRateID)); + }, + [selectedRate?.name, policyID, rateID, subRateID, customUnit], + ); + + return ( + + + Navigation.goBack(ROUTES.WORKSPACE_PER_DIEM_DETAILS.getRoute(policyID, rateID, subRateID))} + /> + + + + {translate('workspace.perDiem.editDestinationSubtitle', {destination: selectedRate?.name ?? ''})} + + + + + + + ); +} + +EditPerDiemDestinationPage.displayName = 'EditPerDiemDestinationPage'; + +export default EditPerDiemDestinationPage; diff --git a/src/pages/workspace/perDiem/EditPerDiemSubratePage.tsx b/src/pages/workspace/perDiem/EditPerDiemSubratePage.tsx new file mode 100644 index 000000000000..84a437eab4ed --- /dev/null +++ b/src/pages/workspace/perDiem/EditPerDiemSubratePage.tsx @@ -0,0 +1,109 @@ +import React, {useCallback} from 'react'; +import {useOnyx} from 'react-native-onyx'; +import FormProvider from '@components/Form/FormProvider'; +import InputWrapper from '@components/Form/InputWrapper'; +import type {FormInputErrors, FormOnyxValues} from '@components/Form/types'; +import HeaderWithBackButton from '@components/HeaderWithBackButton'; +import ScreenWrapper from '@components/ScreenWrapper'; +import TextInput from '@components/TextInput'; +import useAutoFocusInput from '@hooks/useAutoFocusInput'; +import useLocalize from '@hooks/useLocalize'; +import useThemeStyles from '@hooks/useThemeStyles'; +import Navigation from '@libs/Navigation/Navigation'; +import type {PlatformStackScreenProps} from '@libs/Navigation/PlatformStackNavigation/types'; +import {getPerDiemCustomUnit} from '@libs/PolicyUtils'; +import type {SettingsNavigatorParamList} from '@navigation/types'; +import AccessOrNotFoundWrapper from '@pages/workspace/AccessOrNotFoundWrapper'; +import * as PerDiem from '@userActions/Policy/PerDiem'; +import CONST from '@src/CONST'; +import ONYXKEYS from '@src/ONYXKEYS'; +import ROUTES from '@src/ROUTES'; +import type SCREENS from '@src/SCREENS'; +import INPUT_IDS from '@src/types/form/WorkspacePerDiemForm'; +import {isEmptyObject} from '@src/types/utils/EmptyObject'; + +type EditPerDiemSubratePageProps = PlatformStackScreenProps; + +function EditPerDiemSubratePage({route}: EditPerDiemSubratePageProps) { + const styles = useThemeStyles(); + const {translate} = useLocalize(); + const policyID = route.params.policyID ?? '-1'; + const rateID = route.params.rateID; + const subRateID = route.params.subRateID; + const [policy] = useOnyx(`${ONYXKEYS.COLLECTION.POLICY}${policyID}`); + + const customUnit = getPerDiemCustomUnit(policy); + + const selectedRate = customUnit?.rates?.[rateID]; + const selectedSubrate = selectedRate?.subRates?.find((subRate) => subRate.id === subRateID); + + const {inputCallbackRef} = useAutoFocusInput(); + + const validate = useCallback( + (values: FormOnyxValues): FormInputErrors => { + const errors: FormInputErrors = {}; + + if (!values.subrate.trim()) { + errors.subrate = translate('common.error.fieldRequired'); + } + + return errors; + }, + [translate], + ); + + const editSubrate = useCallback( + (values: FormOnyxValues) => { + const newSubrate = values.subrate.trim(); + if (newSubrate !== selectedSubrate?.name) { + PerDiem.editPerDiemRateSubrate(policyID, rateID, subRateID, customUnit, newSubrate); + } + Navigation.goBack(ROUTES.WORKSPACE_PER_DIEM_DETAILS.getRoute(policyID, rateID, subRateID)); + }, + [selectedSubrate?.name, policyID, rateID, subRateID, customUnit], + ); + + return ( + + + Navigation.goBack(ROUTES.WORKSPACE_PER_DIEM_DETAILS.getRoute(policyID, rateID, subRateID))} + /> + + + + + + ); +} + +EditPerDiemSubratePage.displayName = 'EditPerDiemSubratePage'; + +export default EditPerDiemSubratePage; diff --git a/src/pages/workspace/perDiem/WorkspacePerDiemDetailsPage.tsx b/src/pages/workspace/perDiem/WorkspacePerDiemDetailsPage.tsx new file mode 100644 index 000000000000..d1dea9c99f77 --- /dev/null +++ b/src/pages/workspace/perDiem/WorkspacePerDiemDetailsPage.tsx @@ -0,0 +1,128 @@ +import React, {useState} from 'react'; +import {View} from 'react-native'; +import {useOnyx} from 'react-native-onyx'; +import FullPageOfflineBlockingView from '@components/BlockingViews/FullPageOfflineBlockingView'; +import ConfirmModal from '@components/ConfirmModal'; +import HeaderWithBackButton from '@components/HeaderWithBackButton'; +import * as Expensicons from '@components/Icon/Expensicons'; +import MenuItem from '@components/MenuItem'; +import MenuItemWithTopDescription from '@components/MenuItemWithTopDescription'; +import ScreenWrapper from '@components/ScreenWrapper'; +import ScrollView from '@components/ScrollView'; +import useLocalize from '@hooks/useLocalize'; +import useThemeStyles from '@hooks/useThemeStyles'; +import {convertToFrontendAmountAsString, getCurrencySymbol} from '@libs/CurrencyUtils'; +import Navigation from '@libs/Navigation/Navigation'; +import type {PlatformStackScreenProps} from '@libs/Navigation/PlatformStackNavigation/types'; +import {getPerDiemCustomUnit} from '@libs/PolicyUtils'; +import type {SettingsNavigatorParamList} from '@navigation/types'; +import AccessOrNotFoundWrapper from '@pages/workspace/AccessOrNotFoundWrapper'; +import * as PerDiem from '@userActions/Policy/PerDiem'; +import CONST from '@src/CONST'; +import ONYXKEYS from '@src/ONYXKEYS'; +import ROUTES from '@src/ROUTES'; +import type SCREENS from '@src/SCREENS'; +import {isEmptyObject} from '@src/types/utils/EmptyObject'; + +type WorkspacePerDiemDetailsPageProps = PlatformStackScreenProps; + +function WorkspacePerDiemDetailsPage({route}: WorkspacePerDiemDetailsPageProps) { + const policyID = route.params.policyID; + const rateID = route.params.rateID; + const subRateID = route.params.subRateID; + const [deletePerDiemConfirmModalVisible, setDeletePerDiemConfirmModalVisible] = useState(false); + const [policy] = useOnyx(`${ONYXKEYS.COLLECTION.POLICY}${policyID}`); + + const styles = useThemeStyles(); + const {translate} = useLocalize(); + const customUnit = getPerDiemCustomUnit(policy); + + const selectedRate = customUnit?.rates?.[rateID]; + const selectedSubRate = selectedRate?.subRates?.find((subRate) => subRate.id === subRateID); + + const amountValue = selectedSubRate?.rate ? convertToFrontendAmountAsString(Number(selectedSubRate.rate)) : undefined; + const currencyValue = selectedRate?.currency ? `${selectedRate.currency} - ${getCurrencySymbol(selectedRate.currency)}` : undefined; + + const FullPageBlockingView = isEmptyObject(selectedSubRate) ? FullPageOfflineBlockingView : View; + + const handleDeletePerDiemRate = () => { + PerDiem.deleteWorkspacePerDiemRates(policyID, customUnit, [ + { + destination: selectedRate?.name ?? '', + subRateName: selectedSubRate?.name ?? '', + rate: selectedSubRate?.rate ?? 0, + currency: selectedRate?.currency ?? '', + rateID, + subRateID, + }, + ]); + setDeletePerDiemConfirmModalVisible(false); + Navigation.goBack(); + }; + + return ( + + + + setDeletePerDiemConfirmModalVisible(false)} + title={translate('workspace.perDiem.deletePerDiemRate')} + prompt={translate('workspace.perDiem.areYouSureDelete', {count: 1})} + confirmText={translate('common.delete')} + cancelText={translate('common.cancel')} + danger + /> + + + Navigation.navigate(ROUTES.WORKSPACE_PER_DIEM_EDIT_DESTINATION.getRoute(policyID, rateID, subRateID))} + shouldShowRightIcon + /> + Navigation.navigate(ROUTES.WORKSPACE_PER_DIEM_EDIT_SUBRATE.getRoute(policyID, rateID, subRateID))} + shouldShowRightIcon + /> + Navigation.navigate(ROUTES.WORKSPACE_PER_DIEM_EDIT_AMOUNT.getRoute(policyID, rateID, subRateID))} + shouldShowRightIcon + /> + Navigation.navigate(ROUTES.WORKSPACE_PER_DIEM_EDIT_CURRENCY.getRoute(policyID, rateID, subRateID))} + shouldShowRightIcon + /> + setDeletePerDiemConfirmModalVisible(true)} + /> + + + + + ); +} + +WorkspacePerDiemDetailsPage.displayName = 'WorkspacePerDiemDetailsPage'; + +export default WorkspacePerDiemDetailsPage; diff --git a/src/pages/workspace/perDiem/WorkspacePerDiemPage.tsx b/src/pages/workspace/perDiem/WorkspacePerDiemPage.tsx index 33ef0109a7a7..61e763c9eda7 100644 --- a/src/pages/workspace/perDiem/WorkspacePerDiemPage.tsx +++ b/src/pages/workspace/perDiem/WorkspacePerDiemPage.tsx @@ -223,18 +223,12 @@ function WorkspacePerDiemPage({route}: WorkspacePerDiemPageProps) { Navigation.navigate(ROUTES.WORKSPACE_PER_DIEM_SETTINGS.getRoute(policyID)); }; - // eslint-disable-next-line @typescript-eslint/no-unused-vars const openSubRateDetails = (rate: PolicyOption) => { - // TODO: Uncomment this when the import feature is ready - // Navigation.navigate(ROUTES.WORKSPACE_PER_DIEM_RATE_DETAILS.getRoute(policyID, rate.rateID, rate.subRateID)); - }; - - // eslint-disable-next-line @typescript-eslint/no-unused-vars - const dismissError = (item: PolicyOption) => { - // TODO: Implement this when the editing feature is ready + Navigation.navigate(ROUTES.WORKSPACE_PER_DIEM_DETAILS.getRoute(policyID, rate.rateID, rate.subRateID)); }; const handleDeletePerDiemRates = () => { + PerDiem.deleteWorkspacePerDiemRates(policyID, customUnit, selectedPerDiem); setSelectedPerDiem([]); setDeletePerDiemConfirmModalVisible(false); }; @@ -423,7 +417,6 @@ function WorkspacePerDiemPage({route}: WorkspacePerDiemPageProps) { shouldPreventDefaultFocusOnSelectRow={!DeviceCapabilities.canUseTouchScreen()} onSelectAll={toggleAllSubRates} ListItem={TableListItem} - onDismissError={dismissError} customListHeader={getCustomListHeader()} listHeaderWrapperStyle={[styles.ph9, styles.pv3, styles.pb5]} listHeaderContent={shouldUseNarrowLayout ? getHeaderText() : null} diff --git a/src/types/form/WorkspacePerDiemForm.ts b/src/types/form/WorkspacePerDiemForm.ts new file mode 100644 index 000000000000..86dc58cb1d5c --- /dev/null +++ b/src/types/form/WorkspacePerDiemForm.ts @@ -0,0 +1,22 @@ +import type {ValueOf} from 'type-fest'; +import type Form from './Form'; + +const INPUT_IDS = { + DESTINATION: 'destination', + SUBRATE: 'subrate', + AMOUNT: 'amount', +} as const; + +type InputID = ValueOf; + +type WorkspacePerDiemForm = Form< + InputID, + { + [INPUT_IDS.DESTINATION]: string; + [INPUT_IDS.SUBRATE]: string; + [INPUT_IDS.AMOUNT]: string; + } +>; + +export type {WorkspacePerDiemForm}; +export default INPUT_IDS; diff --git a/src/types/form/index.ts b/src/types/form/index.ts index e8e37bebef9a..3c9d90ba3c2d 100644 --- a/src/types/form/index.ts +++ b/src/types/form/index.ts @@ -87,3 +87,4 @@ export type {WorkspaceCompanyCardFeedName} from './WorkspaceCompanyCardFeedName' export type {SearchSavedSearchRenameForm} from './SearchSavedSearchRenameForm'; export type {WorkspaceCompanyCardEditName} from './WorkspaceCompanyCardEditName'; export type {PersonalDetailsForm} from './PersonalDetailsForm'; +export type {WorkspacePerDiemForm} from './WorkspacePerDiemForm'; From d3d1e8d05c55c2ff75db5e54a35ee170b59c7120 Mon Sep 17 00:00:00 2001 From: Shubham Agrawal Date: Thu, 5 Dec 2024 14:44:32 +0530 Subject: [PATCH 011/161] Fix style --- src/pages/workspace/perDiem/EditPerDiemCurrencyPage.tsx | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/pages/workspace/perDiem/EditPerDiemCurrencyPage.tsx b/src/pages/workspace/perDiem/EditPerDiemCurrencyPage.tsx index b142372c2411..1c75d2704c3b 100644 --- a/src/pages/workspace/perDiem/EditPerDiemCurrencyPage.tsx +++ b/src/pages/workspace/perDiem/EditPerDiemCurrencyPage.tsx @@ -62,7 +62,7 @@ function EditPerDiemCurrencyPage({route}: EditPerDiemCurrencyPageProps) { title={translate('common.currency')} onBackButtonPress={() => Navigation.goBack(ROUTES.WORKSPACE_PER_DIEM_DETAILS.getRoute(policyID, rateID, subRateID))} /> - + {translate('workspace.perDiem.editCurrencySubtitle', {destination: selectedRate?.name ?? ''})} Date: Thu, 5 Dec 2024 15:04:17 +0530 Subject: [PATCH 012/161] Fix onyx update when doing delete operation --- src/libs/actions/Policy/PerDiem.ts | 15 ++++++++++++--- 1 file changed, 12 insertions(+), 3 deletions(-) diff --git a/src/libs/actions/Policy/PerDiem.ts b/src/libs/actions/Policy/PerDiem.ts index 5f11eeff0b97..6d7cfcaf642f 100644 --- a/src/libs/actions/Policy/PerDiem.ts +++ b/src/libs/actions/Policy/PerDiem.ts @@ -205,6 +205,10 @@ function clearPolicyPerDiemRatesErrorFields(policyID: string, customUnitID: stri }); } +type DeletePerDiemCustomUnitOnyxType = Omit & { + rates: Record; +}; + function prepareNewCustomUnit(customUnit: CustomUnit, subRatesToBeDeleted: SubRateData[]) { const mappedDeletedSubRatesToRate = subRatesToBeDeleted.reduce((acc, subRate) => { if (subRate.rateID in acc) { @@ -217,6 +221,7 @@ function prepareNewCustomUnit(customUnit: CustomUnit, subRatesToBeDeleted: SubRa // Copy the custom unit and remove the sub rates that are to be deleted const newCustomUnit: CustomUnit = lodashDeepClone(customUnit); + const customUnitOnyxUpdate: DeletePerDiemCustomUnitOnyxType = lodashDeepClone(customUnit); for (const rateID in mappedDeletedSubRatesToRate) { if (!(rateID in newCustomUnit.rates)) { // eslint-disable-next-line no-continue @@ -225,19 +230,23 @@ function prepareNewCustomUnit(customUnit: CustomUnit, subRatesToBeDeleted: SubRa const subRates = mappedDeletedSubRatesToRate[rateID]; if (subRates.length === newCustomUnit.rates[rateID].subRates?.length) { delete newCustomUnit.rates[rateID]; + customUnitOnyxUpdate.rates[rateID] = null; } else { const newSubRates = newCustomUnit.rates[rateID].subRates?.filter((subRate) => !subRates.some((subRateToBeDeleted) => subRateToBeDeleted.subRateID === subRate.id)); newCustomUnit.rates[rateID].subRates = newSubRates; + if (!isEmptyObject(customUnitOnyxUpdate.rates[rateID])) { + customUnitOnyxUpdate.rates[rateID].subRates = newSubRates; + } } } - return newCustomUnit; + return {newCustomUnit, customUnitOnyxUpdate}; } function deleteWorkspacePerDiemRates(policyID: string, customUnit: CustomUnit | undefined, subRatesToBeDeleted: SubRateData[]) { if (!policyID || isEmptyObject(customUnit) || !subRatesToBeDeleted.length) { return; } - const newCustomUnit = prepareNewCustomUnit(customUnit, subRatesToBeDeleted); + const {newCustomUnit, customUnitOnyxUpdate} = prepareNewCustomUnit(customUnit, subRatesToBeDeleted); const onyxData: OnyxData = { optimisticData: [ { @@ -245,7 +254,7 @@ function deleteWorkspacePerDiemRates(policyID: string, customUnit: CustomUnit | key: `${ONYXKEYS.COLLECTION.POLICY}${policyID}`, value: { customUnits: { - [customUnit.customUnitID]: newCustomUnit, + [customUnit.customUnitID]: customUnitOnyxUpdate, }, }, }, From 7203e2e352b8c519f7bd67bd2adc295730c7ae47 Mon Sep 17 00:00:00 2001 From: Shubham Agrawal Date: Thu, 5 Dec 2024 15:12:44 +0530 Subject: [PATCH 013/161] Fix ts lint --- src/libs/actions/Policy/PerDiem.ts | 4 +--- 1 file changed, 1 insertion(+), 3 deletions(-) diff --git a/src/libs/actions/Policy/PerDiem.ts b/src/libs/actions/Policy/PerDiem.ts index 6d7cfcaf642f..71823e80f512 100644 --- a/src/libs/actions/Policy/PerDiem.ts +++ b/src/libs/actions/Policy/PerDiem.ts @@ -234,9 +234,7 @@ function prepareNewCustomUnit(customUnit: CustomUnit, subRatesToBeDeleted: SubRa } else { const newSubRates = newCustomUnit.rates[rateID].subRates?.filter((subRate) => !subRates.some((subRateToBeDeleted) => subRateToBeDeleted.subRateID === subRate.id)); newCustomUnit.rates[rateID].subRates = newSubRates; - if (!isEmptyObject(customUnitOnyxUpdate.rates[rateID])) { - customUnitOnyxUpdate.rates[rateID].subRates = newSubRates; - } + customUnitOnyxUpdate.rates[rateID] = {...customUnitOnyxUpdate.rates[rateID], subRates: newSubRates}; } } return {newCustomUnit, customUnitOnyxUpdate}; From 95ac5024ccfb25f090066c85d96fea036e4a8f21 Mon Sep 17 00:00:00 2001 From: Julian Kobrynski Date: Thu, 5 Dec 2024 21:13:33 +0100 Subject: [PATCH 014/161] replace FlatList with .map solution --- .../Transaction/DebugTransactionViolations.tsx | 13 +++++-------- 1 file changed, 5 insertions(+), 8 deletions(-) diff --git a/src/pages/Debug/Transaction/DebugTransactionViolations.tsx b/src/pages/Debug/Transaction/DebugTransactionViolations.tsx index d3e37f726a96..e13fd01fdcd7 100644 --- a/src/pages/Debug/Transaction/DebugTransactionViolations.tsx +++ b/src/pages/Debug/Transaction/DebugTransactionViolations.tsx @@ -1,8 +1,6 @@ import React from 'react'; -import type {ListRenderItemInfo} from 'react-native'; import {useOnyx} from 'react-native-onyx'; import Button from '@components/Button'; -import FlatList from '@components/FlatList'; import PressableWithFeedback from '@components/Pressable/PressableWithFeedback'; import ScrollView from '@components/ScrollView'; import Text from '@components/Text'; @@ -23,12 +21,13 @@ function DebugTransactionViolations({transactionID}: DebugTransactionViolationsP const styles = useThemeStyles(); const {translate} = useLocalize(); - const renderItem = ({item, index}: ListRenderItemInfo) => ( + const renderItem = (item: TransactionViolation, index: number) => ( Navigation.navigate(ROUTES.DEBUG_TRANSACTION_VIOLATION.getRoute(transactionID, String(index)))} style={({pressed}) => [styles.flexRow, styles.justifyContentBetween, pressed && styles.hoveredComponentBG, styles.p4]} hoverStyle={styles.hoveredComponentBG} + key={index} > {item.type} {item.name} @@ -44,11 +43,9 @@ function DebugTransactionViolations({transactionID}: DebugTransactionViolationsP onPress={() => Navigation.navigate(ROUTES.DEBUG_TRANSACTION_VIOLATION_CREATE.getRoute(transactionID))} style={[styles.pb5, styles.ph3]} /> - + {/* This list was previously rendered as a FlatList, but it turned out that it caused the component to flash in some cases, + so it was replaced by this solution. */} + {transactionViolations?.map((item, index) => renderItem(item, index))} ); } From 014995e49fe387b8959fb0e581040a754443da92 Mon Sep 17 00:00:00 2001 From: Shubham Agrawal Date: Fri, 6 Dec 2024 13:03:41 +0530 Subject: [PATCH 015/161] Fix parameters --- .../API/parameters/UpdateWorkspaceCustomUnitParams.ts | 2 +- src/libs/actions/Policy/PerDiem.ts | 10 +++++----- 2 files changed, 6 insertions(+), 6 deletions(-) diff --git a/src/libs/API/parameters/UpdateWorkspaceCustomUnitParams.ts b/src/libs/API/parameters/UpdateWorkspaceCustomUnitParams.ts index 8ff704c4a0dd..fa1fc3d8c911 100644 --- a/src/libs/API/parameters/UpdateWorkspaceCustomUnitParams.ts +++ b/src/libs/API/parameters/UpdateWorkspaceCustomUnitParams.ts @@ -1,6 +1,6 @@ type UpdateWorkspaceCustomUnitParams = { policyID: string; - customUnitData: string; + customUnit: string; }; export default UpdateWorkspaceCustomUnitParams; diff --git a/src/libs/actions/Policy/PerDiem.ts b/src/libs/actions/Policy/PerDiem.ts index 71823e80f512..ec6f4e7bc216 100644 --- a/src/libs/actions/Policy/PerDiem.ts +++ b/src/libs/actions/Policy/PerDiem.ts @@ -261,7 +261,7 @@ function deleteWorkspacePerDiemRates(policyID: string, customUnit: CustomUnit | const parameters = { policyID, - customUnitData: JSON.stringify(newCustomUnit), + customUnit: JSON.stringify(newCustomUnit), }; API.write(WRITE_COMMANDS.UPDATE_WORKSPACE_CUSTOM_UNIT, parameters, onyxData); @@ -291,7 +291,7 @@ function editPerDiemRateDestination(policyID: string, rateID: string, customUnit const parameters = { policyID, - customUnitData: JSON.stringify(newCustomUnit), + customUnit: JSON.stringify(newCustomUnit), }; API.write(WRITE_COMMANDS.UPDATE_WORKSPACE_CUSTOM_UNIT, parameters, onyxData); @@ -326,7 +326,7 @@ function editPerDiemRateSubrate(policyID: string, rateID: string, subRateID: str const parameters = { policyID, - customUnitData: JSON.stringify(newCustomUnit), + customUnit: JSON.stringify(newCustomUnit), }; API.write(WRITE_COMMANDS.UPDATE_WORKSPACE_CUSTOM_UNIT, parameters, onyxData); @@ -361,7 +361,7 @@ function editPerDiemRateAmount(policyID: string, rateID: string, subRateID: stri const parameters = { policyID, - customUnitData: JSON.stringify(newCustomUnit), + customUnit: JSON.stringify(newCustomUnit), }; API.write(WRITE_COMMANDS.UPDATE_WORKSPACE_CUSTOM_UNIT, parameters, onyxData); @@ -391,7 +391,7 @@ function editPerDiemRateCurrency(policyID: string, rateID: string, customUnit: C const parameters = { policyID, - customUnitData: JSON.stringify(newCustomUnit), + customUnit: JSON.stringify(newCustomUnit), }; API.write(WRITE_COMMANDS.UPDATE_WORKSPACE_CUSTOM_UNIT, parameters, onyxData); From 5950c1180c199ba9364b2b19a3dcf1691ba82b14 Mon Sep 17 00:00:00 2001 From: daledah Date: Fri, 6 Dec 2024 14:48:37 +0700 Subject: [PATCH 016/161] refactor: change logics to more precise --- src/libs/PersonalDetailsUtils.ts | 20 +++++++++++++++++--- 1 file changed, 17 insertions(+), 3 deletions(-) diff --git a/src/libs/PersonalDetailsUtils.ts b/src/libs/PersonalDetailsUtils.ts index 31d344facb32..395ab930b116 100644 --- a/src/libs/PersonalDetailsUtils.ts +++ b/src/libs/PersonalDetailsUtils.ts @@ -52,6 +52,8 @@ const regexMergedAccount = new RegExp(CONST.REGEX.MERGED_ACCOUNT_PREFIX); function getDisplayNameOrDefault(passedPersonalDetails?: Partial | null, defaultValue = '', shouldFallbackToHidden = true, shouldAddCurrentUserPostfix = false): string { let displayName = passedPersonalDetails?.displayName ?? ''; + let login = passedPersonalDetails?.login ?? ''; + // If the displayName starts with the merged account prefix, remove it. if (regexMergedAccount.test(displayName)) { // Remove the merged account prefix from the displayName. @@ -60,8 +62,11 @@ function getDisplayNameOrDefault(passedPersonalDetails?: Partial Date: Fri, 6 Dec 2024 11:31:09 +0100 Subject: [PATCH 017/161] Replace FlatList with .map solution in DebugReportActions.tsx --- src/pages/Debug/Report/DebugReportActions.tsx | 15 +++++++-------- 1 file changed, 7 insertions(+), 8 deletions(-) diff --git a/src/pages/Debug/Report/DebugReportActions.tsx b/src/pages/Debug/Report/DebugReportActions.tsx index 9368ca5116bd..fdc2aa8b1ca8 100644 --- a/src/pages/Debug/Report/DebugReportActions.tsx +++ b/src/pages/Debug/Report/DebugReportActions.tsx @@ -1,8 +1,6 @@ import React from 'react'; -import type {ListRenderItemInfo} from 'react-native'; import {useOnyx} from 'react-native-onyx'; import Button from '@components/Button'; -import FlatList from '@components/FlatList'; import {PressableWithFeedback} from '@components/Pressable'; import ScrollView from '@components/ScrollView'; import Text from '@components/Text'; @@ -28,17 +26,20 @@ function DebugReportActions({reportID}: DebugReportActionsProps) { canEvict: false, selector: (allReportActions) => ReportActionsUtils.getSortedReportActionsForDisplay(allReportActions, canUserPerformWriteAction, true), }); - const renderItem = ({item}: ListRenderItemInfo) => ( + + const renderItem = (item: ReportAction, index: number) => ( Navigation.navigate(ROUTES.DEBUG_REPORT_ACTION.getRoute(reportID, item.reportActionID))} style={({pressed}) => [styles.flexRow, styles.justifyContentBetween, pressed && styles.hoveredComponentBG, styles.p4]} hoverStyle={styles.hoveredComponentBG} + key={index} > {item.reportActionID} {datetimeToCalendarTime(item.created, false, false)} ); + return (