From 4810696d937d92af414c59fba2160537b801fde0 Mon Sep 17 00:00:00 2001 From: dukenv0307 Date: Tue, 21 Nov 2023 23:58:29 +0700 Subject: [PATCH 001/209] Update all selectors to use new format for selected participant --- src/libs/OptionsListUtils.js | 4 +++- src/pages/RoomInvitePage.js | 19 +++++++++++-------- src/pages/workspace/WorkspaceInvitePage.js | 18 ++++++++++-------- 3 files changed, 24 insertions(+), 17 deletions(-) diff --git a/src/libs/OptionsListUtils.js b/src/libs/OptionsListUtils.js index b97ae6daed11..f1e97151a062 100644 --- a/src/libs/OptionsListUtils.js +++ b/src/libs/OptionsListUtils.js @@ -1595,15 +1595,17 @@ function formatMemberForList(member, config = {}) { * @param {Array} betas * @param {String} searchValue * @param {Array} excludeLogins + * @param {Boolean} includeSelectedOptions * @returns {Object} */ -function getMemberInviteOptions(personalDetails, betas = [], searchValue = '', excludeLogins = []) { +function getMemberInviteOptions(personalDetails, betas = [], searchValue = '', excludeLogins = [], includeSelectedOptions = false) { return getOptions([], personalDetails, { betas, searchInputValue: searchValue.trim(), includePersonalDetails: true, excludeLogins, sortPersonalDetailsByAlphaAsc: true, + includeSelectedOptions, }); } diff --git a/src/pages/RoomInvitePage.js b/src/pages/RoomInvitePage.js index a1f7d22c3dc3..cacf603f8836 100644 --- a/src/pages/RoomInvitePage.js +++ b/src/pages/RoomInvitePage.js @@ -84,7 +84,7 @@ function RoomInvitePage(props) { }, []); useEffect(() => { - const inviteOptions = OptionsListUtils.getMemberInviteOptions(props.personalDetails, props.betas, searchTerm, excludedUsers); + const inviteOptions = OptionsListUtils.getMemberInviteOptions(props.personalDetails, props.betas, searchTerm, excludedUsers, true); // Update selectedOptions with the latest personalDetails information const detailsMap = {}; @@ -104,13 +104,16 @@ function RoomInvitePage(props) { const sections = []; let indexOffset = 0; - sections.push({ - title: undefined, - data: selectedOptions, - shouldShow: true, - indexOffset, - }); - indexOffset += selectedOptions.length; + // Only show the selected participants if the search is empty + if (searchTerm === '') { + sections.push({ + title: undefined, + data: selectedOptions, + shouldShow: true, + indexOffset, + }); + indexOffset += selectedOptions.length; + } // Filtering out selected users from the search results const selectedLogins = _.map(selectedOptions, ({login}) => login); diff --git a/src/pages/workspace/WorkspaceInvitePage.js b/src/pages/workspace/WorkspaceInvitePage.js index 4bef69c82414..eeffb35b4d23 100644 --- a/src/pages/workspace/WorkspaceInvitePage.js +++ b/src/pages/workspace/WorkspaceInvitePage.js @@ -90,7 +90,7 @@ function WorkspaceInvitePage(props) { const newPersonalDetailsDict = {}; const newSelectedOptionsDict = {}; - const inviteOptions = OptionsListUtils.getMemberInviteOptions(props.personalDetails, props.betas, searchTerm, excludedUsers); + const inviteOptions = OptionsListUtils.getMemberInviteOptions(props.personalDetails, props.betas, searchTerm, excludedUsers, true); // Update selectedOptions with the latest personalDetails and policyMembers information const detailsMap = {}; @@ -130,13 +130,15 @@ function WorkspaceInvitePage(props) { const sections = []; let indexOffset = 0; - sections.push({ - title: undefined, - data: selectedOptions, - shouldShow: true, - indexOffset, - }); - indexOffset += selectedOptions.length; + if (searchTerm === '') { + sections.push({ + title: undefined, + data: selectedOptions, + shouldShow: true, + indexOffset, + }); + indexOffset += selectedOptions.length; + } // Filtering out selected users from the search results const selectedLogins = _.map(selectedOptions, ({login}) => login); From 38c2d4e35e44c0fd529750d4d41207160caa4277 Mon Sep 17 00:00:00 2001 From: Pavlo Tsimura Date: Fri, 1 Dec 2023 12:48:51 +0100 Subject: [PATCH 002/209] Use PersonalDetailsUtils.getDisplayNameOrDefault to escape merged account prefix --- src/libs/OptionsListUtils.js | 17 +++++++++++------ src/libs/PersonalDetailsUtils.js | 3 ++- src/libs/ReportUtils.ts | 5 +++-- 3 files changed, 16 insertions(+), 9 deletions(-) diff --git a/src/libs/OptionsListUtils.js b/src/libs/OptionsListUtils.js index e3b6ec77380e..e80f8e7dd1ba 100644 --- a/src/libs/OptionsListUtils.js +++ b/src/libs/OptionsListUtils.js @@ -200,7 +200,7 @@ function isPersonalDetailsReady(personalDetails) { function getParticipantsOption(participant, personalDetails) { const detail = getPersonalDetailsForAccountIDs([participant.accountID], personalDetails)[participant.accountID]; const login = detail.login || participant.login; - const displayName = detail.displayName || LocalePhoneNumber.formatPhoneNumber(login); + const displayName = PersonalDetailsUtils.getDisplayNameOrDefault(detail, 'displayName', LocalePhoneNumber.formatPhoneNumber(login)); return { keyForList: String(detail.accountID), login, @@ -245,7 +245,8 @@ function getParticipantNames(personalDetailList) { participantNames.add(participant.lastName.toLowerCase()); } if (participant.displayName) { - participantNames.add(participant.displayName.toLowerCase()); + const displayName = PersonalDetailsUtils.getDisplayNameOrDefault(participant, 'displayName'); + participantNames.add(displayName.toLowerCase()); } }); return participantNames; @@ -298,7 +299,11 @@ function getSearchText(report, reportName, personalDetailList, isChatRoomOrPolic // The regex below is used to remove dots only from the local part of the user email (local-part@domain) // so that we can match emails that have dots without explicitly writing the dots (e.g: fistlast@domain will match first.last@domain) // More info https://github.com/Expensify/App/issues/8007 - searchTerms = searchTerms.concat([personalDetail.displayName, personalDetail.login, personalDetail.login.replace(/\.(?=[^\s@]*@)/g, '')]); + searchTerms = searchTerms.concat([ + PersonalDetailsUtils.getDisplayNameOrDefault(personalDetail, 'displayName'), + personalDetail.login, + personalDetail.login.replace(/\.(?=[^\s@]*@)/g, '') + ]); } } } @@ -507,7 +512,7 @@ function createOption(accountIDs, personalDetails, report, reportActions = {}, { const lastMessageTextFromReport = getLastMessageTextForReport(report); const lastActorDetails = personalDetailMap[report.lastActorAccountID] || null; - let lastMessageText = hasMultipleParticipants && lastActorDetails && lastActorDetails.accountID !== currentUserAccountID ? `${lastActorDetails.displayName}: ` : ''; + let lastMessageText = hasMultipleParticipants && lastActorDetails && lastActorDetails.accountID !== currentUserAccountID ? `${PersonalDetailsUtils.getDisplayNameOrDefault(lastActorDetails, 'displayName')}: ` : ''; lastMessageText += report ? lastMessageTextFromReport : ''; if (result.isArchivedRoom) { @@ -1429,8 +1434,8 @@ function getSearchOptions(reports, personalDetails, searchValue = '', betas) { function getIOUConfirmationOptionsFromPayeePersonalDetail(personalDetail, amountText) { const formattedLogin = LocalePhoneNumber.formatPhoneNumber(personalDetail.login); return { - text: personalDetail.displayName || formattedLogin, - alternateText: formattedLogin || personalDetail.displayName, + text: PersonalDetailsUtils.getDisplayNameOrDefault(personalDetail, 'displayName', formattedLogin), + alternateText: formattedLogin || PersonalDetailsUtils.getDisplayNameOrDefault(personalDetail, 'displayName'), icons: [ { source: UserUtils.getAvatar(personalDetail.avatar, personalDetail.accountID), diff --git a/src/libs/PersonalDetailsUtils.js b/src/libs/PersonalDetailsUtils.js index 560480dcec9d..30b710e9d15c 100644 --- a/src/libs/PersonalDetailsUtils.js +++ b/src/libs/PersonalDetailsUtils.js @@ -1,6 +1,7 @@ import lodashGet from 'lodash/get'; import Onyx from 'react-native-onyx'; import _ from 'underscore'; +import CONST from '@src/CONST'; import ONYXKEYS from '@src/ONYXKEYS'; import * as LocalePhoneNumber from './LocalePhoneNumber'; import * as Localize from './Localize'; @@ -23,7 +24,7 @@ Onyx.connect({ * @returns {String} */ function getDisplayNameOrDefault(passedPersonalDetails, pathToDisplayName, defaultValue = '') { - const displayName = lodashGet(passedPersonalDetails, pathToDisplayName); + const displayName = lodashGet(passedPersonalDetails, pathToDisplayName, '').replace(CONST.REGEX.MERGED_ACCOUNT_PREFIX, ''); return displayName || defaultValue || Localize.translateLocal('common.hidden'); } diff --git a/src/libs/ReportUtils.ts b/src/libs/ReportUtils.ts index f6c3090143f4..b914658fe448 100644 --- a/src/libs/ReportUtils.ts +++ b/src/libs/ReportUtils.ts @@ -31,6 +31,7 @@ import linkingConfig from './Navigation/linkingConfig'; import Navigation from './Navigation/Navigation'; import * as NumberUtils from './NumberUtils'; import Permissions from './Permissions'; +import * as PersonalDetailsUtils from './PersonalDetailsUtils'; import * as PolicyUtils from './PolicyUtils'; import * as ReportActionsUtils from './ReportActionsUtils'; import {LastVisibleMessage} from './ReportActionsUtils'; @@ -1270,7 +1271,7 @@ function getIcons( const parentReportAction = ReportActionsUtils.getParentReportAction(report); const actorAccountID = parentReportAction.actorAccountID; - const actorDisplayName = allPersonalDetails?.[actorAccountID ?? -1]?.displayName ?? ''; + const actorDisplayName = PersonalDetailsUtils.getDisplayNameOrDefault(allPersonalDetails, [actorAccountID ?? -1, 'displayName']); const actorIcon = { id: actorAccountID, source: UserUtils.getAvatar(personalDetails?.[actorAccountID ?? -1]?.avatar ?? '', actorAccountID ?? -1), @@ -1394,7 +1395,7 @@ function getDisplayNameForParticipant(accountID?: number, shouldUseShortForm = f // eslint-disable-next-line @typescript-eslint/prefer-nullish-coalescing return formattedLogin; } - const longName = personalDetails.displayName ? personalDetails.displayName : formattedLogin; + const longName = PersonalDetailsUtils.getDisplayNameOrDefault(personalDetails, 'displayName', formattedLogin); const shortName = personalDetails.firstName ? personalDetails.firstName : longName; From cd7f52b1a39a6b60613dac842d0c4fd6d3092667 Mon Sep 17 00:00:00 2001 From: Ishpaul Singh Date: Tue, 5 Dec 2023 21:10:45 +0530 Subject: [PATCH 003/209] fix reposition of popover when window resize --- src/CONST.ts | 1 + .../settings/Wallet/PaymentMethodList.js | 5 +++-- .../settings/Wallet/WalletPage/WalletPage.js | 21 ++++++++++++++++--- 3 files changed, 22 insertions(+), 5 deletions(-) diff --git a/src/CONST.ts b/src/CONST.ts index 3d69c83c5c22..b461c702c53f 100755 --- a/src/CONST.ts +++ b/src/CONST.ts @@ -689,6 +689,7 @@ const CONST = { TRIE_INITIALIZATION: 'trie_initialization', COMMENT_LENGTH_DEBOUNCE_TIME: 500, SEARCH_FOR_REPORTS_DEBOUNCE_TIME: 300, + RESIZE_DEBOUNCE_TIME: 100, }, PRIORITY_MODE: { GSD: 'gsd', diff --git a/src/pages/settings/Wallet/PaymentMethodList.js b/src/pages/settings/Wallet/PaymentMethodList.js index 5af4129aefbc..5ac623008fd4 100644 --- a/src/pages/settings/Wallet/PaymentMethodList.js +++ b/src/pages/settings/Wallet/PaymentMethodList.js @@ -289,10 +289,11 @@ function PaymentMethodList({ title={translate('walletPage.addBankAccount')} icon={Expensicons.Plus} wrapperStyle={styles.paymentMethod} + ref={buttonRef} /> ), - [onPress, styles.paymentMethod, translate], + [onPress, styles.paymentMethod, translate, buttonRef], ); /** @@ -346,10 +347,10 @@ function PaymentMethodList({ keyExtractor={keyExtractor} ListEmptyComponent={shouldShowEmptyListMessage ? renderListEmptyComponent : null} ListHeaderComponent={listHeaderComponent} - ListFooterComponent={shouldShowAddBankAccount ? renderListFooterComponent : null} onContentSizeChange={onListContentSizeChange} scrollEnabled={shouldEnableScroll} /> + {shouldShowAddBankAccount && renderListFooterComponent()} {shouldShowAddPaymentMethodButton && ( diff --git a/src/pages/settings/Wallet/WalletPage/WalletPage.js b/src/pages/settings/Wallet/WalletPage/WalletPage.js index 6f452eed3629..9dc6c878a1bf 100644 --- a/src/pages/settings/Wallet/WalletPage/WalletPage.js +++ b/src/pages/settings/Wallet/WalletPage/WalletPage.js @@ -1,6 +1,6 @@ import lodashGet from 'lodash/get'; import React, {useCallback, useEffect, useRef, useState} from 'react'; -import {ActivityIndicator, InteractionManager, ScrollView, View} from 'react-native'; +import {ActivityIndicator, Dimensions, InteractionManager, ScrollView, View} from 'react-native'; import {withOnyx} from 'react-native-onyx'; import _ from 'underscore'; import AddPaymentMethodMenu from '@components/AddPaymentMethodMenu'; @@ -283,8 +283,23 @@ function WalletPage({bankAccountList, cardList, fundList, isLoadingPaymentMethod if (!shouldListenForResize) { return; } - setMenuPosition(); - }, [shouldListenForResize, setMenuPosition]); + const popoverPositionListener = Dimensions.addEventListener('change', () => { + if (!shouldShowAddPaymentMenu && !shouldShowDefaultDeleteMenu) { + return; + } + if (shouldShowAddPaymentMenu) { + _.debounce(setMenuPosition, CONST.TIMING.RESIZE_DEBOUNCE_TIME)(); + return; + } + setMenuPosition(); + }); + return () => { + if (!popoverPositionListener) { + return; + } + popoverPositionListener.remove(); + }; + }, [shouldShowAddPaymentMenu, shouldShowDefaultDeleteMenu, setMenuPosition, shouldListenForResize]); useEffect(() => { if (!shouldShowDefaultDeleteMenu) { From 3d85737a06294ebc2231d54c18031fda9570f5eb Mon Sep 17 00:00:00 2001 From: Etotaziba Olei Date: Wed, 6 Dec 2023 10:07:07 +0100 Subject: [PATCH 004/209] initial input screens --- src/ROUTES.ts | 8 ++ .../MoneyRequestConfirmationList.js | 36 +++++ .../AppNavigator/ModalStackNavigators.js | 2 + src/libs/Navigation/linkingConfig.ts | 2 + src/libs/Navigation/types.ts | 8 ++ src/libs/actions/IOU.js | 10 ++ .../iou/steps/IOURequestStepTaxAmountPage.js | 123 ++++++++++++++++++ .../iou/steps/IOURequestStepTaxRatePage.js | 59 +++++++++ 8 files changed, 248 insertions(+) create mode 100644 src/pages/iou/steps/IOURequestStepTaxAmountPage.js create mode 100644 src/pages/iou/steps/IOURequestStepTaxRatePage.js diff --git a/src/ROUTES.ts b/src/ROUTES.ts index a3aa28c44609..6c68617307a5 100644 --- a/src/ROUTES.ts +++ b/src/ROUTES.ts @@ -283,6 +283,14 @@ const ROUTES = { route: ':iouType/new/merchant/:reportID?', getRoute: (iouType: string, reportID = '') => `${iouType}/new/merchant/${reportID}` as const, }, + MONEY_REQUEST_TAX_RATE: { + route: ':iouType/new/taxRate/:reportID?', + getRoute: (iouType: string, reportID = '') => `${iouType}/new/taxRate/${reportID}` as const, + }, + MONEY_REQUEST_TAX_AMOUNT: { + route: ':iouType/new/taxAmount/:reportID?', + getRoute: (iouType: string, reportID = '') => `${iouType}/new/taxAmount/${reportID}` as const, + }, MONEY_REQUEST_WAYPOINT: { route: ':iouType/new/waypoint/:waypointIndex', getRoute: (iouType: string, waypointIndex: number) => `${iouType}/new/waypoint/${waypointIndex}` as const, diff --git a/src/components/MoneyRequestConfirmationList.js b/src/components/MoneyRequestConfirmationList.js index 1b4967a9c54c..22f733526e2a 100755 --- a/src/components/MoneyRequestConfirmationList.js +++ b/src/components/MoneyRequestConfirmationList.js @@ -241,6 +241,12 @@ function MoneyRequestConfirmationList(props) { // A flag for showing the tags field const shouldShowTags = props.isPolicyExpenseChat && (props.iouTag || OptionsListUtils.hasEnabledOptions(_.values(policyTagList))); + // A flag for showing tax rate + const shouldShowTaxRate = true; + + // A flag for showing tax rate + const shouldShowTaxAmount = true; + // A flag for showing the billable field const shouldShowBillable = !lodashGet(props.policy, 'disabledFields.defaultBillable', true); @@ -721,6 +727,36 @@ function MoneyRequestConfirmationList(props) { /> )} + {shouldShowTaxRate && ( + Navigation.navigate(ROUTES.MONEY_REQUEST_TAX_RATE.getRoute(props.iouType, props.reportID))} + disabled={didConfirm} + interactive={!props.isReadOnly} + brickRoadIndicator='' + error='' + /> + )} + + {shouldShowTaxAmount && ( + Navigation.navigate(ROUTES.MONEY_REQUEST_TAX_AMOUNT.getRoute(props.iouType, props.reportID))} + disabled={didConfirm} + interactive={!props.isReadOnly} + brickRoadIndicator='' + error='' + /> + )} + {shouldShowBillable && ( {translate('common.billable')} diff --git a/src/libs/Navigation/AppNavigator/ModalStackNavigators.js b/src/libs/Navigation/AppNavigator/ModalStackNavigators.js index be803e62a98b..bb78b4f0d5b5 100644 --- a/src/libs/Navigation/AppNavigator/ModalStackNavigators.js +++ b/src/libs/Navigation/AppNavigator/ModalStackNavigators.js @@ -46,6 +46,8 @@ function createModalStackNavigator(screens) { const MoneyRequestModalStackNavigator = createModalStackNavigator({ Money_Request: () => require('../../../pages/iou/MoneyRequestSelectorPage').default, Money_Request_Amount: () => require('../../../pages/iou/steps/NewRequestAmountPage').default, + Money_Request_Tax_Rate: () => require('../../../pages/iou/steps/IOURequestStepTaxRatePage').default, + Money_Request_Tax_Amount: () => require('../../../pages/iou/steps/IOURequestStepTaxAmountPage').default, Money_Request_Participants: () => require('../../../pages/iou/steps/MoneyRequstParticipantsPage/MoneyRequestParticipantsPage').default, Money_Request_Confirmation: () => require('../../../pages/iou/steps/MoneyRequestConfirmPage').default, Money_Request_Currency: () => require('../../../pages/iou/IOUCurrencySelection').default, diff --git a/src/libs/Navigation/linkingConfig.ts b/src/libs/Navigation/linkingConfig.ts index 92a04778b9a6..97be3c2b68a7 100644 --- a/src/libs/Navigation/linkingConfig.ts +++ b/src/libs/Navigation/linkingConfig.ts @@ -379,6 +379,8 @@ const linkingConfig: LinkingOptions = { }, }, Money_Request_Amount: ROUTES.MONEY_REQUEST_AMOUNT.route, + Money_Request_Tax_Rate: ROUTES.MONEY_REQUEST_TAX_RATE.route, + Money_Request_Tax_Amount: ROUTES.MONEY_REQUEST_TAX_AMOUNT.route, Money_Request_Participants: ROUTES.MONEY_REQUEST_PARTICIPANTS.route, Money_Request_Confirmation: ROUTES.MONEY_REQUEST_CONFIRMATION.route, Money_Request_Date: ROUTES.MONEY_REQUEST_DATE.route, diff --git a/src/libs/Navigation/types.ts b/src/libs/Navigation/types.ts index 41df21d8e237..00cad1983ea0 100644 --- a/src/libs/Navigation/types.ts +++ b/src/libs/Navigation/types.ts @@ -211,6 +211,14 @@ type MoneyRequestNavigatorParamList = { iouType: string; reportID: string; }; + Money_Request_Tax_Rate: { + iouType: string; + reportID: string; + }; + Money_Request_Tax_Amount: { + iouType: string; + reportID: string; + }; Money_Request_Merchant: { iouType: string; reportID: string; diff --git a/src/libs/actions/IOU.js b/src/libs/actions/IOU.js index d9de984ad12c..017942177aee 100644 --- a/src/libs/actions/IOU.js +++ b/src/libs/actions/IOU.js @@ -2875,6 +2875,14 @@ function resetMoneyRequestTag() { Onyx.merge(ONYXKEYS.IOU, {tag: ''}); } +function setMoneyRequestTaxRate(transactionID, taxRate) { + Onyx.merge(`${ONYXKEYS.COLLECTION.TRANSACTION_DRAFT}${transactionID}`, {taxRate}); +} + +function setMoneyRequestTaxAmount(transactionID, taxAmount) { + Onyx.merge(`${ONYXKEYS.COLLECTION.TRANSACTION_DRAFT}${transactionID}`, {taxAmount}); +} + /** * @param {Boolean} billable */ @@ -2996,6 +3004,8 @@ export { resetMoneyRequestCategory, setMoneyRequestTag, resetMoneyRequestTag, + setMoneyRequestTaxRate, + setMoneyRequestTaxAmount, setMoneyRequestBillable, setMoneyRequestParticipants, setMoneyRequestReceipt, diff --git a/src/pages/iou/steps/IOURequestStepTaxAmountPage.js b/src/pages/iou/steps/IOURequestStepTaxAmountPage.js new file mode 100644 index 000000000000..c12aada5d859 --- /dev/null +++ b/src/pages/iou/steps/IOURequestStepTaxAmountPage.js @@ -0,0 +1,123 @@ +import {useFocusEffect} from '@react-navigation/native'; +import lodashGet from 'lodash/get'; +import PropTypes from 'prop-types'; +import React, {useCallback, useRef} from 'react'; +import {View} from 'react-native'; +import {withOnyx} from 'react-native-onyx'; +import FullPageNotFoundView from '@components/BlockingViews/FullPageNotFoundView'; +import HeaderWithBackButton from '@components/HeaderWithBackButton'; +import ScreenWrapper from '@components/ScreenWrapper'; +import * as CurrencyUtils from '@libs/CurrencyUtils'; +import * as IOUUtils from '@libs/IOUUtils'; +import Navigation from '@libs/Navigation/Navigation'; +import {iouDefaultProps, iouPropTypes} from '@pages/iou/propTypes'; +import useThemeStyles from '@styles/useThemeStyles'; +import * as IOU from '@userActions/IOU'; +import CONST from '@src/CONST'; +import ONYXKEYS from '@src/ONYXKEYS'; +import ROUTES from '@src/ROUTES'; +import MoneyRequestAmountForm from './MoneyRequestAmountForm'; + +const propTypes = { + /** React Navigation route */ + route: PropTypes.shape({ + /** Params from the route */ + params: PropTypes.shape({ + /** The type of IOU report, i.e. bill, request, send */ + iouType: PropTypes.string, + + /** The report ID of the IOU */ + reportID: PropTypes.string, + + /** Selected currency from IOUCurrencySelection */ + currency: PropTypes.string, + }), + }).isRequired, + + /** Holds data related to Money Request view state, rather than the underlying Money Request data. */ + iou: iouPropTypes, +}; + +const defaultProps = { + iou: iouDefaultProps, +}; + +function IOURequestStepTaxAmountPage({route, iou}) { + const styles = useThemeStyles(); + const textInput = useRef(null); + const isEditing = Navigation.getActiveRoute().includes('taxAmount'); + + const iouType = lodashGet(route, 'params.iouType', ''); + const reportID = lodashGet(route, 'params.reportID', ''); + const currentCurrency = lodashGet(route, 'params.currency', ''); + const currency = CurrencyUtils.isValidCurrencyCode(currentCurrency) ? currentCurrency : iou.currency; + + const focusTimeoutRef = useRef(null); + useFocusEffect( + useCallback(() => { + focusTimeoutRef.current = setTimeout(() => textInput.current && textInput.current.focus(), CONST.ANIMATED_TRANSITION); + return () => { + if (!focusTimeoutRef.current) { + return; + } + clearTimeout(focusTimeoutRef.current); + }; + }, []), + ); + + const navigateBack = () => { + Navigation.goBack(isEditing ? ROUTES.MONEY_REQUEST_CONFIRMATION.getRoute(iouType, reportID) : ROUTES.HOME); + }; + + const navigateToCurrencySelectionPage = () => { + // If the money request being created is a distance request, don't allow the user to choose the currency. + // Only USD is allowed for distance requests. + // Remove query from the route and encode it. + const activeRoute = encodeURIComponent(Navigation.getActiveRouteWithoutParams()); + Navigation.navigate(ROUTES.MONEY_REQUEST_CURRENCY.getRoute(iouType, reportID, currency, activeRoute)); + }; + + const updateTaxAmount = (currentAmount) => { + const amountInSmallestCurrencyUnits = CurrencyUtils.convertToBackendAmount(Number.parseFloat(currentAmount)); + IOU.setMoneyRequestTaxAmount(amountInSmallestCurrencyUnits); + navigateBack(); + }; + + const content = ( + (textInput.current = e)} + onCurrencyButtonPress={navigateToCurrencySelectionPage} + onSubmitButtonPress={updateTaxAmount} + /> + ); + + return ( + + {({safeAreaPaddingBottomStyle}) => ( + + + + {content} + + + )} + + ); +} + +IOURequestStepTaxAmountPage.propTypes = propTypes; +IOURequestStepTaxAmountPage.defaultProps = defaultProps; +IOURequestStepTaxAmountPage.displayName = 'IOURequestStepTaxAmountPage'; +export default withOnyx({ + iou: {key: ONYXKEYS.IOU}, +})(IOURequestStepTaxAmountPage); diff --git a/src/pages/iou/steps/IOURequestStepTaxRatePage.js b/src/pages/iou/steps/IOURequestStepTaxRatePage.js new file mode 100644 index 000000000000..58128e643f61 --- /dev/null +++ b/src/pages/iou/steps/IOURequestStepTaxRatePage.js @@ -0,0 +1,59 @@ +import lodashGet from 'lodash/get'; +import PropTypes from 'prop-types'; +import React from 'react'; +import HeaderWithBackButton from '@components/HeaderWithBackButton'; +import ScreenWrapper from '@components/ScreenWrapper'; +import Navigation from '@libs/Navigation/Navigation'; +import ROUTES from '@src/ROUTES'; + +const propTypes = { + /** Route from navigation */ + route: PropTypes.shape({ + /** Params from the route */ + params: PropTypes.shape({ + /** The type of IOU report, i.e. bill, request, send */ + iouType: PropTypes.string, + + /** The report ID of the IOU */ + reportID: PropTypes.string, + + /** Which field we are editing */ + field: PropTypes.string, + + /** reportID for the "transaction thread" */ + threadReportID: PropTypes.string, + }), + }).isRequired, +}; + +const defaultProps = {}; + +function IOURequestStepTaxRatePage({route}) { + const iouType = lodashGet(route, 'params.iouType', ''); + const reportID = lodashGet(route, 'params.reportID', ''); + + function navigateBack() { + Navigation.goBack(ROUTES.MONEY_REQUEST_CONFIRMATION.getRoute(iouType, reportID)); + } + + return ( + + <> + navigateBack()} + /> + + + ); +} + +IOURequestStepTaxRatePage.propTypes = propTypes; +IOURequestStepTaxRatePage.defaultProps = defaultProps; +IOURequestStepTaxRatePage.displayName = 'IOURequestStepTaxRatePage'; + +export default IOURequestStepTaxRatePage; From 20efbfc0412abd1b5b4e98e996a229583eddc2cb Mon Sep 17 00:00:00 2001 From: Etotaziba Olei Date: Wed, 6 Dec 2023 10:19:10 +0100 Subject: [PATCH 005/209] fix lint --- src/components/MoneyRequestConfirmationList.js | 16 ++++++++-------- .../iou/steps/IOURequestStepTaxAmountPage.js | 2 +- src/pages/iou/steps/IOURequestStepTaxRatePage.js | 2 +- 3 files changed, 10 insertions(+), 10 deletions(-) diff --git a/src/components/MoneyRequestConfirmationList.js b/src/components/MoneyRequestConfirmationList.js index 22f733526e2a..8dd71def21e7 100755 --- a/src/components/MoneyRequestConfirmationList.js +++ b/src/components/MoneyRequestConfirmationList.js @@ -730,30 +730,30 @@ function MoneyRequestConfirmationList(props) { {shouldShowTaxRate && ( Navigation.navigate(ROUTES.MONEY_REQUEST_TAX_RATE.getRoute(props.iouType, props.reportID))} disabled={didConfirm} interactive={!props.isReadOnly} - brickRoadIndicator='' - error='' + brickRoadIndicator="" + error="" /> )} {shouldShowTaxAmount && ( Navigation.navigate(ROUTES.MONEY_REQUEST_TAX_AMOUNT.getRoute(props.iouType, props.reportID))} disabled={didConfirm} interactive={!props.isReadOnly} - brickRoadIndicator='' - error='' + brickRoadIndicator="" + error="" /> )} diff --git a/src/pages/iou/steps/IOURequestStepTaxAmountPage.js b/src/pages/iou/steps/IOURequestStepTaxAmountPage.js index c12aada5d859..ff57674f7889 100644 --- a/src/pages/iou/steps/IOURequestStepTaxAmountPage.js +++ b/src/pages/iou/steps/IOURequestStepTaxAmountPage.js @@ -104,7 +104,7 @@ function IOURequestStepTaxAmountPage({route, iou}) { {content} diff --git a/src/pages/iou/steps/IOURequestStepTaxRatePage.js b/src/pages/iou/steps/IOURequestStepTaxRatePage.js index 58128e643f61..739275b73fb7 100644 --- a/src/pages/iou/steps/IOURequestStepTaxRatePage.js +++ b/src/pages/iou/steps/IOURequestStepTaxRatePage.js @@ -44,7 +44,7 @@ function IOURequestStepTaxRatePage({route}) { > <> navigateBack()} /> From cb7ba8208be15db5589ec7cddd53fe77a367ea48 Mon Sep 17 00:00:00 2001 From: cdOut <88325488+cdOut@users.noreply.github.com> Date: Thu, 7 Dec 2023 15:36:38 +0100 Subject: [PATCH 006/209] migrate MoneyRequestHeaderStatusBar to typescript --- ...rStatusBar.js => MoneyRequestHeaderStatusBar.tsx} | 12 +++++------- 1 file changed, 5 insertions(+), 7 deletions(-) rename src/components/{MoneyRequestHeaderStatusBar.js => MoneyRequestHeaderStatusBar.tsx} (80%) diff --git a/src/components/MoneyRequestHeaderStatusBar.js b/src/components/MoneyRequestHeaderStatusBar.tsx similarity index 80% rename from src/components/MoneyRequestHeaderStatusBar.js rename to src/components/MoneyRequestHeaderStatusBar.tsx index fc91678cf6fb..09ff6560e14d 100644 --- a/src/components/MoneyRequestHeaderStatusBar.js +++ b/src/components/MoneyRequestHeaderStatusBar.tsx @@ -1,21 +1,20 @@ -import PropTypes from 'prop-types'; import React from 'react'; import {View} from 'react-native'; import useThemeStyles from '@styles/useThemeStyles'; import Text from './Text'; -const propTypes = { +type MoneyRequestHeaderStatusBarProps = { /** Title displayed in badge */ - title: PropTypes.string.isRequired, + title: string; /** Banner Description */ - description: PropTypes.string.isRequired, + description: string; /** Whether we show the border bottom */ - shouldShowBorderBottom: PropTypes.bool.isRequired, + shouldShowBorderBottom: boolean; }; -function MoneyRequestHeaderStatusBar({title, description, shouldShowBorderBottom}) { +function MoneyRequestHeaderStatusBar({title, description, shouldShowBorderBottom}: MoneyRequestHeaderStatusBarProps) { const styles = useThemeStyles(); const borderBottomStyle = shouldShowBorderBottom ? styles.borderBottom : {}; return ( @@ -31,6 +30,5 @@ function MoneyRequestHeaderStatusBar({title, description, shouldShowBorderBottom } MoneyRequestHeaderStatusBar.displayName = 'MoneyRequestHeaderStatusBar'; -MoneyRequestHeaderStatusBar.propTypes = propTypes; export default MoneyRequestHeaderStatusBar; From 71b49b9e0eb166a1586729fc20eb9aacd538842d Mon Sep 17 00:00:00 2001 From: Etotaziba Olei Date: Thu, 7 Dec 2023 23:46:25 +0100 Subject: [PATCH 007/209] check policy expense chat: a flag for showing tax rate --- src/components/MoneyRequestConfirmationList.js | 9 +++------ 1 file changed, 3 insertions(+), 6 deletions(-) diff --git a/src/components/MoneyRequestConfirmationList.js b/src/components/MoneyRequestConfirmationList.js index 8dd71def21e7..6abad7c28058 100755 --- a/src/components/MoneyRequestConfirmationList.js +++ b/src/components/MoneyRequestConfirmationList.js @@ -242,10 +242,7 @@ function MoneyRequestConfirmationList(props) { const shouldShowTags = props.isPolicyExpenseChat && (props.iouTag || OptionsListUtils.hasEnabledOptions(_.values(policyTagList))); // A flag for showing tax rate - const shouldShowTaxRate = true; - - // A flag for showing tax rate - const shouldShowTaxAmount = true; + const shouldShowTax = props.isPolicyExpenseChat && props.policy.isTaxTrackingEnabled; // A flag for showing the billable field const shouldShowBillable = !lodashGet(props.policy, 'disabledFields.defaultBillable', true); @@ -727,7 +724,7 @@ function MoneyRequestConfirmationList(props) { /> )} - {shouldShowTaxRate && ( + {shouldShowTax && ( )} - {shouldShowTaxAmount && ( + {shouldShowTax && ( Date: Thu, 7 Dec 2023 23:52:11 +0100 Subject: [PATCH 008/209] add policy tax rates in tax rate page --- src/ONYXKEYS.ts | 2 ++ .../iou/steps/IOURequestStepTaxRatePage.js | 33 +++++++++++++++---- 2 files changed, 28 insertions(+), 7 deletions(-) diff --git a/src/ONYXKEYS.ts b/src/ONYXKEYS.ts index 5576eb64736d..e4344cd2095f 100755 --- a/src/ONYXKEYS.ts +++ b/src/ONYXKEYS.ts @@ -247,6 +247,8 @@ const ONYXKEYS = { POLICY_CATEGORIES: 'policyCategories_', POLICY_RECENTLY_USED_CATEGORIES: 'policyRecentlyUsedCategories_', POLICY_TAGS: 'policyTags_', + POLICY_TAX_RATE: 'policyTaxRates_', + POLICY_RECENTLY_USED_TAGS: 'policyRecentlyUsedTags_', WORKSPACE_INVITE_MEMBERS_DRAFT: 'workspaceInviteMembersDraft_', WORKSPACE_INVITE_MESSAGE_DRAFT: 'workspaceInviteMessageDraft_', diff --git a/src/pages/iou/steps/IOURequestStepTaxRatePage.js b/src/pages/iou/steps/IOURequestStepTaxRatePage.js index 739275b73fb7..8a5fc56339f6 100644 --- a/src/pages/iou/steps/IOURequestStepTaxRatePage.js +++ b/src/pages/iou/steps/IOURequestStepTaxRatePage.js @@ -1,9 +1,13 @@ import lodashGet from 'lodash/get'; import PropTypes from 'prop-types'; import React from 'react'; +import {withOnyx} from 'react-native-onyx'; import HeaderWithBackButton from '@components/HeaderWithBackButton'; import ScreenWrapper from '@components/ScreenWrapper'; +import compose from '@libs/compose'; import Navigation from '@libs/Navigation/Navigation'; +import * as IOU from '@userActions/IOU'; +import ONYXKEYS from '@src/ONYXKEYS'; import ROUTES from '@src/ROUTES'; const propTypes = { @@ -16,12 +20,6 @@ const propTypes = { /** The report ID of the IOU */ reportID: PropTypes.string, - - /** Which field we are editing */ - field: PropTypes.string, - - /** reportID for the "transaction thread" */ - threadReportID: PropTypes.string, }), }).isRequired, }; @@ -56,4 +54,25 @@ IOURequestStepTaxRatePage.propTypes = propTypes; IOURequestStepTaxRatePage.defaultProps = defaultProps; IOURequestStepTaxRatePage.displayName = 'IOURequestStepTaxRatePage'; -export default IOURequestStepTaxRatePage; +export default compose( + withOnyx({ + iou: { + key: ONYXKEYS.IOU, + }, + }), + // eslint-disable-next-line rulesdir/no-multiple-onyx-in-file + withOnyx({ + report: { + key: ({route, iou}) => { + const reportID = IOU.getIOUReportID(iou, route); + + return `${ONYXKEYS.COLLECTION.REPORT}${reportID}`; + }, + }, + }), + withOnyx({ + policyTaxRates: { + key: ({report}) => `${ONYXKEYS.COLLECTION.POLICY_TAX_RATE}${report ? report.policyID : '0'}`, + }, + }), +)(IOURequestStepTaxRatePage); From c6835007095796093ac8b0809093fc240ad1e884 Mon Sep 17 00:00:00 2001 From: Etotaziba Olei Date: Fri, 8 Dec 2023 03:27:33 +0100 Subject: [PATCH 009/209] add tax rates list threshold --- src/CONST.ts | 1 + 1 file changed, 1 insertion(+) diff --git a/src/CONST.ts b/src/CONST.ts index 13b79179f431..c95bd6fce27c 100755 --- a/src/CONST.ts +++ b/src/CONST.ts @@ -2778,6 +2778,7 @@ const CONST = { PARENT_CHILD_SEPARATOR: ': ', CATEGORY_LIST_THRESHOLD: 8, TAG_LIST_THRESHOLD: 8, + TAX_RATES_LIST_THRESHOLD: 8, DEMO_PAGES: { SAASTR: 'SaaStrDemoSetup', SBE: 'SbeDemoSetup', From c14bbe3d41131800f01ce7ef892a7eda11f57042 Mon Sep 17 00:00:00 2001 From: Etotaziba Olei Date: Fri, 8 Dec 2023 03:29:07 +0100 Subject: [PATCH 010/209] add tax rates sections in options utils --- src/libs/OptionsListUtils.js | 74 ++++++++++++++++++++++++++++++++++++ 1 file changed, 74 insertions(+) diff --git a/src/libs/OptionsListUtils.js b/src/libs/OptionsListUtils.js index 820339ebc6c4..b8bf1270ee84 100644 --- a/src/libs/OptionsListUtils.js +++ b/src/libs/OptionsListUtils.js @@ -1046,6 +1046,55 @@ function getTagListSections(rawTags, recentlyUsedTags, selectedOptions, searchIn return tagSections; } +function sortTaxRates(taxRates) { + const sortedtaxRates = _.chain(taxRates) + .values() + .sortBy((taxRates) => taxRates.name) + .value(); + + return sortedtaxRates; +} + +function getTaxRatesOptions(taxRates) { + return _.map(taxRates, (taxRate) => ({ + text: `${taxRate.name} (${taxRate.value})`, + keyForList: taxRate.name, + searchText: taxRate.name, + tooltipText: taxRate.name, + isDisabled: false, + })); +} + +function getTaxRatesSection(policyTaxRates, selectedOptions) { + const policyRatesSections = []; + + const sortedTaxRates = sortTaxRates(policyTaxRates.taxes); + const numberOfTaxRates = _.size(sortedTaxRates); + let indexOffset = 0; + + if (numberOfTaxRates === 0 && selectedOptions.length > 0) { + categorySections.push({ + // "Selected" section + title: '', + shouldShow: false, + indexOffset, + data: getCategoryOptionTree(getTaxRatesOptions), + }); + + return policyRatesSections; + } + + policyRatesSections.push({ + // "All" section when items amount more than the threshold + title: Localize.translateLocal('common.all'), + shouldShow: true, + indexOffset, + data: getTaxRatesOptions(sortedTaxRates), + }); + + return policyRatesSections; +} + /** * Build the options * @@ -1087,6 +1136,8 @@ function getOptions( recentlyUsedTags = [], canInviteUser = true, includeSelectedOptions = false, + includePolicyTaxRates, + policyTaxRates }, ) { if (includeCategories) { @@ -1099,6 +1150,7 @@ function getOptions( currentUserOption: null, categoryOptions, tagOptions: [], + policyTaxRatesOptions: [] }; } @@ -1112,6 +1164,21 @@ function getOptions( currentUserOption: null, categoryOptions: [], tagOptions, + policyTaxRatesOptions: [] + }; + } + + if (includePolicyTaxRates) { + const policyTaxRatesOptions = getTaxRatesSection(policyTaxRates, selectedOptions, searchInputValue, maxRecentReportsToShow); + + return { + recentReports: [], + personalDetails: [], + userToInvite: null, + currentUserOption: null, + categoryOptions: [], + tagOptions: [], + policyTaxRatesOptions }; } @@ -1123,6 +1190,7 @@ function getOptions( currentUserOption: null, categoryOptions: [], tagOptions: [], + policyTaxRatesOptions: [] }; } @@ -1389,6 +1457,7 @@ function getOptions( currentUserOption, categoryOptions: [], tagOptions: [], + policyTaxRatesOptions: [] }; } @@ -1478,6 +1547,7 @@ function getIOUConfirmationOptionsFromParticipants(participants, amountText) { * @param {Array} [recentlyUsedTags] * @param {boolean} [canInviteUser] * @param {boolean} [includeSelectedOptions] + * @param {Object} [policyTaxRates] * @returns {Object} */ function getFilteredOptions( @@ -1497,6 +1567,8 @@ function getFilteredOptions( recentlyUsedTags = [], canInviteUser = true, includeSelectedOptions = false, + includePolicyTaxRates = false, + policyTaxRates = {}, ) { return getOptions(reports, personalDetails, { betas, @@ -1516,6 +1588,8 @@ function getFilteredOptions( recentlyUsedTags, canInviteUser, includeSelectedOptions, + includePolicyTaxRates, + policyTaxRates, }); } From 88d352dee9dde099d43bacc415a8688e4d698559 Mon Sep 17 00:00:00 2001 From: Etotaziba Olei Date: Fri, 8 Dec 2023 03:29:52 +0100 Subject: [PATCH 011/209] add tax propTypes --- src/components/taxPropTypes.js | 28 ++++++++++++++++++++++++++++ 1 file changed, 28 insertions(+) create mode 100644 src/components/taxPropTypes.js diff --git a/src/components/taxPropTypes.js b/src/components/taxPropTypes.js new file mode 100644 index 000000000000..ff5a45518c28 --- /dev/null +++ b/src/components/taxPropTypes.js @@ -0,0 +1,28 @@ +import PropTypes from 'prop-types'; + +const taxPropTypes = PropTypes.shape({ + /** Name of a tax */ + name: PropTypes.string.isRequired, + + /** value of a tax */ + value: PropTypes.string.isRequired, +}); + +export default PropTypes.objectOf( + PropTypes.shape({ + /** Defualt name of taxes */ + name: PropTypes.string.isRequired, + + /** Defualt external ID of taxes */ + defaultExternalID: PropTypes.string.isRequired, + + /** Default value of taxes */ + defaultValue: PropTypes.string.isRequired, + + /** Default Foreign Tax ID */ + foreignTaxDefault: PropTypes.string.isRequired, + + /** List of Taxes names and values */ + taxes: PropTypes.objectOf(taxPropTypes), + }), +); From 8b26228dbcc697da536d70e251338bc70c669274 Mon Sep 17 00:00:00 2001 From: Etotaziba Olei Date: Fri, 8 Dec 2023 03:30:17 +0100 Subject: [PATCH 012/209] add tax pickerPropTypes --- .../TaxPicker/taxPickerPropTypes.js | 22 +++++++++++++++++++ 1 file changed, 22 insertions(+) create mode 100644 src/components/TaxPicker/taxPickerPropTypes.js diff --git a/src/components/TaxPicker/taxPickerPropTypes.js b/src/components/TaxPicker/taxPickerPropTypes.js new file mode 100644 index 000000000000..12757e3d614b --- /dev/null +++ b/src/components/TaxPicker/taxPickerPropTypes.js @@ -0,0 +1,22 @@ +import PropTypes from 'prop-types'; +import taxPropTypes from '@components/taxPropTypes'; + +const propTypes = { + /** The selected tax rate of an expense */ + selectedTaxRate: PropTypes.string, + + /* Onyx Props */ + /** Collection of tax rates attached to a policy */ + policyTaxRates: taxPropTypes, + + + /** Callback to fire when a tax is pressed */ + onSubmit: PropTypes.func.isRequired, +}; + +const defaultProps = { + selectedTaxRate: '', + policyTaxRates: {}, +}; + +export {propTypes, defaultProps}; From e4be96d7aa7303eb23f831b0dc8226cd3ebc5273 Mon Sep 17 00:00:00 2001 From: Etotaziba Olei Date: Fri, 8 Dec 2023 03:30:40 +0100 Subject: [PATCH 013/209] add tax picker --- src/components/TaxPicker/index.js | 79 +++++++++++++++++++++++++++++++ 1 file changed, 79 insertions(+) create mode 100644 src/components/TaxPicker/index.js diff --git a/src/components/TaxPicker/index.js b/src/components/TaxPicker/index.js new file mode 100644 index 000000000000..cbd8a84e9370 --- /dev/null +++ b/src/components/TaxPicker/index.js @@ -0,0 +1,79 @@ +import lodashGet from 'lodash/get'; +import React, {useMemo, useState} from 'react'; +import _ from 'underscore'; +import OptionsSelector from '@components/OptionsSelector'; +import useLocalize from '@hooks/useLocalize'; +import * as OptionsListUtils from '@libs/OptionsListUtils'; +import useThemeStyles from '@styles/useThemeStyles'; +import {defaultProps, propTypes} from './taxPickerPropTypes'; + +function TaxPicker({selectedTaxRate, policyTaxRates, onSubmit}) { + const styles = useThemeStyles(); + const {translate} = useLocalize(); + const [searchValue, setSearchValue] = useState(''); + + const selectedOptions = useMemo(() => { + if (!selectedTaxRate) { + return []; + } + + return [ + { + name: selectedTaxRate, + enabled: true, + accountID: null, + }, + ]; + }, [selectedTaxRate]); + + const sections = useMemo(() => { + const {policyTaxRatesOptions} = OptionsListUtils.getFilteredOptions( + {}, // reports {} + {}, // personalDetails {} + [], // betas [] + searchValue, // searchValue string + selectedOptions, // selectedOptions any[] + [], // excludedLogins any[] + false, // includeOwnedWorkspaceChats boolean + false, // includeP2P boolean + false, // includeCategories boolean + {}, // categories {} + [], // recentlyUsedCategories string[] + false, // includeTags boolean + {}, // tags {} + [], // recentlyUsedTags string[] + false, // canInviteUser boolean + false, // includeSelectedOptions + true, // includePolicyTaxRates boolean + policyTaxRates // policyTaxRates {} + ); + return policyTaxRatesOptions; + }, [policyTaxRates, searchValue, selectedOptions]); + + const selectedOptionKey = lodashGet(_.filter(lodashGet(sections, '[0].data', []), (taxRate) => taxRate.searchText === selectedTaxRate)[0], 'keyForList'); + + return ( + + ); +} + +TaxPicker.displayName = 'TaxPicker'; +TaxPicker.propTypes = propTypes; +TaxPicker.defaultProps = defaultProps; + +export default TaxPicker; From 11a469136f0fad9e2434919c9eebf90cf45ea8dc Mon Sep 17 00:00:00 2001 From: Etotaziba Olei Date: Fri, 8 Dec 2023 03:31:24 +0100 Subject: [PATCH 014/209] use TaxPicker in tax rates page --- src/pages/iou/steps/IOURequestStepTaxRatePage.js | 8 +++++++- 1 file changed, 7 insertions(+), 1 deletion(-) diff --git a/src/pages/iou/steps/IOURequestStepTaxRatePage.js b/src/pages/iou/steps/IOURequestStepTaxRatePage.js index 8a5fc56339f6..a11377fa2d62 100644 --- a/src/pages/iou/steps/IOURequestStepTaxRatePage.js +++ b/src/pages/iou/steps/IOURequestStepTaxRatePage.js @@ -4,6 +4,7 @@ import React from 'react'; import {withOnyx} from 'react-native-onyx'; import HeaderWithBackButton from '@components/HeaderWithBackButton'; import ScreenWrapper from '@components/ScreenWrapper'; +import TaxPicker from '@components/TaxPicker'; import compose from '@libs/compose'; import Navigation from '@libs/Navigation/Navigation'; import * as IOU from '@userActions/IOU'; @@ -26,7 +27,7 @@ const propTypes = { const defaultProps = {}; -function IOURequestStepTaxRatePage({route}) { +function IOURequestStepTaxRatePage({route, policyTaxRates}) { const iouType = lodashGet(route, 'params.iouType', ''); const reportID = lodashGet(route, 'params.reportID', ''); @@ -45,6 +46,11 @@ function IOURequestStepTaxRatePage({route}) { title="Tax Rate" onBackButtonPress={() => navigateBack()} /> + {}} + /> ); From df81cf5d687ae1e6021f47a4f0e46012d3ea91f9 Mon Sep 17 00:00:00 2001 From: Etotaziba Olei Date: Fri, 8 Dec 2023 03:44:50 +0100 Subject: [PATCH 015/209] fix lint --- src/components/TaxPicker/index.js | 2 +- .../TaxPicker/taxPickerPropTypes.js | 1 - src/components/taxPropTypes.js | 6 +++--- src/libs/OptionsListUtils.js | 21 ++++++++++--------- .../iou/steps/IOURequestStepTaxRatePage.js | 10 ++++++--- 5 files changed, 22 insertions(+), 18 deletions(-) diff --git a/src/components/TaxPicker/index.js b/src/components/TaxPicker/index.js index cbd8a84e9370..befe5530d891 100644 --- a/src/components/TaxPicker/index.js +++ b/src/components/TaxPicker/index.js @@ -45,7 +45,7 @@ function TaxPicker({selectedTaxRate, policyTaxRates, onSubmit}) { false, // canInviteUser boolean false, // includeSelectedOptions true, // includePolicyTaxRates boolean - policyTaxRates // policyTaxRates {} + policyTaxRates, // policyTaxRates {} ); return policyTaxRatesOptions; }, [policyTaxRates, searchValue, selectedOptions]); diff --git a/src/components/TaxPicker/taxPickerPropTypes.js b/src/components/TaxPicker/taxPickerPropTypes.js index 12757e3d614b..d0cd8e896493 100644 --- a/src/components/TaxPicker/taxPickerPropTypes.js +++ b/src/components/TaxPicker/taxPickerPropTypes.js @@ -9,7 +9,6 @@ const propTypes = { /** Collection of tax rates attached to a policy */ policyTaxRates: taxPropTypes, - /** Callback to fire when a tax is pressed */ onSubmit: PropTypes.func.isRequired, }; diff --git a/src/components/taxPropTypes.js b/src/components/taxPropTypes.js index ff5a45518c28..91f220b92395 100644 --- a/src/components/taxPropTypes.js +++ b/src/components/taxPropTypes.js @@ -4,8 +4,8 @@ const taxPropTypes = PropTypes.shape({ /** Name of a tax */ name: PropTypes.string.isRequired, - /** value of a tax */ - value: PropTypes.string.isRequired, + /** value of a tax */ + value: PropTypes.string.isRequired, }); export default PropTypes.objectOf( @@ -16,7 +16,7 @@ export default PropTypes.objectOf( /** Defualt external ID of taxes */ defaultExternalID: PropTypes.string.isRequired, - /** Default value of taxes */ + /** Default value of taxes */ defaultValue: PropTypes.string.isRequired, /** Default Foreign Tax ID */ diff --git a/src/libs/OptionsListUtils.js b/src/libs/OptionsListUtils.js index b8bf1270ee84..c062924234ce 100644 --- a/src/libs/OptionsListUtils.js +++ b/src/libs/OptionsListUtils.js @@ -1049,7 +1049,7 @@ function getTagListSections(rawTags, recentlyUsedTags, selectedOptions, searchIn function sortTaxRates(taxRates) { const sortedtaxRates = _.chain(taxRates) .values() - .sortBy((taxRates) => taxRates.name) + .sortBy((taxRate) => taxRate.name) .value(); return sortedtaxRates; @@ -1057,7 +1057,7 @@ function sortTaxRates(taxRates) { function getTaxRatesOptions(taxRates) { return _.map(taxRates, (taxRate) => ({ - text: `${taxRate.name} (${taxRate.value})`, + text: `${taxRate.name} (${taxRate.value})`, keyForList: taxRate.name, searchText: taxRate.name, tooltipText: taxRate.name, @@ -1070,10 +1070,10 @@ function getTaxRatesSection(policyTaxRates, selectedOptions) { const sortedTaxRates = sortTaxRates(policyTaxRates.taxes); const numberOfTaxRates = _.size(sortedTaxRates); - let indexOffset = 0; + const indexOffset = 0; if (numberOfTaxRates === 0 && selectedOptions.length > 0) { - categorySections.push({ + policyRatesSections.push({ // "Selected" section title: '', shouldShow: false, @@ -1137,7 +1137,7 @@ function getOptions( canInviteUser = true, includeSelectedOptions = false, includePolicyTaxRates, - policyTaxRates + policyTaxRates, }, ) { if (includeCategories) { @@ -1150,7 +1150,7 @@ function getOptions( currentUserOption: null, categoryOptions, tagOptions: [], - policyTaxRatesOptions: [] + policyTaxRatesOptions: [], }; } @@ -1164,7 +1164,7 @@ function getOptions( currentUserOption: null, categoryOptions: [], tagOptions, - policyTaxRatesOptions: [] + policyTaxRatesOptions: [], }; } @@ -1178,7 +1178,7 @@ function getOptions( currentUserOption: null, categoryOptions: [], tagOptions: [], - policyTaxRatesOptions + policyTaxRatesOptions, }; } @@ -1190,7 +1190,7 @@ function getOptions( currentUserOption: null, categoryOptions: [], tagOptions: [], - policyTaxRatesOptions: [] + policyTaxRatesOptions: [], }; } @@ -1457,7 +1457,7 @@ function getOptions( currentUserOption, categoryOptions: [], tagOptions: [], - policyTaxRatesOptions: [] + policyTaxRatesOptions: [], }; } @@ -1547,6 +1547,7 @@ function getIOUConfirmationOptionsFromParticipants(participants, amountText) { * @param {Array} [recentlyUsedTags] * @param {boolean} [canInviteUser] * @param {boolean} [includeSelectedOptions] + * @param {boolean} [includePolicyTaxRates] * @param {Object} [policyTaxRates] * @returns {Object} */ diff --git a/src/pages/iou/steps/IOURequestStepTaxRatePage.js b/src/pages/iou/steps/IOURequestStepTaxRatePage.js index a11377fa2d62..50dc64b735a4 100644 --- a/src/pages/iou/steps/IOURequestStepTaxRatePage.js +++ b/src/pages/iou/steps/IOURequestStepTaxRatePage.js @@ -5,6 +5,7 @@ import {withOnyx} from 'react-native-onyx'; import HeaderWithBackButton from '@components/HeaderWithBackButton'; import ScreenWrapper from '@components/ScreenWrapper'; import TaxPicker from '@components/TaxPicker'; +import taxPropTypes from '@components/taxPropTypes'; import compose from '@libs/compose'; import Navigation from '@libs/Navigation/Navigation'; import * as IOU from '@userActions/IOU'; @@ -23,9 +24,12 @@ const propTypes = { reportID: PropTypes.string, }), }).isRequired, + policyTaxRates: taxPropTypes, }; -const defaultProps = {}; +const defaultProps = { + policyTaxRates: {}, +}; function IOURequestStepTaxRatePage({route, policyTaxRates}) { const iouType = lodashGet(route, 'params.iouType', ''); @@ -47,9 +51,9 @@ function IOURequestStepTaxRatePage({route, policyTaxRates}) { onBackButtonPress={() => navigateBack()} /> {}} + onSubmit={() => {}} /> From 3741ecf64429d71571f68b8f70373622125ae091 Mon Sep 17 00:00:00 2001 From: tienifr Date: Fri, 8 Dec 2023 14:04:43 +0700 Subject: [PATCH 016/209] fix: inconsistency between autocomplete address and displayed address --- src/components/AddressSearch/index.js | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/src/components/AddressSearch/index.js b/src/components/AddressSearch/index.js index 2fed1d153947..3b8727cada8d 100644 --- a/src/components/AddressSearch/index.js +++ b/src/components/AddressSearch/index.js @@ -192,7 +192,7 @@ function AddressSearch({ // amount of data massaging needs to happen for what the parent expects to get from this function. if (_.size(details)) { onPress({ - address: lodashGet(details, 'description'), + address: autocompleteData.description || lodashGet(details, 'formatted_address', ''), lat: lodashGet(details, 'geometry.location.lat', 0), lng: lodashGet(details, 'geometry.location.lng', 0), name: lodashGet(details, 'name'), @@ -261,7 +261,7 @@ function AddressSearch({ lat: lodashGet(details, 'geometry.location.lat', 0), lng: lodashGet(details, 'geometry.location.lng', 0), - address: lodashGet(details, 'formatted_address', ''), + address: autocompleteData.description || lodashGet(details, 'formatted_address', ''), }; // If the address is not in the US, use the full length state name since we're displaying the address's From a4013d8df0a453facfdb512008355da900a1b49e Mon Sep 17 00:00:00 2001 From: tienifr Date: Fri, 8 Dec 2023 14:09:35 +0700 Subject: [PATCH 017/209] fix typo --- src/components/AddressSearch/index.js | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/components/AddressSearch/index.js b/src/components/AddressSearch/index.js index 3b8727cada8d..379804bf67c6 100644 --- a/src/components/AddressSearch/index.js +++ b/src/components/AddressSearch/index.js @@ -192,7 +192,7 @@ function AddressSearch({ // amount of data massaging needs to happen for what the parent expects to get from this function. if (_.size(details)) { onPress({ - address: autocompleteData.description || lodashGet(details, 'formatted_address', ''), + address: autocompleteData.description || lodashGet(details, 'description', ''), lat: lodashGet(details, 'geometry.location.lat', 0), lng: lodashGet(details, 'geometry.location.lng', 0), name: lodashGet(details, 'name'), From c34095ec90909a056c62222c1fb34c149a85cefc Mon Sep 17 00:00:00 2001 From: Etotaziba Olei Date: Fri, 8 Dec 2023 10:55:00 +0100 Subject: [PATCH 018/209] remove whitespace --- src/ONYXKEYS.ts | 1 - 1 file changed, 1 deletion(-) diff --git a/src/ONYXKEYS.ts b/src/ONYXKEYS.ts index e4344cd2095f..14f1b5d34852 100755 --- a/src/ONYXKEYS.ts +++ b/src/ONYXKEYS.ts @@ -248,7 +248,6 @@ const ONYXKEYS = { POLICY_RECENTLY_USED_CATEGORIES: 'policyRecentlyUsedCategories_', POLICY_TAGS: 'policyTags_', POLICY_TAX_RATE: 'policyTaxRates_', - POLICY_RECENTLY_USED_TAGS: 'policyRecentlyUsedTags_', WORKSPACE_INVITE_MEMBERS_DRAFT: 'workspaceInviteMembersDraft_', WORKSPACE_INVITE_MESSAGE_DRAFT: 'workspaceInviteMessageDraft_', From 2f78afd65e0e3ec8244197e6bebfdcc2f2af2a8d Mon Sep 17 00:00:00 2001 From: Etotaziba Olei Date: Sun, 10 Dec 2023 02:26:02 +0100 Subject: [PATCH 019/209] add default amount to avoid Nan --- src/libs/CurrencyUtils.ts | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/libs/CurrencyUtils.ts b/src/libs/CurrencyUtils.ts index 4829ce115592..b8f5b434cc4a 100644 --- a/src/libs/CurrencyUtils.ts +++ b/src/libs/CurrencyUtils.ts @@ -99,7 +99,7 @@ function convertToFrontendAmount(amountAsInt: number): number { * @param currency - IOU currency * @param shouldFallbackToTbd - whether to return 'TBD' instead of a falsy value (e.g. 0.00) */ -function convertToDisplayString(amountInCents: number, currency: string = CONST.CURRENCY.USD, shouldFallbackToTbd = false): string { +function convertToDisplayString(amountInCents = 0, currency: string = CONST.CURRENCY.USD, shouldFallbackToTbd = false): string { if (shouldFallbackToTbd && !amountInCents) { return Localize.translateLocal('common.tbd'); } From 8098ae1dfad5315d7c0434121dc0da4da5cb989a Mon Sep 17 00:00:00 2001 From: Etotaziba Olei Date: Sun, 10 Dec 2023 02:27:43 +0100 Subject: [PATCH 020/209] add default title for tax rate and format amount --- .../MoneyRequestConfirmationList.js | 24 +++++++++++++++---- 1 file changed, 20 insertions(+), 4 deletions(-) diff --git a/src/components/MoneyRequestConfirmationList.js b/src/components/MoneyRequestConfirmationList.js index 6abad7c28058..0d0ce3b92e56 100755 --- a/src/components/MoneyRequestConfirmationList.js +++ b/src/components/MoneyRequestConfirmationList.js @@ -166,6 +166,11 @@ const propTypes = { /** Holds data related to Money Request view state, rather than the underlying Money Request data. */ iou: iouPropTypes, + + transactionsDraft: PropTypes.shape({ + taxRate: PropTypes.string, + taxAmount: PropTypes.number, + }), }; const defaultProps = { @@ -200,6 +205,10 @@ const defaultProps = { shouldShowSmartScanFields: true, isPolicyExpenseChat: false, iou: iouDefaultProps, + transactionsDraft: { + taxRate: null, + taxAmount: null, + }, }; function MoneyRequestConfirmationList(props) { @@ -209,7 +218,7 @@ function MoneyRequestConfirmationList(props) { // Prop functions pass props itself as a "this" value to the function which means they change every time props change. const {onSendMoney, onConfirm, onSelectParticipant} = props; const {translate, toLocaleDigit} = useLocalize(); - const transaction = props.isEditingSplitBill ? props.draftTransaction || props.transaction : props.transaction; + const transaction = props.isEditingSplitBill ? props.splitTransactionDraft || props.transaction : props.transaction; const {canUseViolations} = usePermissions(); const isTypeRequest = props.iouType === CONST.IOU.TYPE.REQUEST; @@ -255,6 +264,7 @@ function MoneyRequestConfirmationList(props) { shouldCalculateDistanceAmount ? DistanceRequestUtils.getDistanceRequestAmount(distance, unit, rate) : props.iouAmount, props.isDistanceRequest ? currency : props.iouCurrencyCode, ); + const formattedTaxAmount = CurrencyUtils.convertToDisplayString(props.transactionsDraft.taxAmount, props.iouCurrencyCode); const isFocused = useIsFocused(); const [formError, setFormError] = useState(''); @@ -727,7 +737,7 @@ function MoneyRequestConfirmationList(props) { {shouldShowTax && ( `${ONYXKEYS.COLLECTION.POLICY}${policyID}`, selector: DistanceRequestUtils.getDefaultMileageRate, }, - draftTransaction: { + splitTransactionDraft: { key: ({transactionID}) => `${ONYXKEYS.COLLECTION.SPLIT_TRANSACTION_DRAFT}${transactionID}`, }, transaction: { key: ({transactionID}) => `${ONYXKEYS.COLLECTION.TRANSACTION}${transactionID}`, }, + transactionsDraft: { + key: ({transactionID}) => `${ONYXKEYS.COLLECTION.TRANSACTION_DRAFT}${transactionID}`, + }, policy: { key: ({policyID}) => `${ONYXKEYS.COLLECTION.POLICY}${policyID}`, }, + policyTaxRates: { + key: ({policyID}) => `${ONYXKEYS.COLLECTION.POLICY_TAX_RATE}${policyID}`, + }, iou: { key: ONYXKEYS.IOU, }, From 11ab5300460b5884b82ed0bda1d9f4f76cfa5c78 Mon Sep 17 00:00:00 2001 From: Etotaziba Olei Date: Sun, 10 Dec 2023 02:28:55 +0100 Subject: [PATCH 021/209] set tax rate and tax amount to onyx --- .../iou/steps/IOURequestStepTaxAmountPage.js | 30 ++++++++++++++----- .../iou/steps/IOURequestStepTaxRatePage.js | 29 ++++++++++++++++-- 2 files changed, 49 insertions(+), 10 deletions(-) diff --git a/src/pages/iou/steps/IOURequestStepTaxAmountPage.js b/src/pages/iou/steps/IOURequestStepTaxAmountPage.js index ff57674f7889..43d776dc3670 100644 --- a/src/pages/iou/steps/IOURequestStepTaxAmountPage.js +++ b/src/pages/iou/steps/IOURequestStepTaxAmountPage.js @@ -7,6 +7,7 @@ import {withOnyx} from 'react-native-onyx'; import FullPageNotFoundView from '@components/BlockingViews/FullPageNotFoundView'; import HeaderWithBackButton from '@components/HeaderWithBackButton'; import ScreenWrapper from '@components/ScreenWrapper'; +import compose from '@libs/compose'; import * as CurrencyUtils from '@libs/CurrencyUtils'; import * as IOUUtils from '@libs/IOUUtils'; import Navigation from '@libs/Navigation/Navigation'; @@ -36,13 +37,20 @@ const propTypes = { /** Holds data related to Money Request view state, rather than the underlying Money Request data. */ iou: iouPropTypes, + + transactionsDraft: PropTypes.shape({ + taxAmount: PropTypes.number, + }), }; const defaultProps = { iou: iouDefaultProps, + transactionsDraft: { + taxAmount: null, + }, }; -function IOURequestStepTaxAmountPage({route, iou}) { +function IOURequestStepTaxAmountPage({route, iou, transactionsDraft}) { const styles = useThemeStyles(); const textInput = useRef(null); const isEditing = Navigation.getActiveRoute().includes('taxAmount'); @@ -79,15 +87,16 @@ function IOURequestStepTaxAmountPage({route, iou}) { const updateTaxAmount = (currentAmount) => { const amountInSmallestCurrencyUnits = CurrencyUtils.convertToBackendAmount(Number.parseFloat(currentAmount)); - IOU.setMoneyRequestTaxAmount(amountInSmallestCurrencyUnits); - navigateBack(); + IOU.setMoneyRequestTaxAmount(iou.transactionID, amountInSmallestCurrencyUnits); + IOU.setMoneyRequestCurrency(currency); + Navigation.goBack(ROUTES.MONEY_REQUEST_CONFIRMATION.getRoute(iouType, reportID)); }; const content = ( (textInput.current = e)} onCurrencyButtonPress={navigateToCurrencySelectionPage} onSubmitButtonPress={updateTaxAmount} @@ -118,6 +127,13 @@ function IOURequestStepTaxAmountPage({route, iou}) { IOURequestStepTaxAmountPage.propTypes = propTypes; IOURequestStepTaxAmountPage.defaultProps = defaultProps; IOURequestStepTaxAmountPage.displayName = 'IOURequestStepTaxAmountPage'; -export default withOnyx({ - iou: {key: ONYXKEYS.IOU}, -})(IOURequestStepTaxAmountPage); +export default compose( + withOnyx({ + iou: {key: ONYXKEYS.IOU}, + }), + withOnyx({ + transactionsDraft: { + key: ({iou}) => `${ONYXKEYS.COLLECTION.TRANSACTION_DRAFT}${iou.transactionID}`, + }, + }), +)(IOURequestStepTaxAmountPage); diff --git a/src/pages/iou/steps/IOURequestStepTaxRatePage.js b/src/pages/iou/steps/IOURequestStepTaxRatePage.js index 50dc64b735a4..4d0e6bda659d 100644 --- a/src/pages/iou/steps/IOURequestStepTaxRatePage.js +++ b/src/pages/iou/steps/IOURequestStepTaxRatePage.js @@ -8,6 +8,7 @@ import TaxPicker from '@components/TaxPicker'; import taxPropTypes from '@components/taxPropTypes'; import compose from '@libs/compose'; import Navigation from '@libs/Navigation/Navigation'; +import {iouDefaultProps, iouPropTypes} from '@pages/iou/propTypes'; import * as IOU from '@userActions/IOU'; import ONYXKEYS from '@src/ONYXKEYS'; import ROUTES from '@src/ROUTES'; @@ -25,13 +26,24 @@ const propTypes = { }), }).isRequired, policyTaxRates: taxPropTypes, + + /** Holds data related to Money Request view state, rather than the underlying Money Request data. */ + iou: iouPropTypes, + + transactionsDraft: PropTypes.shape({ + taxRate: PropTypes.string, + }), }; const defaultProps = { policyTaxRates: {}, + iou: iouDefaultProps, + transactionsDraft: { + taxRate: null, + }, }; -function IOURequestStepTaxRatePage({route, policyTaxRates}) { +function IOURequestStepTaxRatePage({route, iou, policyTaxRates, transactionsDraft}) { const iouType = lodashGet(route, 'params.iouType', ''); const reportID = lodashGet(route, 'params.reportID', ''); @@ -39,6 +51,12 @@ function IOURequestStepTaxRatePage({route, policyTaxRates}) { Navigation.goBack(ROUTES.MONEY_REQUEST_CONFIRMATION.getRoute(iouType, reportID)); } + const updateTaxRates = (taxes) => { + IOU.setMoneyRequestTaxRate(iou.transactionID, taxes.text); + + Navigation.goBack(ROUTES.MONEY_REQUEST_CONFIRMATION.getRoute(iouType, reportID)); + }; + return ( navigateBack()} /> {}} + onSubmit={updateTaxRates} /> @@ -85,4 +103,9 @@ export default compose( key: ({report}) => `${ONYXKEYS.COLLECTION.POLICY_TAX_RATE}${report ? report.policyID : '0'}`, }, }), + withOnyx({ + transactionsDraft: { + key: ({iou}) => `${ONYXKEYS.COLLECTION.TRANSACTION_DRAFT}${iou.transactionID}`, + }, + }), )(IOURequestStepTaxRatePage); From 2db612015f48d8a6e4ed57bb3554c45fdaabdabe Mon Sep 17 00:00:00 2001 From: Pavlo Tsimura Date: Sun, 10 Dec 2023 14:11:52 +0100 Subject: [PATCH 022/209] Use PersonalDetailsUtils.getDisplayNameOrDefault where possible --- src/components/ArchivedReportFooter.tsx | 6 +++--- src/libs/OptionsListUtils.js | 18 +++++++++--------- src/libs/PersonalDetailsUtils.js | 11 ++++++----- src/libs/ReportUtils.ts | 8 ++++---- src/libs/SidebarUtils.ts | 2 +- src/libs/actions/Task.js | 3 ++- src/pages/DetailsPage.js | 10 ++++++---- src/pages/ProfilePage.js | 3 ++- src/pages/ReportParticipantsPage.js | 2 +- .../ReportActionCompose/SuggestionMention.js | 6 ++++-- src/pages/home/report/ReportActionItem.js | 4 ++-- 11 files changed, 40 insertions(+), 33 deletions(-) diff --git a/src/components/ArchivedReportFooter.tsx b/src/components/ArchivedReportFooter.tsx index 3187bf3604e8..0f5956836773 100644 --- a/src/components/ArchivedReportFooter.tsx +++ b/src/components/ArchivedReportFooter.tsx @@ -30,14 +30,14 @@ function ArchivedReportFooter({report, reportClosedAction, personalDetails = {}} const originalMessage = reportClosedAction?.actionName === CONST.REPORT.ACTIONS.TYPE.CLOSED ? reportClosedAction.originalMessage : null; const archiveReason = originalMessage?.reason ?? CONST.REPORT.ARCHIVE_REASON.DEFAULT; - let displayName = PersonalDetailsUtils.getDisplayNameOrDefault(personalDetails, [report.ownerAccountID, 'displayName']); + let displayName = PersonalDetailsUtils.getDisplayNameOrDefault(personalDetails?.[report.ownerAccountID ?? -1]); let oldDisplayName: string | undefined; if (archiveReason === CONST.REPORT.ARCHIVE_REASON.ACCOUNT_MERGED) { const newAccountID = originalMessage?.newAccountID; const oldAccountID = originalMessage?.oldAccountID; - displayName = PersonalDetailsUtils.getDisplayNameOrDefault(personalDetails, [newAccountID, 'displayName']); - oldDisplayName = PersonalDetailsUtils.getDisplayNameOrDefault(personalDetails, [oldAccountID, 'displayName']); + displayName = PersonalDetailsUtils.getDisplayNameOrDefault(personalDetails?.[newAccountID ?? -1]); + oldDisplayName = PersonalDetailsUtils.getDisplayNameOrDefault(personalDetails?.[oldAccountID ?? -1]); } const shouldRenderHTML = archiveReason !== CONST.REPORT.ARCHIVE_REASON.DEFAULT; diff --git a/src/libs/OptionsListUtils.js b/src/libs/OptionsListUtils.js index f06ca8e12fcc..6276dc0b0dea 100644 --- a/src/libs/OptionsListUtils.js +++ b/src/libs/OptionsListUtils.js @@ -201,7 +201,7 @@ function isPersonalDetailsReady(personalDetails) { function getParticipantsOption(participant, personalDetails) { const detail = getPersonalDetailsForAccountIDs([participant.accountID], personalDetails)[participant.accountID]; const login = detail.login || participant.login; - const displayName = PersonalDetailsUtils.getDisplayNameOrDefault(detail, 'displayName', LocalePhoneNumber.formatPhoneNumber(login)); + const displayName = PersonalDetailsUtils.getDisplayNameOrDefault(detail, LocalePhoneNumber.formatPhoneNumber(login)); return { keyForList: String(detail.accountID), login, @@ -246,8 +246,7 @@ function getParticipantNames(personalDetailList) { participantNames.add(participant.lastName.toLowerCase()); } if (participant.displayName) { - const displayName = PersonalDetailsUtils.getDisplayNameOrDefault(participant, 'displayName'); - participantNames.add(displayName.toLowerCase()); + participantNames.add(PersonalDetailsUtils.getDisplayNameOrDefault(participant).toLowerCase()); } }); return participantNames; @@ -301,9 +300,9 @@ function getSearchText(report, reportName, personalDetailList, isChatRoomOrPolic // so that we can match emails that have dots without explicitly writing the dots (e.g: fistlast@domain will match first.last@domain) // More info https://github.com/Expensify/App/issues/8007 searchTerms = searchTerms.concat([ - PersonalDetailsUtils.getDisplayNameOrDefault(personalDetail, 'displayName'), + PersonalDetailsUtils.getDisplayNameOrDefault(personalDetail, '', false), personalDetail.login, - personalDetail.login.replace(/\.(?=[^\s@]*@)/g, '') + personalDetail.login.replace(/\.(?=[^\s@]*@)/g, ''), ]); } } @@ -517,7 +516,8 @@ function createOption(accountIDs, personalDetails, report, reportActions = {}, { const lastMessageTextFromReport = getLastMessageTextForReport(report); const lastActorDetails = personalDetailMap[report.lastActorAccountID] || null; - let lastMessageText = hasMultipleParticipants && lastActorDetails && lastActorDetails.accountID !== currentUserAccountID ? `${PersonalDetailsUtils.getDisplayNameOrDefault(lastActorDetails, 'displayName')}: ` : ''; + let lastMessageText = + hasMultipleParticipants && lastActorDetails && lastActorDetails.accountID !== currentUserAccountID ? `${PersonalDetailsUtils.getDisplayNameOrDefault(lastActorDetails)}: ` : ''; lastMessageText += report ? lastMessageTextFromReport : ''; if (result.isArchivedRoom) { @@ -525,7 +525,7 @@ function createOption(accountIDs, personalDetails, report, reportActions = {}, { (lastReportActions[report.reportID] && lastReportActions[report.reportID].originalMessage && lastReportActions[report.reportID].originalMessage.reason) || CONST.REPORT.ARCHIVE_REASON.DEFAULT; lastMessageText = Localize.translate(preferredLocale, `reportArchiveReasons.${archiveReason}`, { - displayName: archiveReason.displayName || PersonalDetailsUtils.getDisplayNameOrDefault(lastActorDetails, 'displayName'), + displayName: archiveReason.displayName || PersonalDetailsUtils.getDisplayNameOrDefault(lastActorDetails), policyName: ReportUtils.getPolicyName(report), }); } @@ -1438,8 +1438,8 @@ function getSearchOptions(reports, personalDetails, searchValue = '', betas) { function getIOUConfirmationOptionsFromPayeePersonalDetail(personalDetail, amountText) { const formattedLogin = LocalePhoneNumber.formatPhoneNumber(personalDetail.login); return { - text: PersonalDetailsUtils.getDisplayNameOrDefault(personalDetail, 'displayName', formattedLogin), - alternateText: formattedLogin || PersonalDetailsUtils.getDisplayNameOrDefault(personalDetail, 'displayName'), + text: PersonalDetailsUtils.getDisplayNameOrDefault(personalDetail, formattedLogin), + alternateText: formattedLogin || PersonalDetailsUtils.getDisplayNameOrDefault(personalDetail, '', false), icons: [ { source: UserUtils.getAvatar(personalDetail.avatar, personalDetail.accountID), diff --git a/src/libs/PersonalDetailsUtils.js b/src/libs/PersonalDetailsUtils.js index 30b710e9d15c..0525540d1ee1 100644 --- a/src/libs/PersonalDetailsUtils.js +++ b/src/libs/PersonalDetailsUtils.js @@ -18,15 +18,16 @@ Onyx.connect({ }); /** - * @param {Object | Null} passedPersonalDetails - * @param {Array | String} pathToDisplayName + * @param {Object | Null | Undefined} passedPersonalDetails * @param {String} [defaultValue] optional default display name value + * @param {Boolean} [shouldFallbackToHidden] whether to fall back to 'hidden' if the display name and default value are empty * @returns {String} */ -function getDisplayNameOrDefault(passedPersonalDetails, pathToDisplayName, defaultValue = '') { - const displayName = lodashGet(passedPersonalDetails, pathToDisplayName, '').replace(CONST.REGEX.MERGED_ACCOUNT_PREFIX, ''); +function getDisplayNameOrDefault(passedPersonalDetails, defaultValue = '', shouldFallbackToHidden = true) { + const displayName = lodashGet(passedPersonalDetails, 'displayName', '').replace(CONST.REGEX.MERGED_ACCOUNT_PREFIX, ''); + const fallbackValue = shouldFallbackToHidden ? Localize.translateLocal('common.hidden') : ''; - return displayName || defaultValue || Localize.translateLocal('common.hidden'); + return displayName || defaultValue || fallbackValue; } /** diff --git a/src/libs/ReportUtils.ts b/src/libs/ReportUtils.ts index 2329827dd376..3bf8dbeca4aa 100644 --- a/src/libs/ReportUtils.ts +++ b/src/libs/ReportUtils.ts @@ -1289,7 +1289,7 @@ function getIcons( const parentReportAction = ReportActionsUtils.getParentReportAction(report); const actorAccountID = parentReportAction.actorAccountID; - const actorDisplayName = PersonalDetailsUtils.getDisplayNameOrDefault(allPersonalDetails, [actorAccountID ?? -1, 'displayName']); + const actorDisplayName = PersonalDetailsUtils.getDisplayNameOrDefault(allPersonalDetails?.[actorAccountID ?? -1]); const actorIcon = { id: actorAccountID, source: UserUtils.getAvatar(personalDetails?.[actorAccountID ?? -1]?.avatar ?? '', actorAccountID ?? -1), @@ -1413,13 +1413,13 @@ function getDisplayNameForParticipant(accountID?: number, shouldUseShortForm = f // eslint-disable-next-line @typescript-eslint/prefer-nullish-coalescing return formattedLogin; } - const longName = PersonalDetailsUtils.getDisplayNameOrDefault(personalDetails, 'displayName', formattedLogin); - - const shortName = personalDetails.firstName ? personalDetails.firstName : longName; + const longName = PersonalDetailsUtils.getDisplayNameOrDefault(personalDetails, formattedLogin, false); if (!longName && shouldFallbackToHidden) { return Localize.translateLocal('common.hidden'); } + + const shortName = personalDetails.firstName ? personalDetails.firstName : longName; return shouldUseShortForm ? shortName : longName; } diff --git a/src/libs/SidebarUtils.ts b/src/libs/SidebarUtils.ts index 1da1469a2687..786846cc3d61 100644 --- a/src/libs/SidebarUtils.ts +++ b/src/libs/SidebarUtils.ts @@ -346,7 +346,7 @@ function getOptionData( case CONST.REPORT.ARCHIVE_REASON.POLICY_DELETED: { lastMessageText = Localize.translate(preferredLocale, `reportArchiveReasons.${archiveReason}`, { policyName: ReportUtils.getPolicyName(report, false, policy), - displayName: PersonalDetailsUtils.getDisplayNameOrDefault(lastActorDetails, 'displayName'), + displayName: PersonalDetailsUtils.getDisplayNameOrDefault(lastActorDetails), }); break; } diff --git a/src/libs/actions/Task.js b/src/libs/actions/Task.js index e5037d250d2e..b81f2a743ee8 100644 --- a/src/libs/actions/Task.js +++ b/src/libs/actions/Task.js @@ -8,6 +8,7 @@ import * as ErrorUtils from '@libs/ErrorUtils'; import * as LocalePhoneNumber from '@libs/LocalePhoneNumber'; import Navigation from '@libs/Navigation/Navigation'; import * as OptionsListUtils from '@libs/OptionsListUtils'; +import * as PersonalDetailsUtils from '@libs/PersonalDetailsUtils'; import * as ReportActionsUtils from '@libs/ReportActionsUtils'; import * as ReportUtils from '@libs/ReportUtils'; import * as UserUtils from '@libs/UserUtils'; @@ -668,7 +669,7 @@ function getAssignee(assigneeAccountID, personalDetails) { } return { icons: ReportUtils.getIconsForParticipants([details.accountID], personalDetails), - displayName: details.displayName, + displayName: PersonalDetailsUtils.getDisplayNameOrDefault(details), subtitle: details.login, }; } diff --git a/src/pages/DetailsPage.js b/src/pages/DetailsPage.js index 66345107dbb1..bf899ca27d4b 100755 --- a/src/pages/DetailsPage.js +++ b/src/pages/DetailsPage.js @@ -21,6 +21,7 @@ import Text from '@components/Text'; import UserDetailsTooltip from '@components/UserDetailsTooltip'; import withLocalize, {withLocalizePropTypes} from '@components/withLocalize'; import compose from '@libs/compose'; +import * as PersonalDetailsUtils from '@libs/PersonalDetailsUtils'; import * as ReportUtils from '@libs/ReportUtils'; import * as UserUtils from '@libs/UserUtils'; import useThemeStyles from '@styles/useThemeStyles'; @@ -120,6 +121,7 @@ function DetailsPage(props) { const phoneNumber = getPhoneNumber(details); const phoneOrEmail = isSMSLogin ? getPhoneNumber(details) : details.login; + const displayName = PersonalDetailsUtils.getDisplayNameOrDefault(details, '', false); const isCurrentUser = props.session.accountID === details.accountID; @@ -132,7 +134,7 @@ function DetailsPage(props) { )} - {Boolean(details.displayName) && ( + {Boolean(displayName) && ( - {details.displayName} + {displayName} )} {details.login ? ( @@ -194,7 +196,7 @@ function DetailsPage(props) { {!isCurrentUser && ( Report.navigateToAndOpenReport([login])} diff --git a/src/pages/ProfilePage.js b/src/pages/ProfilePage.js index 97ec3f99da3c..1b9127e15ad2 100755 --- a/src/pages/ProfilePage.js +++ b/src/pages/ProfilePage.js @@ -25,6 +25,7 @@ import UserDetailsTooltip from '@components/UserDetailsTooltip'; import withLocalize, {withLocalizePropTypes} from '@components/withLocalize'; import compose from '@libs/compose'; import Navigation from '@libs/Navigation/Navigation'; +import * as PersonalDetailsUtils from '@libs/PersonalDetailsUtils'; import * as ReportUtils from '@libs/ReportUtils'; import * as UserUtils from '@libs/UserUtils'; import * as ValidationUtils from '@libs/ValidationUtils'; @@ -103,7 +104,7 @@ function ProfilePage(props) { const accountID = Number(lodashGet(props.route.params, 'accountID', 0)); const details = lodashGet(props.personalDetails, accountID, ValidationUtils.isValidAccountRoute(accountID) ? {} : {isloading: false}); - const displayName = details.displayName ? details.displayName : props.translate('common.hidden'); + const displayName = PersonalDetailsUtils.getDisplayNameOrDefault(details); const avatar = lodashGet(details, 'avatar', UserUtils.getDefaultAvatar()); const fallbackIcon = lodashGet(details, 'fallbackIcon', ''); const originalFileName = lodashGet(details, 'originalFileName', ''); diff --git a/src/pages/ReportParticipantsPage.js b/src/pages/ReportParticipantsPage.js index ceaa53a41a6b..b40bdc1f7de1 100755 --- a/src/pages/ReportParticipantsPage.js +++ b/src/pages/ReportParticipantsPage.js @@ -60,7 +60,7 @@ const getAllParticipants = (report, personalDetails, translate) => .map((accountID, index) => { const userPersonalDetail = lodashGet(personalDetails, accountID, {displayName: personalDetails.displayName || translate('common.hidden'), avatar: ''}); const userLogin = LocalePhoneNumber.formatPhoneNumber(userPersonalDetail.login || '') || translate('common.hidden'); - const displayName = PersonalDetailsUtils.getDisplayNameOrDefault(userPersonalDetail, 'displayName'); + const displayName = PersonalDetailsUtils.getDisplayNameOrDefault(userPersonalDetail); return { alternateText: userLogin, diff --git a/src/pages/home/report/ReportActionCompose/SuggestionMention.js b/src/pages/home/report/ReportActionCompose/SuggestionMention.js index e55b96ad99f5..af3074eec06d 100644 --- a/src/pages/home/report/ReportActionCompose/SuggestionMention.js +++ b/src/pages/home/report/ReportActionCompose/SuggestionMention.js @@ -7,6 +7,7 @@ import {usePersonalDetails} from '@components/OnyxProvider'; import useArrowKeyFocusManager from '@hooks/useArrowKeyFocusManager'; import useLocalize from '@hooks/useLocalize'; import usePrevious from '@hooks/usePrevious'; +import * as PersonalDetailsUtils from '@libs/PersonalDetailsUtils'; import * as SuggestionsUtils from '@libs/SuggestionUtils'; import * as UserUtils from '@libs/UserUtils'; import CONST from '@src/CONST'; @@ -149,7 +150,8 @@ function SuggestionMention({ if (!detail.login || detail.isOptimisticPersonalDetail) { return false; } - const displayText = detail.displayName === formatPhoneNumber(detail.login) ? detail.displayName : `${detail.displayName} ${detail.login}`; + const displayName = PersonalDetailsUtils.getDisplayNameOrDefault(detail); + const displayText = displayName === formatPhoneNumber(detail.login) ? displayName : `${displayName} ${detail.login}`; if (searchValue && !displayText.toLowerCase().includes(searchValue.toLowerCase())) { return false; } @@ -159,7 +161,7 @@ function SuggestionMention({ const sortedPersonalDetails = _.sortBy(filteredPersonalDetails, (detail) => detail.displayName || detail.login); _.each(_.first(sortedPersonalDetails, CONST.AUTO_COMPLETE_SUGGESTER.MAX_AMOUNT_OF_SUGGESTIONS - suggestions.length), (detail) => { suggestions.push({ - text: detail.displayName, + text: PersonalDetailsUtils.getDisplayNameOrDefault(detail), alternateText: formatPhoneNumber(detail.login), login: detail.login, icons: [ diff --git a/src/pages/home/report/ReportActionItem.js b/src/pages/home/report/ReportActionItem.js index f850daaa1ffb..c6484b93e93e 100644 --- a/src/pages/home/report/ReportActionItem.js +++ b/src/pages/home/report/ReportActionItem.js @@ -373,7 +373,7 @@ function ReportActionItem(props) { ); } else if (props.action.actionName === CONST.REPORT.ACTIONS.TYPE.REIMBURSEMENTQUEUED) { - const submitterDisplayName = PersonalDetailsUtils.getDisplayNameOrDefault(personalDetails, [props.report.ownerAccountID, 'displayName']); + const submitterDisplayName = PersonalDetailsUtils.getDisplayNameOrDefault(lodashGet(personalDetails, props.report.ownerAccountID)); const paymentType = lodashGet(props.action, 'originalMessage.paymentType', ''); const isSubmitterOfUnsettledReport = ReportUtils.isCurrentUserSubmitter(props.report.reportID) && !ReportUtils.isSettled(props.report.reportID); @@ -421,7 +421,7 @@ function ReportActionItem(props) { ); } else if (props.action.actionName === CONST.REPORT.ACTIONS.TYPE.REIMBURSEMENTDEQUEUED) { - const submitterDisplayName = PersonalDetailsUtils.getDisplayNameOrDefault(personalDetails, [props.report.ownerAccountID, 'displayName']); + const submitterDisplayName = PersonalDetailsUtils.getDisplayNameOrDefault(lodashGet(personalDetails, props.report.ownerAccountID)); const amount = CurrencyUtils.convertToDisplayString(props.report.total, props.report.currency); children = ; From 3bfdf189a251a7ea7132a21f48adc0d50dc94728 Mon Sep 17 00:00:00 2001 From: dukenv0307 Date: Tue, 12 Dec 2023 14:51:25 +0700 Subject: [PATCH 023/209] display loading page for workspace page with section --- src/libs/actions/BankAccounts.ts | 34 ++++++++++++++++++- .../workspace/WorkspacePageWithSections.js | 34 +++++++++++++------ 2 files changed, 57 insertions(+), 11 deletions(-) diff --git a/src/libs/actions/BankAccounts.ts b/src/libs/actions/BankAccounts.ts index 8d0c59ab8d60..f7b7ec89c670 100644 --- a/src/libs/actions/BankAccounts.ts +++ b/src/libs/actions/BankAccounts.ts @@ -404,7 +404,39 @@ function verifyIdentityForBankAccount(bankAccountID: number, onfidoData: OnfidoD } function openWorkspaceView() { - API.read('OpenWorkspaceView', {}, {}); + API.read( + 'OpenWorkspaceView', + {}, + { + optimisticData: [ + { + onyxMethod: Onyx.METHOD.MERGE, + key: ONYXKEYS.REIMBURSEMENT_ACCOUNT, + value: { + isLoading: true, + }, + }, + ], + successData: [ + { + onyxMethod: Onyx.METHOD.MERGE, + key: ONYXKEYS.REIMBURSEMENT_ACCOUNT, + value: { + isLoading: false, + }, + }, + ], + failureData: [ + { + onyxMethod: Onyx.METHOD.MERGE, + key: ONYXKEYS.REIMBURSEMENT_ACCOUNT, + value: { + isLoading: false, + }, + }, + ], + }, + ); } function handlePlaidError(bankAccountID: number, error: string, errorDescription: string, plaidRequestID: string) { diff --git a/src/pages/workspace/WorkspacePageWithSections.js b/src/pages/workspace/WorkspacePageWithSections.js index bea94ed3ef4e..2450e2b29fb0 100644 --- a/src/pages/workspace/WorkspacePageWithSections.js +++ b/src/pages/workspace/WorkspacePageWithSections.js @@ -1,10 +1,11 @@ import lodashGet from 'lodash/get'; import PropTypes from 'prop-types'; -import React, {useEffect} from 'react'; +import React, {useEffect, useRef} from 'react'; import {View} from 'react-native'; import {withOnyx} from 'react-native-onyx'; import _ from 'underscore'; import FullPageNotFoundView from '@components/BlockingViews/FullPageNotFoundView'; +import FullScreenLoadingIndicator from '@components/FullscreenLoadingIndicator'; import HeaderWithBackButton from '@components/HeaderWithBackButton'; import ScreenWrapper from '@components/ScreenWrapper'; import ScrollViewWithContext from '@components/ScrollViewWithContext'; @@ -87,12 +88,19 @@ function WorkspacePageWithSections({backButtonRoute, children, footer, guidesCal const styles = useThemeStyles(); useNetwork({onReconnect: () => fetchData(shouldSkipVBBACall)}); + const isLoading = lodashGet(reimbursementAccount, 'isLoading', true); const achState = lodashGet(reimbursementAccount, 'achData.state', ''); const hasVBA = achState === BankAccount.STATE.OPEN; const isUsingECard = lodashGet(user, 'isUsingExpensifyCard', false); const policyID = lodashGet(route, 'params.policyID'); const policyName = lodashGet(policy, 'name'); const content = children(hasVBA, policyID, isUsingECard); + const firstRender = useRef(true); + + useEffect(() => { + // Because isLoading is false before merging in Onyx, we need firstRender ref to display loading page as well before isLoading is change to true + firstRender.current = false; + }, []); useEffect(() => { fetchData(shouldSkipVBBACall); @@ -117,17 +125,23 @@ function WorkspacePageWithSections({backButtonRoute, children, footer, guidesCal guidesCallTaskID={guidesCallTaskID} onBackButtonPress={() => Navigation.goBack(backButtonRoute || ROUTES.WORKSPACE_INITIAL.getRoute(policyID))} /> - {shouldUseScrollView ? ( - - {content} - + {isLoading || firstRender.current ? ( + ) : ( - content + <> + {shouldUseScrollView ? ( + + {content} + + ) : ( + content + )} + {footer} + )} - {footer} ); From bebace8ac01a95eb994d2c316cfc3898d241bf41 Mon Sep 17 00:00:00 2001 From: Etotaziba Olei Date: Wed, 13 Dec 2023 06:10:08 +0100 Subject: [PATCH 024/209] add defaulttax title --- src/components/MoneyRequestConfirmationList.js | 5 ++++- 1 file changed, 4 insertions(+), 1 deletion(-) diff --git a/src/components/MoneyRequestConfirmationList.js b/src/components/MoneyRequestConfirmationList.js index 0d0ce3b92e56..d82dfc358197 100755 --- a/src/components/MoneyRequestConfirmationList.js +++ b/src/components/MoneyRequestConfirmationList.js @@ -349,6 +349,9 @@ function MoneyRequestConfirmationList(props) { const canModifyParticipants = !props.isReadOnly && props.canModifyParticipants && props.hasMultipleParticipants; const shouldDisablePaidBySection = canModifyParticipants; + const defaulTaxKey = props.policyTaxRates.defaultExternalID; + const defaultTaxName = props.policyTaxRates.taxes[defaulTaxKey].name; + const optionSelectorSections = useMemo(() => { const sections = []; const unselectedParticipants = _.filter(props.selectedParticipants, (participant) => !participant.selected); @@ -737,7 +740,7 @@ function MoneyRequestConfirmationList(props) { {shouldShowTax && ( Date: Wed, 13 Dec 2023 06:14:19 +0100 Subject: [PATCH 025/209] update tax rate section --- src/libs/OptionsListUtils.js | 86 ++++++++++++++++++++++++++++++++---- 1 file changed, 78 insertions(+), 8 deletions(-) diff --git a/src/libs/OptionsListUtils.js b/src/libs/OptionsListUtils.js index c062924234ce..22d053347b27 100644 --- a/src/libs/OptionsListUtils.js +++ b/src/libs/OptionsListUtils.js @@ -639,6 +639,18 @@ function getEnabledCategoriesCount(options) { return _.filter(options, (option) => option.enabled).length; } +/** + * Calculates count of all tax enabled options + * + * @param {Object[]} options - an initial strings array + * @param {Boolean} options[].isDisabled - a flag to enable/disable option in a list + * @param {String} options[].name - a name of an option + * @returns {Number} + */ +function getEnabledTaxRateCount(options) { + return _.filter(options, (option) => !option.isDisabled).length; +} + /** * Verifies that there is at least one enabled option * @@ -1057,39 +1069,96 @@ function sortTaxRates(taxRates) { function getTaxRatesOptions(taxRates) { return _.map(taxRates, (taxRate) => ({ - text: `${taxRate.name} (${taxRate.value})`, + text: taxRate.name, keyForList: taxRate.name, searchText: taxRate.name, tooltipText: taxRate.name, - isDisabled: false, + isDisabled: taxRate.isDisabled, })); } -function getTaxRatesSection(policyTaxRates, selectedOptions) { +function getTaxRatesSection(policyTaxRates, selectedOptions, searchInputValue) { const policyRatesSections = []; const sortedTaxRates = sortTaxRates(policyTaxRates.taxes); - const numberOfTaxRates = _.size(sortedTaxRates); - const indexOffset = 0; + const enabledTaxRates = _.filter(sortedTaxRates, (taxRate) => !taxRate.isDisabled); + const numberOfTaxRates = _.size(enabledTaxRates); + + let indexOffset = 0; + // If all tax rates are disabled but there's a previously selected tag, show only the selected tag if (numberOfTaxRates === 0 && selectedOptions.length > 0) { + const selectedTaxRateOptions = _.map(selectedOptions, (option) => ({ + name: option.name, + // Should be marked as enabled to be able to be de-selected + isDisabled: false, + })); policyRatesSections.push({ // "Selected" section title: '', shouldShow: false, indexOffset, - data: getCategoryOptionTree(getTaxRatesOptions), + data: getTaxRatesOptions(selectedTaxRateOptions), }); return policyRatesSections; } + if (!_.isEmpty(searchInputValue)) { + const searchTaxRates = _.filter(enabledTaxRates, (taxRate) => taxRate.name.toLowerCase().includes(searchInputValue.toLowerCase())); + + policyRatesSections.push({ + // "Search" section + title: '', + shouldShow: true, + indexOffset, + data: getTaxRatesOptions(searchTaxRates), + }); + + return policyRatesSections; + } + + if (numberOfTaxRates < CONST.TAX_RATES_LIST_THRESHOLD) { + policyRatesSections.push({ + // "All" section when items amount less than the threshold + title: '', + shouldShow: false, + indexOffset, + data: getTaxRatesOptions(enabledTaxRates), + }); + + return policyRatesSections; + } + + const selectedOptionNames = _.map(selectedOptions, (selectedOption) => selectedOption.name); + const filteredTaxRates = _.filter(enabledTaxRates, (taxRate) => !_.includes(selectedOptionNames, taxRate.name)); + + if (!_.isEmpty(selectedOptions)) { + const selectedTaxRatesOptions = _.map(selectedOptions, (option) => { + const taxRateObject = _.find(policyTaxRates.taxes, (taxRate) => taxRate.name === option.name); + return { + name: option.name, + enabled: Boolean(taxRateObject && !taxRateObject.isDisabled), + }; + }); + + policyRatesSections.push({ + // "Selected" section + title: '', + shouldShow: true, + indexOffset, + data: getTaxRatesOptions(selectedTaxRatesOptions), + }); + + indexOffset += selectedOptions.length; + } + policyRatesSections.push({ // "All" section when items amount more than the threshold - title: Localize.translateLocal('common.all'), + title: '', shouldShow: true, indexOffset, - data: getTaxRatesOptions(sortedTaxRates), + data: getTaxRatesOptions(filteredTaxRates), }); return policyRatesSections; @@ -1829,6 +1898,7 @@ export { shouldOptionShowTooltip, getLastMessageTextForReport, getEnabledCategoriesCount, + getEnabledTaxRateCount, hasEnabledOptions, sortCategories, getCategoryOptionTree, From 2abfc7c3a1b1b75f104535c6a61d262e041cfc3d Mon Sep 17 00:00:00 2001 From: Etotaziba Olei Date: Wed, 13 Dec 2023 06:30:13 +0100 Subject: [PATCH 026/209] update tax Amount based on tax rate --- .../iou/steps/IOURequestStepTaxRatePage.js | 37 +++++++++++++------ 1 file changed, 26 insertions(+), 11 deletions(-) diff --git a/src/pages/iou/steps/IOURequestStepTaxRatePage.js b/src/pages/iou/steps/IOURequestStepTaxRatePage.js index 4d0e6bda659d..0c9d16aed540 100644 --- a/src/pages/iou/steps/IOURequestStepTaxRatePage.js +++ b/src/pages/iou/steps/IOURequestStepTaxRatePage.js @@ -2,11 +2,13 @@ import lodashGet from 'lodash/get'; import PropTypes from 'prop-types'; import React from 'react'; import {withOnyx} from 'react-native-onyx'; +import _ from 'underscore'; import HeaderWithBackButton from '@components/HeaderWithBackButton'; import ScreenWrapper from '@components/ScreenWrapper'; import TaxPicker from '@components/TaxPicker'; import taxPropTypes from '@components/taxPropTypes'; import compose from '@libs/compose'; +import * as CurrencyUtils from '@libs/CurrencyUtils'; import Navigation from '@libs/Navigation/Navigation'; import {iouDefaultProps, iouPropTypes} from '@pages/iou/propTypes'; import * as IOU from '@userActions/IOU'; @@ -43,6 +45,13 @@ const defaultProps = { }, }; +// this is the formulae to calculate tax +const calculateAmount = (taxRates, selectedTaxRate, amount) => { + const percentage = _.find(taxRates, (taxRate) => taxRate.name === selectedTaxRate).value; + const divisor = percentage.slice(0, -1) / 100 + 1; // slice to remove % at the end; converts "10%" to "10" + return parseInt(Math.round(amount - amount / divisor), 10) / 100; // returns The expense amount of transaction +}; + function IOURequestStepTaxRatePage({route, iou, policyTaxRates, transactionsDraft}) { const iouType = lodashGet(route, 'params.iouType', ''); const reportID = lodashGet(route, 'params.reportID', ''); @@ -52,7 +61,10 @@ function IOURequestStepTaxRatePage({route, iou, policyTaxRates, transactionsDraf } const updateTaxRates = (taxes) => { + calculateAmount(policyTaxRates.taxes, taxes.text, iou.amount); + const amountInSmallestCurrencyUnits = CurrencyUtils.convertToBackendAmount(Number.parseFloat(calculateAmount)); IOU.setMoneyRequestTaxRate(iou.transactionID, taxes.text); + IOU.setMoneyRequestTaxAmount(iou.transactionID, amountInSmallestCurrencyUnits); Navigation.goBack(ROUTES.MONEY_REQUEST_CONFIRMATION.getRoute(iouType, reportID)); }; @@ -63,17 +75,20 @@ function IOURequestStepTaxRatePage({route, iou, policyTaxRates, transactionsDraf shouldEnableMaxHeight testID={IOURequestStepTaxRatePage.displayName} > - <> - navigateBack()} - /> - - + {({insets}) => ( + <> + navigateBack()} + /> + + + )} ); } From ce50e7cd2f4d2339f14a32104e87f1b4346582ad Mon Sep 17 00:00:00 2001 From: Etotaziba Olei Date: Wed, 13 Dec 2023 06:31:55 +0100 Subject: [PATCH 027/209] should show search input if threshold is more than 8 --- src/components/TaxPicker/index.js | 11 ++++++++++- 1 file changed, 10 insertions(+), 1 deletion(-) diff --git a/src/components/TaxPicker/index.js b/src/components/TaxPicker/index.js index befe5530d891..92858ca37928 100644 --- a/src/components/TaxPicker/index.js +++ b/src/components/TaxPicker/index.js @@ -4,14 +4,21 @@ import _ from 'underscore'; import OptionsSelector from '@components/OptionsSelector'; import useLocalize from '@hooks/useLocalize'; import * as OptionsListUtils from '@libs/OptionsListUtils'; +import * as StyleUtils from '@styles/StyleUtils'; import useThemeStyles from '@styles/useThemeStyles'; +import CONST from '@src/CONST'; import {defaultProps, propTypes} from './taxPickerPropTypes'; -function TaxPicker({selectedTaxRate, policyTaxRates, onSubmit}) { +function TaxPicker({selectedTaxRate, policyTaxRates, insets, onSubmit}) { const styles = useThemeStyles(); const {translate} = useLocalize(); const [searchValue, setSearchValue] = useState(''); + const policyTaxRatesCount = OptionsListUtils.getEnabledTaxRateCount(policyTaxRates.taxes); + const isTaxRatesCountBelowThreshold = policyTaxRatesCount < CONST.TAX_RATES_LIST_THRESHOLD; + + const shouldShowTextInput = !isTaxRatesCountBelowThreshold; + const selectedOptions = useMemo(() => { if (!selectedTaxRate) { return []; @@ -54,6 +61,7 @@ function TaxPicker({selectedTaxRate, policyTaxRates, onSubmit}) { return ( From 9e12ece66efadad9de6323b9d764b9d198630bfe Mon Sep 17 00:00:00 2001 From: Etotaziba Olei Date: Wed, 13 Dec 2023 06:32:56 +0100 Subject: [PATCH 028/209] should show error if current amount is greater than iou amount --- src/pages/iou/steps/MoneyRequestAmountForm.js | 21 ++++++++++++++++--- 1 file changed, 18 insertions(+), 3 deletions(-) diff --git a/src/pages/iou/steps/MoneyRequestAmountForm.js b/src/pages/iou/steps/MoneyRequestAmountForm.js index 2150af0d1040..59726809caaa 100644 --- a/src/pages/iou/steps/MoneyRequestAmountForm.js +++ b/src/pages/iou/steps/MoneyRequestAmountForm.js @@ -2,6 +2,7 @@ import lodashGet from 'lodash/get'; import PropTypes from 'prop-types'; import React, {useCallback, useEffect, useRef, useState} from 'react'; import {ScrollView, View} from 'react-native'; +import {withOnyx} from 'react-native-onyx'; import _ from 'underscore'; import BigNumberPad from '@components/BigNumberPad'; import Button from '@components/Button'; @@ -15,8 +16,10 @@ import * as CurrencyUtils from '@libs/CurrencyUtils'; import * as DeviceCapabilities from '@libs/DeviceCapabilities'; import getOperatingSystem from '@libs/getOperatingSystem'; import * as MoneyRequestUtils from '@libs/MoneyRequestUtils'; +import {iouDefaultProps, iouPropTypes} from '@pages/iou/propTypes'; import useThemeStyles from '@styles/useThemeStyles'; import CONST from '@src/CONST'; +import ONYXKEYS from '@src/ONYXKEYS'; const propTypes = { /** IOU amount saved in Onyx */ @@ -39,9 +42,13 @@ const propTypes = { /** The current tab we have navigated to in the request modal. String that corresponds to the request type. */ selectedTab: PropTypes.oneOf([CONST.TAB.DISTANCE, CONST.TAB.MANUAL, CONST.TAB.SCAN]), + + /** Holds data related to Money Request view state, rather than the underlying Money Request data. */ + iou: iouPropTypes, }; const defaultProps = { + iou: iouDefaultProps, amount: 0, currency: CONST.CURRENCY.USD, forwardedRef: null, @@ -63,12 +70,13 @@ const getNewSelection = (oldSelection, prevLength, newLength) => { }; const isAmountInvalid = (amount) => !amount.length || parseFloat(amount) < 0.01; +const isTaxAmountInvalid = (currentAmount, amount) => amount > 0 && currentAmount > CurrencyUtils.convertToFrontendAmount(amount); const AMOUNT_VIEW_ID = 'amountView'; const NUM_PAD_CONTAINER_VIEW_ID = 'numPadContainerView'; const NUM_PAD_VIEW_ID = 'numPadView'; -function MoneyRequestAmountForm({amount, currency, isEditing, forwardedRef, onCurrencyButtonPress, onSubmitButtonPress, selectedTab}) { +function MoneyRequestAmountForm({iou, amount, currency, isEditing, forwardedRef, onCurrencyButtonPress, onSubmitButtonPress, selectedTab}) { const styles = useThemeStyles(); const {isExtraSmallScreenHeight} = useWindowDimensions(); const {translate, toLocaleDigit, numberFormat} = useLocalize(); @@ -223,13 +231,18 @@ function MoneyRequestAmountForm({amount, currency, isEditing, forwardedRef, onCu return; } + if (isTaxAmountInvalid(currentAmount, iou.amount)) { + setFormError('iou.error.invalidAmount'); + return; + } + // Update display amount string post-edit to ensure consistency with backend amount // Reference: https://github.com/Expensify/App/issues/30505 const backendAmount = CurrencyUtils.convertToBackendAmount(Number.parseFloat(currentAmount)); initializeAmount(backendAmount); onSubmitButtonPress(currentAmount); - }, [onSubmitButtonPress, currentAmount, initializeAmount]); + }, [onSubmitButtonPress, currentAmount, iou.amount, initializeAmount]); /** * Input handler to check for a forward-delete key (or keyboard shortcut) press. @@ -335,4 +348,6 @@ const MoneyRequestAmountFormWithRef = React.forwardRef((props, ref) => ( MoneyRequestAmountFormWithRef.displayName = 'MoneyRequestAmountFormWithRef'; -export default MoneyRequestAmountFormWithRef; +export default withOnyx({ + iou: {key: ONYXKEYS.IOU}, +})(MoneyRequestAmountFormWithRef); From e5f6e17621e2d2f1c25e857b907c892b18b37190 Mon Sep 17 00:00:00 2001 From: Etotaziba Olei Date: Wed, 13 Dec 2023 06:36:45 +0100 Subject: [PATCH 029/209] update comment --- src/libs/OptionsListUtils.js | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/libs/OptionsListUtils.js b/src/libs/OptionsListUtils.js index 22d053347b27..e8cace7a625a 100644 --- a/src/libs/OptionsListUtils.js +++ b/src/libs/OptionsListUtils.js @@ -1086,7 +1086,7 @@ function getTaxRatesSection(policyTaxRates, selectedOptions, searchInputValue) { let indexOffset = 0; - // If all tax rates are disabled but there's a previously selected tag, show only the selected tag + // If all tax are disabled but there's a previously selected tag, show only the selected tag if (numberOfTaxRates === 0 && selectedOptions.length > 0) { const selectedTaxRateOptions = _.map(selectedOptions, (option) => ({ name: option.name, From d9fa49d85ae49de277cdeef580a31e2a69fb9e45 Mon Sep 17 00:00:00 2001 From: Etotaziba Olei Date: Wed, 13 Dec 2023 06:40:38 +0100 Subject: [PATCH 030/209] set taxAmount in Onyx --- src/pages/iou/steps/IOURequestStepTaxRatePage.js | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/src/pages/iou/steps/IOURequestStepTaxRatePage.js b/src/pages/iou/steps/IOURequestStepTaxRatePage.js index 0c9d16aed540..5caeb37438b5 100644 --- a/src/pages/iou/steps/IOURequestStepTaxRatePage.js +++ b/src/pages/iou/steps/IOURequestStepTaxRatePage.js @@ -61,8 +61,8 @@ function IOURequestStepTaxRatePage({route, iou, policyTaxRates, transactionsDraf } const updateTaxRates = (taxes) => { - calculateAmount(policyTaxRates.taxes, taxes.text, iou.amount); - const amountInSmallestCurrencyUnits = CurrencyUtils.convertToBackendAmount(Number.parseFloat(calculateAmount)); + const taxAmount = calculateAmount(policyTaxRates.taxes, taxes.text, iou.amount); + const amountInSmallestCurrencyUnits = CurrencyUtils.convertToBackendAmount(Number.parseFloat(taxAmount)); IOU.setMoneyRequestTaxRate(iou.transactionID, taxes.text); IOU.setMoneyRequestTaxAmount(iou.transactionID, amountInSmallestCurrencyUnits); From 98db78be8d3146213478f9bad0eb5bbe649f5419 Mon Sep 17 00:00:00 2001 From: Etotaziba Olei Date: Wed, 13 Dec 2023 10:23:06 +0100 Subject: [PATCH 031/209] add tax translation strings --- src/languages/en.ts | 3 +++ src/languages/es.ts | 3 +++ 2 files changed, 6 insertions(+) diff --git a/src/languages/en.ts b/src/languages/en.ts index 8f772f1260bb..adec0bafad40 100755 --- a/src/languages/en.ts +++ b/src/languages/en.ts @@ -528,6 +528,8 @@ export default { }, iou: { amount: 'Amount', + taxAmount: 'Tax amount', + taxRate: 'Tax rate', approve: 'Approve', approved: 'Approved', cash: 'Cash', @@ -596,6 +598,7 @@ export default { categorySelection: 'Select a category to add additional organization to your money.', error: { invalidAmount: 'Please enter a valid amount before continuing.', + invalidTaxAmount: ({amount}: RequestAmountParams) => `Maximum tax amount is ${amount}`, invalidSplit: 'Split amounts do not equal total amount', other: 'Unexpected error, please try again later', genericCreateFailureMessage: 'Unexpected error requesting money, please try again later', diff --git a/src/languages/es.ts b/src/languages/es.ts index 3887891299df..1b3a68621fb1 100644 --- a/src/languages/es.ts +++ b/src/languages/es.ts @@ -520,6 +520,8 @@ export default { }, iou: { amount: 'Importe', + taxAmount: 'Tax amount', + taxRate: 'Tax rate', approve: 'Aprobar', approved: 'Aprobado', cash: 'Efectivo', @@ -590,6 +592,7 @@ export default { categorySelection: 'Seleccione una categoría para organizar mejor tu dinero.', error: { invalidAmount: 'Por favor ingresa un monto válido antes de continuar.', + invalidTaxAmount: ({amount}: RequestAmountParams) => `El monto máximo del impuesto es ${amount}`, invalidSplit: 'La suma de las partes no equivale al monto total', other: 'Error inesperado, por favor inténtalo más tarde', genericCreateFailureMessage: 'Error inesperado solicitando dinero, Por favor, inténtalo más tarde', From e28ae1b81cb82a7b43e0f2cddfb0b1f17e3e9eb9 Mon Sep 17 00:00:00 2001 From: Etotaziba Olei Date: Wed, 13 Dec 2023 10:29:24 +0100 Subject: [PATCH 032/209] format tax amount and use translation for invalid tax amount --- src/pages/iou/steps/MoneyRequestAmountForm.js | 14 ++++++++------ 1 file changed, 8 insertions(+), 6 deletions(-) diff --git a/src/pages/iou/steps/MoneyRequestAmountForm.js b/src/pages/iou/steps/MoneyRequestAmountForm.js index 59726809caaa..970d559f10d8 100644 --- a/src/pages/iou/steps/MoneyRequestAmountForm.js +++ b/src/pages/iou/steps/MoneyRequestAmountForm.js @@ -70,7 +70,7 @@ const getNewSelection = (oldSelection, prevLength, newLength) => { }; const isAmountInvalid = (amount) => !amount.length || parseFloat(amount) < 0.01; -const isTaxAmountInvalid = (currentAmount, amount) => amount > 0 && currentAmount > CurrencyUtils.convertToFrontendAmount(amount); +const isTaxAmountInvalid = (currentAmount, amount, isEditing) => isEditing && currentAmount > CurrencyUtils.convertToFrontendAmount(amount); const AMOUNT_VIEW_ID = 'amountView'; const NUM_PAD_CONTAINER_VIEW_ID = 'numPadContainerView'; @@ -97,6 +97,8 @@ function MoneyRequestAmountForm({iou, amount, currency, isEditing, forwardedRef, const forwardDeletePressedRef = useRef(false); + const formattedTaxAmount = CurrencyUtils.convertToDisplayString(iou.amount, iou.currency); + /** * Event occurs when a user presses a mouse button over an DOM element. * @@ -227,12 +229,12 @@ function MoneyRequestAmountForm({iou, amount, currency, isEditing, forwardedRef, */ const submitAndNavigateToNextPage = useCallback(() => { if (isAmountInvalid(currentAmount)) { - setFormError('iou.error.invalidAmount'); + setFormError(translate('iou.error.invalidAmount')); return; } - if (isTaxAmountInvalid(currentAmount, iou.amount)) { - setFormError('iou.error.invalidAmount'); + if (isTaxAmountInvalid(currentAmount, iou.amount, isEditing)) { + setFormError(translate('iou.error.invalidTaxAmount', {amount: formattedTaxAmount})); return; } @@ -242,7 +244,7 @@ function MoneyRequestAmountForm({iou, amount, currency, isEditing, forwardedRef, initializeAmount(backendAmount); onSubmitButtonPress(currentAmount); - }, [onSubmitButtonPress, currentAmount, iou.amount, initializeAmount]); + }, [onSubmitButtonPress, currentAmount, iou.amount, isEditing, formattedTaxAmount, translate, initializeAmount]); /** * Input handler to check for a forward-delete key (or keyboard shortcut) press. @@ -303,7 +305,7 @@ function MoneyRequestAmountForm({iou, amount, currency, isEditing, forwardedRef, )} From 781952a9fabcc4a9384c9e986f4527a6af62f89c Mon Sep 17 00:00:00 2001 From: Etotaziba Olei Date: Wed, 13 Dec 2023 10:30:15 +0100 Subject: [PATCH 033/209] translate titles --- src/pages/iou/steps/IOURequestStepTaxAmountPage.js | 4 +++- src/pages/iou/steps/IOURequestStepTaxRatePage.js | 4 +++- 2 files changed, 6 insertions(+), 2 deletions(-) diff --git a/src/pages/iou/steps/IOURequestStepTaxAmountPage.js b/src/pages/iou/steps/IOURequestStepTaxAmountPage.js index 43d776dc3670..6f8e8e9583b7 100644 --- a/src/pages/iou/steps/IOURequestStepTaxAmountPage.js +++ b/src/pages/iou/steps/IOURequestStepTaxAmountPage.js @@ -7,6 +7,7 @@ import {withOnyx} from 'react-native-onyx'; import FullPageNotFoundView from '@components/BlockingViews/FullPageNotFoundView'; import HeaderWithBackButton from '@components/HeaderWithBackButton'; import ScreenWrapper from '@components/ScreenWrapper'; +import useLocalize from '@hooks/useLocalize'; import compose from '@libs/compose'; import * as CurrencyUtils from '@libs/CurrencyUtils'; import * as IOUUtils from '@libs/IOUUtils'; @@ -51,6 +52,7 @@ const defaultProps = { }; function IOURequestStepTaxAmountPage({route, iou, transactionsDraft}) { + const {translate} = useLocalize(); const styles = useThemeStyles(); const textInput = useRef(null); const isEditing = Navigation.getActiveRoute().includes('taxAmount'); @@ -113,7 +115,7 @@ function IOURequestStepTaxAmountPage({route, iou, transactionsDraft}) { {content} diff --git a/src/pages/iou/steps/IOURequestStepTaxRatePage.js b/src/pages/iou/steps/IOURequestStepTaxRatePage.js index 5caeb37438b5..1cf12cf56880 100644 --- a/src/pages/iou/steps/IOURequestStepTaxRatePage.js +++ b/src/pages/iou/steps/IOURequestStepTaxRatePage.js @@ -7,6 +7,7 @@ import HeaderWithBackButton from '@components/HeaderWithBackButton'; import ScreenWrapper from '@components/ScreenWrapper'; import TaxPicker from '@components/TaxPicker'; import taxPropTypes from '@components/taxPropTypes'; +import useLocalize from '@hooks/useLocalize'; import compose from '@libs/compose'; import * as CurrencyUtils from '@libs/CurrencyUtils'; import Navigation from '@libs/Navigation/Navigation'; @@ -53,6 +54,7 @@ const calculateAmount = (taxRates, selectedTaxRate, amount) => { }; function IOURequestStepTaxRatePage({route, iou, policyTaxRates, transactionsDraft}) { + const {translate} = useLocalize(); const iouType = lodashGet(route, 'params.iouType', ''); const reportID = lodashGet(route, 'params.reportID', ''); @@ -78,7 +80,7 @@ function IOURequestStepTaxRatePage({route, iou, policyTaxRates, transactionsDraf {({insets}) => ( <> navigateBack()} /> Date: Wed, 13 Dec 2023 11:42:48 +0100 Subject: [PATCH 034/209] update merge conflict --- src/SCREENS.ts | 2 ++ src/components/TaxPicker/index.js | 3 ++- src/libs/Navigation/AppNavigator/ModalStackNavigators.tsx | 2 ++ src/libs/Navigation/linkingConfig.ts | 2 +- src/pages/iou/steps/MoneyRequestAmountForm.js | 4 ++-- 5 files changed, 9 insertions(+), 4 deletions(-) diff --git a/src/SCREENS.ts b/src/SCREENS.ts index 921f57953482..2c5b4a8d0046 100644 --- a/src/SCREENS.ts +++ b/src/SCREENS.ts @@ -129,6 +129,8 @@ const SCREENS = { STEP_WAYPOINT: 'Money_Request_Step_Waypoint', ROOT: 'Money_Request', AMOUNT: 'Money_Request_Amount', + TAX_AMOUNT: 'Money_Request_Tax_Amount', + TAX_RATE: 'Money_Request_Tax_Rate', PARTICIPANTS: 'Money_Request_Participants', CONFIRMATION: 'Money_Request_Confirmation', CURRENCY: 'Money_Request_Currency', diff --git a/src/components/TaxPicker/index.js b/src/components/TaxPicker/index.js index 92858ca37928..743010bddf4d 100644 --- a/src/components/TaxPicker/index.js +++ b/src/components/TaxPicker/index.js @@ -4,13 +4,14 @@ import _ from 'underscore'; import OptionsSelector from '@components/OptionsSelector'; import useLocalize from '@hooks/useLocalize'; import * as OptionsListUtils from '@libs/OptionsListUtils'; -import * as StyleUtils from '@styles/StyleUtils'; +import useStyleUtils from '@styles/useStyleUtils'; import useThemeStyles from '@styles/useThemeStyles'; import CONST from '@src/CONST'; import {defaultProps, propTypes} from './taxPickerPropTypes'; function TaxPicker({selectedTaxRate, policyTaxRates, insets, onSubmit}) { const styles = useThemeStyles(); + const StyleUtils = useStyleUtils(); const {translate} = useLocalize(); const [searchValue, setSearchValue] = useState(''); diff --git a/src/libs/Navigation/AppNavigator/ModalStackNavigators.tsx b/src/libs/Navigation/AppNavigator/ModalStackNavigators.tsx index 29449f52ecd6..5ac9c0853b7c 100644 --- a/src/libs/Navigation/AppNavigator/ModalStackNavigators.tsx +++ b/src/libs/Navigation/AppNavigator/ModalStackNavigators.tsx @@ -88,6 +88,8 @@ const MoneyRequestModalStackNavigator = createModalStackNavigator require('../../../pages/iou/request/step/IOURequestStepWaypoint').default as React.ComponentType, [SCREENS.MONEY_REQUEST.ROOT]: () => require('../../../pages/iou/MoneyRequestSelectorPage').default as React.ComponentType, [SCREENS.MONEY_REQUEST.AMOUNT]: () => require('../../../pages/iou/steps/NewRequestAmountPage').default as React.ComponentType, + [SCREENS.MONEY_REQUEST.TAX_AMOUNT]: () => require('../../../pages/iou/steps/IOURequestStepTaxAmountPage').default as React.ComponentType, + [SCREENS.MONEY_REQUEST.TAX_RATE]: () => require('../../../pages/iou/steps/IOURequestStepTaxRatePage').default as React.ComponentType, [SCREENS.MONEY_REQUEST.PARTICIPANTS]: () => require('../../../pages/iou/steps/MoneyRequstParticipantsPage/MoneyRequestParticipantsPage').default as React.ComponentType, [SCREENS.MONEY_REQUEST.CONFIRMATION]: () => require('../../../pages/iou/steps/MoneyRequestConfirmPage').default as React.ComponentType, [SCREENS.MONEY_REQUEST.CURRENCY]: () => require('../../../pages/iou/IOUCurrencySelection').default as React.ComponentType, diff --git a/src/libs/Navigation/linkingConfig.ts b/src/libs/Navigation/linkingConfig.ts index 6de0afe58b52..1879e2cb9c8e 100644 --- a/src/libs/Navigation/linkingConfig.ts +++ b/src/libs/Navigation/linkingConfig.ts @@ -418,7 +418,7 @@ const linkingConfig: LinkingOptions = { }, [SCREENS.MONEY_REQUEST.AMOUNT]: ROUTES.MONEY_REQUEST_AMOUNT.route, [SCREENS.MONEY_REQUEST.TAX_AMOUNT]: ROUTES.MONEY_REQUEST_TAX_AMOUNT.route, - [SCREENS.MONEY_REQUEST.TAX_RATE]: ROUTES.MONEY_REQUEST_TAX_RATE.route, + [SCREENS.MONEY_REQUEST.TAX_RATE]: ROUTES.MONEY_REQUEST_TAX_RATE.route, [SCREENS.MONEY_REQUEST.PARTICIPANTS]: ROUTES.MONEY_REQUEST_PARTICIPANTS.route, [SCREENS.MONEY_REQUEST.CONFIRMATION]: ROUTES.MONEY_REQUEST_CONFIRMATION.route, [SCREENS.MONEY_REQUEST.DATE]: ROUTES.MONEY_REQUEST_DATE.route, diff --git a/src/pages/iou/steps/MoneyRequestAmountForm.js b/src/pages/iou/steps/MoneyRequestAmountForm.js index 9345c25c0294..d5b6ae6442b3 100644 --- a/src/pages/iou/steps/MoneyRequestAmountForm.js +++ b/src/pages/iou/steps/MoneyRequestAmountForm.js @@ -43,8 +43,8 @@ const propTypes = { /** The current tab we have navigated to in the request modal. String that corresponds to the request type. */ selectedTab: PropTypes.oneOf([CONST.TAB_REQUEST.DISTANCE, CONST.TAB_REQUEST.MANUAL, CONST.TAB_REQUEST.SCAN]), - /** Holds data related to Money Request view state, rather than the underlying Money Request data. */ - iou: iouPropTypes, + /** Holds data related to Money Request view state, rather than the underlying Money Request data. */ + iou: iouPropTypes, }; const defaultProps = { From 62e98c1ad82c91593a5e20e8ddc847e49318d3c5 Mon Sep 17 00:00:00 2001 From: Etotaziba Olei Date: Wed, 13 Dec 2023 13:14:04 +0100 Subject: [PATCH 035/209] translate tax amount and rate --- src/components/MoneyRequestConfirmationList.js | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/src/components/MoneyRequestConfirmationList.js b/src/components/MoneyRequestConfirmationList.js index fc0da151526b..c302c9f9b6d8 100755 --- a/src/components/MoneyRequestConfirmationList.js +++ b/src/components/MoneyRequestConfirmationList.js @@ -757,7 +757,7 @@ function MoneyRequestConfirmationList(props) { Navigation.navigate(ROUTES.MONEY_REQUEST_TAX_RATE.getRoute(props.iouType, props.reportID))} @@ -772,7 +772,7 @@ function MoneyRequestConfirmationList(props) { Navigation.navigate(ROUTES.MONEY_REQUEST_TAX_AMOUNT.getRoute(props.iouType, props.reportID))} From 76cb656e4a593998e9712940e43ff9f462c8836b Mon Sep 17 00:00:00 2001 From: DylanDylann Date: Thu, 14 Dec 2023 00:48:36 +0700 Subject: [PATCH 036/209] fix prevent user from replacing receipt when scanning --- src/components/AttachmentModal.js | 2 +- src/libs/ReportUtils.ts | 5 ++++- src/pages/EditRequestPage.js | 4 ++-- 3 files changed, 7 insertions(+), 4 deletions(-) diff --git a/src/components/AttachmentModal.js b/src/components/AttachmentModal.js index 79be536945ac..38c4ceef13a4 100755 --- a/src/components/AttachmentModal.js +++ b/src/components/AttachmentModal.js @@ -366,7 +366,7 @@ function AttachmentModal(props) { const parentReportAction = props.parentReportActions[props.report.parentReportActionID]; const canEdit = - ReportUtils.canEditFieldOfMoneyRequest(parentReportAction, props.parentReport.reportID, CONST.EDIT_REQUEST_FIELD.RECEIPT) && + ReportUtils.canEditFieldOfMoneyRequest(parentReportAction, props.parentReport.reportID, CONST.EDIT_REQUEST_FIELD.RECEIPT, props.transaction) && !TransactionUtils.isDistanceRequest(props.transaction); if (canEdit) { menuItems.push({ diff --git a/src/libs/ReportUtils.ts b/src/libs/ReportUtils.ts index 63037416c923..20d663bce8ea 100644 --- a/src/libs/ReportUtils.ts +++ b/src/libs/ReportUtils.ts @@ -1839,7 +1839,7 @@ function canEditMoneyRequest(reportAction: OnyxEntry, fieldToEdit * Checks if the current user can edit the provided property of a money request * */ -function canEditFieldOfMoneyRequest(reportAction: OnyxEntry, reportID: string, fieldToEdit: ValueOf): boolean { +function canEditFieldOfMoneyRequest(reportAction: OnyxEntry, reportID: string, fieldToEdit: ValueOf, transaction: Transaction): boolean { // A list of fields that cannot be edited by anyone, once a money request has been settled const nonEditableFieldsWhenSettled: string[] = [ CONST.EDIT_REQUEST_FIELD.AMOUNT, @@ -1853,6 +1853,9 @@ function canEditFieldOfMoneyRequest(reportAction: OnyxEntry, repor if (!canEditMoneyRequest(reportAction, fieldToEdit)) { return false; // User doesn't have permission to edit } + if (fieldToEdit === CONST.EDIT_REQUEST_FIELD.RECEIPT && TransactionUtils.hasReceipt(transaction) && TransactionUtils.isReceiptBeingScanned(transaction)) { + return false; + } // Checks if the report is settled // Checks if the provided property is a restricted one diff --git a/src/pages/EditRequestPage.js b/src/pages/EditRequestPage.js index 95313bea142d..b10d312303c3 100644 --- a/src/pages/EditRequestPage.js +++ b/src/pages/EditRequestPage.js @@ -107,7 +107,7 @@ function EditRequestPage({report, route, parentReport, policyCategories, policyT // Decides whether to allow or disallow editing a money request useEffect(() => { // Do not dismiss the modal, when a current user can edit this property of the money request. - if (ReportUtils.canEditFieldOfMoneyRequest(parentReportAction, parentReport.reportID, fieldToEdit)) { + if (ReportUtils.canEditFieldOfMoneyRequest(parentReportAction, parentReport.reportID, fieldToEdit, transaction)) { return; } @@ -115,7 +115,7 @@ function EditRequestPage({report, route, parentReport, policyCategories, policyT Navigation.isNavigationReady().then(() => { Navigation.dismissModal(); }); - }, [parentReportAction, parentReport.reportID, fieldToEdit]); + }, [parentReportAction, parentReport.reportID, fieldToEdit, transaction]); // Update the transaction object and close the modal function editMoneyRequest(transactionChanges) { From 8b3da643d7369ea100657ad66e59a4fe74fd8a81 Mon Sep 17 00:00:00 2001 From: someone-here Date: Thu, 14 Dec 2023 00:12:13 +0530 Subject: [PATCH 037/209] Workspace currency optimistic actions update --- .../ReportActionItem/ReportPreview.js | 165 +++++++++--------- src/libs/ReportUtils.ts | 4 +- src/libs/actions/IOU.js | 37 ++-- .../step/IOURequestStepConfirmation.js | 3 +- .../iou/steps/MoneyRequestConfirmPage.js | 2 + 5 files changed, 109 insertions(+), 102 deletions(-) diff --git a/src/components/ReportActionItem/ReportPreview.js b/src/components/ReportActionItem/ReportPreview.js index e6b387f362ef..8fcd23f445b4 100644 --- a/src/components/ReportActionItem/ReportPreview.js +++ b/src/components/ReportActionItem/ReportPreview.js @@ -7,6 +7,7 @@ import _ from 'underscore'; import Button from '@components/Button'; import Icon from '@components/Icon'; import * as Expensicons from '@components/Icon/Expensicons'; +import OfflineWithFeedback from '@components/OfflineWithFeedback'; import PressableWithoutFeedback from '@components/Pressable/PressableWithoutFeedback'; import refPropTypes from '@components/refPropTypes'; import SettlementButton from '@components/SettlementButton'; @@ -219,94 +220,96 @@ function ReportPreview(props) { }, [isGroupPolicy, isCurrentUserManager, isDraftExpenseReport, isApproved, iouSettled]); const shouldShowSettlementButton = shouldShowPayButton || shouldShowApproveButton; return ( - - { - Navigation.navigate(ROUTES.REPORT_WITH_ID.getRoute(props.iouReportID)); - }} - onPressIn={() => DeviceCapabilities.canUseTouchScreen() && ControlSelection.block()} - onPressOut={() => ControlSelection.unblock()} - onLongPress={(event) => showContextMenuForReport(event, props.contextMenuAnchor, props.chatReportID, props.action, props.checkIfContextMenuActive)} - style={[styles.flexRow, styles.justifyContentBetween, styles.reportPreviewBox]} - role="button" - accessibilityLabel={props.translate('iou.viewDetails')} - > - - {hasReceipts && ( - - )} - - - - {getPreviewMessage()} - - {!iouSettled && hasErrors && ( - - )} - - - - {getDisplayAmount()} - {ReportUtils.isSettled(props.iouReportID) && ( - - - + + + { + Navigation.navigate(ROUTES.REPORT_WITH_ID.getRoute(props.iouReportID)); + }} + onPressIn={() => DeviceCapabilities.canUseTouchScreen() && ControlSelection.block()} + onPressOut={() => ControlSelection.unblock()} + onLongPress={(event) => showContextMenuForReport(event, props.contextMenuAnchor, props.chatReportID, props.action, props.checkIfContextMenuActive)} + style={[styles.flexRow, styles.justifyContentBetween, styles.reportPreviewBox]} + role="button" + accessibilityLabel={props.translate('iou.viewDetails')} + > + + {hasReceipts && ( + + )} + + + + {getPreviewMessage()} + + {!iouSettled && hasErrors && ( + )} - - {!isScanning && (numberOfRequests > 1 || hasReceipts) && ( - {previewSubtitle || moneyRequestComment} + {getDisplayAmount()} + {ReportUtils.isSettled(props.iouReportID) && ( + + + + )} - )} - {shouldShowSettlementButton && ( - IOU.payMoneyRequest(paymentType, props.chatReport, props.iouReport)} - enablePaymentsRoute={ROUTES.ENABLE_PAYMENTS} - addBankAccountRoute={bankAccountRoute} - shouldHidePaymentOptions={!shouldShowPayButton} - shouldShowApproveButton={shouldShowApproveButton} - style={[styles.mt3]} - kycWallAnchorAlignment={{ - horizontal: CONST.MODAL.ANCHOR_ORIGIN_HORIZONTAL.LEFT, - vertical: CONST.MODAL.ANCHOR_ORIGIN_VERTICAL.BOTTOM, - }} - paymentMethodDropdownAnchorAlignment={{ - horizontal: CONST.MODAL.ANCHOR_ORIGIN_HORIZONTAL.RIGHT, - vertical: CONST.MODAL.ANCHOR_ORIGIN_VERTICAL.BOTTOM, - }} - /> - )} - {shouldShowSubmitButton && ( -