diff --git a/src/CONST.ts b/src/CONST.ts index ebba780fd745..6f1fe37f661d 100755 --- a/src/CONST.ts +++ b/src/CONST.ts @@ -1135,6 +1135,8 @@ const CONST = { }, IOU: { + // This is the transactionID used when going through the create money request flow so that it mimics a real transaction (like the edit flow) + OPTIMISTIC_TRANSACTION_ID: '1', // Note: These payment types are used when building IOU reportAction message values in the server and should // not be changed. PAYMENT_TYPE: { @@ -1147,6 +1149,11 @@ const CONST = { SPLIT: 'split', REQUEST: 'request', }, + REQUEST_TYPE: { + DISTANCE: 'distance', + MANUAL: 'manual', + SCAN: 'scan', + }, REPORT_ACTION_TYPE: { PAY: 'pay', CREATE: 'create', @@ -2755,6 +2762,9 @@ const CONST = { NEW_CHAT: 'chat', NEW_ROOM: 'room', RECEIPT_TAB_ID: 'ReceiptTab', + IOU_REQUEST_TYPE: 'iouRequestType', + }, + TAB_REQUEST: { MANUAL: 'manual', SCAN: 'scan', DISTANCE: 'distance', diff --git a/src/ROUTES.ts b/src/ROUTES.ts index a3aa28c44609..53763d6d7cd1 100644 --- a/src/ROUTES.ts +++ b/src/ROUTES.ts @@ -13,7 +13,8 @@ function getUrlWithBackToParam(url: TUrl, backTo?: string): const ROUTES = { HOME: '', - /** This is a utility route used to go to the user's concierge chat, or the sign-in page if the user's not authenticated */ + + // This is a utility route used to go to the user's concierge chat, or the sign-in page if the user's not authenticated CONCIERGE: 'concierge', FLAG_COMMENT: { route: 'flag/:reportID/:reportActionID', @@ -306,6 +307,82 @@ const ROUTES = { MONEY_REQUEST_MANUAL_TAB: ':iouType/new/:reportID?/manual', MONEY_REQUEST_SCAN_TAB: ':iouType/new/:reportID?/scan', + MONEY_REQUEST_CREATE: { + route: 'create/:iouType/start/:transactionID/:reportID', + getRoute: (iouType: ValueOf, transactionID: string, reportID: string) => `create/${iouType}/start/${transactionID}/${reportID}` as const, + }, + MONEY_REQUEST_STEP_CONFIRMATION: { + route: 'create/:iouType/confirmation/:transactionID/:reportID/', + getRoute: (iouType: ValueOf, transactionID: string, reportID: string) => `create/${iouType}/confirmation/${transactionID}/${reportID}/` as const, + }, + MONEY_REQUEST_STEP_AMOUNT: { + route: 'create/:iouType/amount/:transactionID/:reportID/', + getRoute: (iouType: ValueOf, transactionID: string, reportID: string, backTo = '') => + getUrlWithBackToParam(`create/${iouType}/amount/${transactionID}/${reportID}/`, backTo), + }, + MONEY_REQUEST_STEP_CATEGORY: { + route: 'create/:iouType/category/:transactionID/:reportID/', + getRoute: (iouType: ValueOf, transactionID: string, reportID: string, backTo = '') => + getUrlWithBackToParam(`create/${iouType}/category/${transactionID}/${reportID}/`, backTo), + }, + MONEY_REQUEST_STEP_CURRENCY: { + route: 'create/:iouType/currency/:transactionID/:reportID/:pageIndex?/', + getRoute: (iouType: ValueOf, transactionID: string, reportID: string, pageIndex = '', backTo = '') => + getUrlWithBackToParam(`create/${iouType}/currency/${transactionID}/${reportID}/${pageIndex}`, backTo), + }, + MONEY_REQUEST_STEP_DATE: { + route: 'create/:iouType/date/:transactionID/:reportID/', + getRoute: (iouType: ValueOf, transactionID: string, reportID: string, backTo = '') => + getUrlWithBackToParam(`create/${iouType}/date/${transactionID}/${reportID}/`, backTo), + }, + MONEY_REQUEST_STEP_DESCRIPTION: { + route: 'create/:iouType/description/:transactionID/:reportID/', + getRoute: (iouType: ValueOf, transactionID: string, reportID: string, backTo = '') => + getUrlWithBackToParam(`create/${iouType}/description/${transactionID}/${reportID}/`, backTo), + }, + MONEY_REQUEST_STEP_DISTANCE: { + route: 'create/:iouType/distance/:transactionID/:reportID/', + getRoute: (iouType: ValueOf, transactionID: string, reportID: string, backTo = '') => + getUrlWithBackToParam(`create/${iouType}/distance/${transactionID}/${reportID}/`, backTo), + }, + MONEY_REQUEST_STEP_MERCHANT: { + route: 'create/:iouType/merchante/:transactionID/:reportID/', + getRoute: (iouType: ValueOf, transactionID: string, reportID: string, backTo = '') => + getUrlWithBackToParam(`create/${iouType}/merchante/${transactionID}/${reportID}/`, backTo), + }, + MONEY_REQUEST_STEP_PARTICIPANTS: { + route: 'create/:iouType/participants/:transactionID/:reportID/', + getRoute: (iouType: ValueOf, transactionID: string, reportID: string, backTo = '') => + getUrlWithBackToParam(`create/${iouType}/participants/${transactionID}/${reportID}/`, backTo), + }, + MONEY_REQUEST_STEP_SCAN: { + route: 'create/:iouType/scan/:transactionID/:reportID/', + getRoute: (iouType: ValueOf, transactionID: string, reportID: string, backTo = '') => + getUrlWithBackToParam(`create/${iouType}/scan/${transactionID}/${reportID}/`, backTo), + }, + MONEY_REQUEST_STEP_TAG: { + route: 'create/:iouType/tag/:transactionID/:reportID/', + getRoute: (iouType: ValueOf, transactionID: string, reportID: string, backTo = '') => + getUrlWithBackToParam(`create/${iouType}/tag/${transactionID}/${reportID}/`, backTo), + }, + MONEY_REQUEST_STEP_WAYPOINT: { + route: 'create/:iouType/waypoint/:transactionID/:reportID/:pageIndex/', + getRoute: (iouType: ValueOf, transactionID: string, reportID: string, pageIndex = '', backTo = '') => + getUrlWithBackToParam(`create/${iouType}/waypoint/${transactionID}/${reportID}/${pageIndex}`, backTo), + }, + MONEY_REQUEST_CREATE_TAB_DISTANCE: { + route: 'create/:iouType/start/:transactionID/:reportID/distance', + getRoute: (iouType: ValueOf, transactionID: string, reportID: string) => `create/${iouType}/start/${transactionID}/${reportID}/distance` as const, + }, + MONEY_REQUEST_CREATE_TAB_MANUAL: { + route: 'create/:iouType/start/:transactionID/:reportID/manual', + getRoute: (iouType: ValueOf, transactionID: string, reportID: string) => `create/${iouType}/start/${transactionID}/${reportID}/manual` as const, + }, + MONEY_REQUEST_CREATE_TAB_SCAN: { + route: 'create/:iouType/start/:transactionID/:reportID/scan', + getRoute: (iouType: ValueOf, transactionID: string, reportID: string) => `create/${iouType}/start/${transactionID}/${reportID}/scan` as const, + }, + IOU_REQUEST: 'request/new', IOU_SEND: 'send/new', IOU_SEND_ADD_BANK_ACCOUNT: 'send/new/add-bank-account', @@ -391,6 +468,7 @@ const ROUTES = { MONEY2020: 'money2020', } as const; +export {getUrlWithBackToParam}; export default ROUTES; // eslint-disable-next-line @typescript-eslint/no-explicit-any diff --git a/src/SCREENS.ts b/src/SCREENS.ts index c0d3df82e228..38bd88a87158 100644 --- a/src/SCREENS.ts +++ b/src/SCREENS.ts @@ -102,6 +102,19 @@ const SCREENS = { SAML_SIGN_IN: 'SAMLSignIn', MONEY_REQUEST: { + CREATE: 'Money_Request_Create', + STEP_CONFIRMATION: 'Money_Request_Step_Confirmation', + STEP_AMOUNT: 'Money_Request_Step_Amount', + STEP_CATEGORY: 'Money_Request_Step_Category', + STEP_CURRENCY: 'Money_Request_Step_Currency', + STEP_DATE: 'Money_Request_Step_Date', + STEP_DESCRIPTION: 'Money_Request_Step_Description', + STEP_DISTANCE: 'Money_Request_Step_Distance', + STEP_MERCHANT: 'Money_Request_Step_Merchant', + STEP_PARTICIPANTS: 'Money_Request_Step_Participants', + STEP_SCAN: 'Money_Request_Step_Scan', + STEP_TAG: 'Money_Request_Step_Tag', + STEP_WAYPOINT: 'Money_Request_Step_Waypoint', ROOT: 'Money_Request', AMOUNT: 'Money_Request_Amount', PARTICIPANTS: 'Money_Request_Participants', diff --git a/src/components/ConfirmedRoute.js b/src/components/ConfirmedRoute.js index 4ddd537fdd7d..79b97b38194a 100644 --- a/src/components/ConfirmedRoute.js +++ b/src/components/ConfirmedRoute.js @@ -115,9 +115,6 @@ function ConfirmedRoute({mapboxAccessToken, transaction}) { } export default withOnyx({ - transaction: { - key: ({transactionID}) => `${ONYXKEYS.COLLECTION.TRANSACTION}${transactionID}`, - }, mapboxAccessToken: { key: ONYXKEYS.MAPBOX_ACCESS_TOKEN, }, diff --git a/src/components/DistanceRequest/DistanceRequestFooter.js b/src/components/DistanceRequest/DistanceRequestFooter.js index b212dae615e4..d374f90a1b6c 100644 --- a/src/components/DistanceRequest/DistanceRequestFooter.js +++ b/src/components/DistanceRequest/DistanceRequestFooter.js @@ -41,7 +41,7 @@ const propTypes = { expiration: PropTypes.string, }), - /* Onyx Props */ + /** The transaction being interacted with */ transaction: transactionPropTypes, }; @@ -135,9 +135,6 @@ DistanceRequestFooter.propTypes = propTypes; DistanceRequestFooter.defaultProps = defaultProps; export default withOnyx({ - transaction: { - key: ({transactionID}) => `${ONYXKEYS.COLLECTION.TRANSACTION}${transactionID}`, - }, mapboxAccessToken: { key: ONYXKEYS.MAPBOX_ACCESS_TOKEN, }, diff --git a/src/components/DistanceRequest/index.js b/src/components/DistanceRequest/index.js index 6fa5dfede620..995785bdebd5 100644 --- a/src/components/DistanceRequest/index.js +++ b/src/components/DistanceRequest/index.js @@ -211,7 +211,7 @@ function DistanceRequest({transactionID, report, transaction, route, isEditingRe waypoints={waypoints} hasRouteError={hasRouteError} navigateToWaypointEditPage={navigateToWaypointEditPage} - transactionID={transactionID} + transaction={transaction} /> } /> diff --git a/src/components/MoneyRequestConfirmationList.js b/src/components/MoneyRequestConfirmationList.js index da3c19f48d1b..258a15c4c813 100755 --- a/src/components/MoneyRequestConfirmationList.js +++ b/src/components/MoneyRequestConfirmationList.js @@ -209,7 +209,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.transaction; const {canUseViolations} = usePermissions(); const isTypeRequest = props.iouType === CONST.IOU.TYPE.REQUEST; @@ -409,8 +409,8 @@ function MoneyRequestConfirmationList(props) { return; } const distanceMerchant = DistanceRequestUtils.getDistanceMerchant(hasRoute, distance, unit, rate, currency, translate, toLocaleDigit); - IOU.setMoneyRequestMerchant(distanceMerchant); - }, [hasRoute, distance, unit, rate, currency, translate, toLocaleDigit, props.isDistanceRequest]); + IOU.setMoneyRequestMerchant_temporaryForRefactor(props.transactionID, distanceMerchant); + }, [hasRoute, distance, unit, rate, currency, translate, toLocaleDigit, props.isDistanceRequest, props.transactionID]); /** * @param {Object} option @@ -579,7 +579,7 @@ function MoneyRequestConfirmationList(props) { > {props.isDistanceRequest && ( - + )} {(receiptImage || receiptThumbnail) && ( @@ -772,9 +772,6 @@ export default compose( draftTransaction: { key: ({transactionID}) => `${ONYXKEYS.COLLECTION.SPLIT_TRANSACTION_DRAFT}${transactionID}`, }, - transaction: { - key: ({transactionID}) => `${ONYXKEYS.COLLECTION.TRANSACTION}${transactionID}`, - }, policy: { key: ({policyID}) => `${ONYXKEYS.COLLECTION.POLICY}${policyID}`, }, diff --git a/src/components/MoneyTemporaryForRefactorRequestConfirmationList.js b/src/components/MoneyTemporaryForRefactorRequestConfirmationList.js new file mode 100755 index 000000000000..e622367cd6f9 --- /dev/null +++ b/src/components/MoneyTemporaryForRefactorRequestConfirmationList.js @@ -0,0 +1,780 @@ +import {useIsFocused} from '@react-navigation/native'; +import {format} from 'date-fns'; +import lodashGet from 'lodash/get'; +import PropTypes from 'prop-types'; +import React, {useCallback, useEffect, useMemo, useReducer, useRef, useState} from 'react'; +import {View} from 'react-native'; +import {withOnyx} from 'react-native-onyx'; +import _ from 'underscore'; +import useLocalize from '@hooks/useLocalize'; +import compose from '@libs/compose'; +import * as CurrencyUtils from '@libs/CurrencyUtils'; +import DistanceRequestUtils from '@libs/DistanceRequestUtils'; +import * as IOUUtils from '@libs/IOUUtils'; +import Log from '@libs/Log'; +import * as MoneyRequestUtils from '@libs/MoneyRequestUtils'; +import Navigation from '@libs/Navigation/Navigation'; +import * as OptionsListUtils from '@libs/OptionsListUtils'; +import * as PolicyUtils from '@libs/PolicyUtils'; +import * as ReceiptUtils from '@libs/ReceiptUtils'; +import * as ReportUtils from '@libs/ReportUtils'; +import * as TransactionUtils from '@libs/TransactionUtils'; +import useTheme from '@styles/themes/useTheme'; +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 Button from './Button'; +import ButtonWithDropdownMenu from './ButtonWithDropdownMenu'; +import categoryPropTypes from './categoryPropTypes'; +import ConfirmedRoute from './ConfirmedRoute'; +import FormHelpMessage from './FormHelpMessage'; +import * as Expensicons from './Icon/Expensicons'; +import Image from './Image'; +import MenuItemWithTopDescription from './MenuItemWithTopDescription'; +import optionPropTypes from './optionPropTypes'; +import OptionsSelector from './OptionsSelector'; +import SettlementButton from './SettlementButton'; +import Switch from './Switch'; +import tagPropTypes from './tagPropTypes'; +import Text from './Text'; +import transactionPropTypes from './transactionPropTypes'; +import withCurrentUserPersonalDetails, {withCurrentUserPersonalDetailsDefaultProps, withCurrentUserPersonalDetailsPropTypes} from './withCurrentUserPersonalDetails'; + +const propTypes = { + /** Callback to inform parent modal of success */ + onConfirm: PropTypes.func, + + /** Callback to parent modal to send money */ + onSendMoney: PropTypes.func, + + /** Callback to inform a participant is selected */ + onSelectParticipant: PropTypes.func, + + /** Should we request a single or multiple participant selection from user */ + hasMultipleParticipants: PropTypes.bool.isRequired, + + /** IOU amount */ + iouAmount: PropTypes.number.isRequired, + + /** IOU comment */ + iouComment: PropTypes.string, + + /** IOU currency */ + iouCurrencyCode: PropTypes.string, + + /** IOU type */ + iouType: PropTypes.string, + + /** IOU date */ + iouCreated: PropTypes.string, + + /** IOU merchant */ + iouMerchant: PropTypes.string, + + /** IOU category */ + iouCategory: PropTypes.string, + + /** IOU tag */ + iouTag: PropTypes.string, + + /** IOU isBillable */ + iouIsBillable: PropTypes.bool, + + /** Callback to toggle the billable state */ + onToggleBillable: PropTypes.func, + + /** Selected participants from MoneyRequestModal with login / accountID */ + selectedParticipants: PropTypes.arrayOf(optionPropTypes).isRequired, + + /** Payee of the money request with login */ + payeePersonalDetails: optionPropTypes, + + /** Can the participants be modified or not */ + canModifyParticipants: PropTypes.bool, + + /** Should the list be read only, and not editable? */ + isReadOnly: PropTypes.bool, + + /** Depending on expense report or personal IOU report, respective bank account route */ + bankAccountRoute: PropTypes.string, + + ...withCurrentUserPersonalDetailsPropTypes, + + /** Current user session */ + session: PropTypes.shape({ + email: PropTypes.string.isRequired, + }), + + /** The policyID of the request */ + policyID: PropTypes.string, + + /** The reportID of the request */ + reportID: PropTypes.string, + + /** File path of the receipt */ + receiptPath: PropTypes.string, + + /** File name of the receipt */ + receiptFilename: PropTypes.string, + + /** List styles for OptionsSelector */ + listStyles: PropTypes.oneOfType([PropTypes.arrayOf(PropTypes.object), PropTypes.object]), + + /** ID of the transaction that represents the money request */ + transactionID: PropTypes.string, + + /** Unit and rate used for if the money request is a distance request */ + mileageRate: PropTypes.shape({ + /** Unit used to represent distance */ + unit: PropTypes.oneOf([CONST.CUSTOM_UNITS.DISTANCE_UNIT_MILES, CONST.CUSTOM_UNITS.DISTANCE_UNIT_KILOMETERS]), + + /** Rate used to calculate the distance request amount */ + rate: PropTypes.number, + + /** The currency of the rate */ + currency: PropTypes.string, + }), + + /** Whether the money request is a distance request */ + isDistanceRequest: PropTypes.bool, + + /** Whether the money request is a scan request */ + isScanRequest: PropTypes.bool, + + /** Whether we're editing a split bill */ + isEditingSplitBill: PropTypes.bool, + + /** Whether we should show the amount, date, and merchant fields. */ + shouldShowSmartScanFields: PropTypes.bool, + + /** A flag for verifying that the current report is a sub-report of a workspace chat */ + isPolicyExpenseChat: PropTypes.bool, + + /* Onyx Props */ + /** Collection of categories attached to a policy */ + policyCategories: PropTypes.objectOf(categoryPropTypes), + + /** Collection of tags attached to a policy */ + policyTags: tagPropTypes, + + /** Transaction that represents the money request */ + transaction: transactionPropTypes, +}; + +const defaultProps = { + onConfirm: () => {}, + onSendMoney: () => {}, + onSelectParticipant: () => {}, + iouType: CONST.IOU.TYPE.REQUEST, + iouCategory: '', + iouTag: '', + iouIsBillable: false, + onToggleBillable: () => {}, + payeePersonalDetails: null, + canModifyParticipants: false, + isReadOnly: false, + bankAccountRoute: '', + session: { + email: null, + }, + policyID: '', + reportID: '', + ...withCurrentUserPersonalDetailsDefaultProps, + receiptPath: '', + receiptFilename: '', + listStyles: [], + policyCategories: {}, + policyTags: {}, + transactionID: '', + transaction: {}, + mileageRate: {unit: CONST.CUSTOM_UNITS.DISTANCE_UNIT_MILES, rate: 0, currency: 'USD'}, + isDistanceRequest: false, + isScanRequest: false, + shouldShowSmartScanFields: true, + isPolicyExpenseChat: false, +}; + +function MoneyTemporaryForRefactorRequestConfirmationList({ + bankAccountRoute, + canModifyParticipants, + currentUserPersonalDetails, + hasMultipleParticipants, + hasSmartScanFailed, + iouAmount, + iouCategory, + iouComment, + iouCreated, + iouCurrencyCode, + iouIsBillable, + iouMerchant, + iouTag, + iouType, + isDistanceRequest, + isEditingSplitBill, + isPolicyExpenseChat, + isReadOnly, + isScanRequest, + listStyles, + mileageRate, + onConfirm, + onSelectParticipant, + onSendMoney, + onToggleBillable, + payeePersonalDetails, + policy, + policyCategories, + policyID, + policyTags, + receiptFilename, + receiptPath, + reportActionID, + reportID, + selectedParticipants, + session: {accountID}, + shouldShowSmartScanFields, + transaction, +}) { + const theme = useTheme(); + const styles = useThemeStyles(); + const {translate, toLocaleDigit} = useLocalize(); + + const isTypeRequest = iouType === CONST.IOU.TYPE.REQUEST; + const isTypeSplit = iouType === CONST.IOU.TYPE.SPLIT; + const isTypeSend = iouType === CONST.IOU.TYPE.SEND; + + const isSplitWithScan = isTypeSplit && isScanRequest; + + const {unit, rate, currency} = mileageRate; + const distance = lodashGet(transaction, 'routes.route0.distance', 0); + const shouldCalculateDistanceAmount = isDistanceRequest && iouAmount === 0; + + // A flag for showing the categories field + const shouldShowCategories = isPolicyExpenseChat && (iouCategory || OptionsListUtils.hasEnabledOptions(_.values(policyCategories))); + + // A flag and a toggler for showing the rest of the form fields + const [shouldExpandFields, toggleShouldExpandFields] = useReducer((state) => !state, false); + + // Do not hide fields in case of send money request + const shouldShowAllFields = isDistanceRequest || shouldExpandFields || !shouldShowSmartScanFields || isTypeSend || isEditingSplitBill; + + // In Send Money flow, we don't allow the Merchant or Date to be edited. For distance requests, don't show the merchant as there's already another "Distance" menu item + const shouldShowDate = shouldShowAllFields && !isTypeSend && !isSplitWithScan; + const shouldShowMerchant = shouldShowAllFields && !isTypeSend && !isDistanceRequest && !isSplitWithScan; + + // Fetches the first tag list of the policy + const policyTag = PolicyUtils.getTag(policyTags); + const policyTagList = lodashGet(policyTag, 'tags', {}); + const policyTagListName = lodashGet(policyTag, 'name', translate('common.tag')); + + // A flag for showing the tags field + const shouldShowTags = isPolicyExpenseChat && OptionsListUtils.hasEnabledOptions(_.values(policyTagList)); + + // A flag for showing the billable field + const shouldShowBillable = !lodashGet(policy, 'disabledFields.defaultBillable', true); + + const hasRoute = TransactionUtils.hasRoute(transaction); + const isDistanceRequestWithoutRoute = isDistanceRequest && !hasRoute; + const formattedAmount = isDistanceRequestWithoutRoute + ? translate('common.tbd') + : CurrencyUtils.convertToDisplayString( + shouldCalculateDistanceAmount ? DistanceRequestUtils.getDistanceRequestAmount(distance, unit, rate) : iouAmount, + isDistanceRequest ? currency : iouCurrencyCode, + ); + + const isFocused = useIsFocused(); + const [formError, setFormError] = useState(''); + + const [didConfirm, setDidConfirm] = useState(false); + const [didConfirmSplit, setDidConfirmSplit] = useState(false); + + const shouldDisplayFieldError = useMemo(() => { + if (!isEditingSplitBill) { + return false; + } + + return (hasSmartScanFailed && TransactionUtils.hasMissingSmartscanFields(transaction)) || (didConfirmSplit && TransactionUtils.areRequiredFieldsEmpty(transaction)); + }, [isEditingSplitBill, hasSmartScanFailed, transaction, didConfirmSplit]); + + useEffect(() => { + if (shouldDisplayFieldError && hasSmartScanFailed) { + setFormError('iou.receiptScanningFailed'); + return; + } + if (shouldDisplayFieldError && didConfirmSplit) { + setFormError('iou.error.genericSmartscanFailureMessage'); + return; + } + // reset the form error whenever the screen gains or loses focus + setFormError(''); + }, [isFocused, transaction, shouldDisplayFieldError, hasSmartScanFailed, didConfirmSplit]); + + useEffect(() => { + if (!shouldCalculateDistanceAmount) { + return; + } + + const amount = DistanceRequestUtils.getDistanceRequestAmount(distance, unit, rate); + IOU.setMoneyRequestAmount_temporaryForRefactor(transaction.transactionID, amount, currency); + }, [shouldCalculateDistanceAmount, distance, rate, unit, transaction, currency]); + + /** + * Returns the participants with amount + * @param {Array} participants + * @returns {Array} + */ + const getParticipantsWithAmount = useCallback( + (participantsList) => { + const amount = IOUUtils.calculateAmount(participantsList.length, iouAmount, iouCurrencyCode); + return OptionsListUtils.getIOUConfirmationOptionsFromParticipants(participantsList, amount > 0 ? CurrencyUtils.convertToDisplayString(amount, iouCurrencyCode) : ''); + }, + [iouAmount, iouCurrencyCode], + ); + + // If completing a split bill fails, set didConfirm to false to allow the user to edit the fields again + if (isEditingSplitBill && didConfirm) { + setDidConfirm(false); + } + + const splitOrRequestOptions = useMemo(() => { + let text; + if (isTypeSplit && iouAmount === 0) { + text = translate('iou.split'); + } else if ((receiptPath && isTypeRequest) || isDistanceRequestWithoutRoute) { + text = translate('iou.request'); + if (iouAmount !== 0) { + text = translate('iou.requestAmount', {amount: formattedAmount}); + } + } else { + const translationKey = isTypeSplit ? 'iou.splitAmount' : 'iou.requestAmount'; + text = translate(translationKey, {amount: formattedAmount}); + } + return [ + { + text: text[0].toUpperCase() + text.slice(1), + value: iouType, + }, + ]; + }, [isTypeSplit, isTypeRequest, iouType, iouAmount, receiptPath, formattedAmount, isDistanceRequestWithoutRoute, translate]); + + const selectedParticipantsFiltered = useMemo(() => _.filter(selectedParticipants, (participant) => participant.selected), [selectedParticipants]); + const personalDetailsOfPayee = useMemo(() => payeePersonalDetails || currentUserPersonalDetails, [payeePersonalDetails, currentUserPersonalDetails]); + const userCanModifyParticipants = useRef(!isReadOnly && canModifyParticipants && hasMultipleParticipants); + useEffect(() => { + userCanModifyParticipants.current = !isReadOnly && canModifyParticipants && hasMultipleParticipants; + }, [isReadOnly, canModifyParticipants, hasMultipleParticipants]); + const shouldDisablePaidBySection = userCanModifyParticipants.current; + + const optionSelectorSections = useMemo(() => { + const sections = []; + const unselectedParticipants = _.filter(selectedParticipantsFiltered, (participant) => !participant.selected); + if (hasMultipleParticipants) { + const formattedSelectedParticipants = getParticipantsWithAmount(selectedParticipantsFiltered); + let formattedParticipantsList = _.union(formattedSelectedParticipants, unselectedParticipants); + + if (!userCanModifyParticipants.current) { + formattedParticipantsList = _.map(formattedParticipantsList, (participant) => ({ + ...participant, + isDisabled: ReportUtils.isOptimisticPersonalDetail(participant.accountID), + })); + } + + const myIOUAmount = IOUUtils.calculateAmount(selectedParticipantsFiltered.length, iouAmount, iouCurrencyCode, true); + const formattedPayeeOption = OptionsListUtils.getIOUConfirmationOptionsFromPayeePersonalDetail( + personalDetailsOfPayee, + iouAmount > 0 ? CurrencyUtils.convertToDisplayString(myIOUAmount, iouCurrencyCode) : '', + ); + + sections.push( + { + title: translate('moneyRequestConfirmationList.paidBy'), + data: [formattedPayeeOption], + shouldShow: true, + indexOffset: 0, + isDisabled: shouldDisablePaidBySection, + }, + { + title: translate('moneyRequestConfirmationList.splitWith'), + data: formattedParticipantsList, + shouldShow: true, + indexOffset: 1, + }, + ); + } else { + const formattedSelectedParticipants = _.map(selectedParticipantsFiltered, (participant) => ({ + ...participant, + isDisabled: ReportUtils.isOptimisticPersonalDetail(participant.accountID), + })); + sections.push({ + title: translate('common.to'), + data: formattedSelectedParticipants, + shouldShow: true, + indexOffset: 0, + }); + } + return sections; + }, [ + selectedParticipantsFiltered, + hasMultipleParticipants, + iouAmount, + iouCurrencyCode, + getParticipantsWithAmount, + personalDetailsOfPayee, + translate, + shouldDisablePaidBySection, + userCanModifyParticipants, + ]); + + const selectedOptions = useMemo(() => { + if (!hasMultipleParticipants) { + return []; + } + return [...selectedParticipantsFiltered, OptionsListUtils.getIOUConfirmationOptionsFromPayeePersonalDetail(personalDetailsOfPayee)]; + }, [selectedParticipantsFiltered, hasMultipleParticipants, personalDetailsOfPayee]); + + useEffect(() => { + if (!isDistanceRequest) { + return; + } + const distanceMerchant = DistanceRequestUtils.getDistanceMerchant(hasRoute, distance, unit, rate, currency, translate, toLocaleDigit); + IOU.setMoneyRequestMerchant_temporaryForRefactor(transaction.transactionID, distanceMerchant); + }, [hasRoute, distance, unit, rate, currency, translate, toLocaleDigit, isDistanceRequest, transaction]); + + /** + * @param {Object} option + */ + const selectParticipant = useCallback( + (option) => { + // Return early if selected option is currently logged in user. + if (option.accountID === accountID) { + return; + } + onSelectParticipant(option); + }, + [accountID, onSelectParticipant], + ); + + /** + * Navigate to report details or profile of selected user + * @param {Object} option + */ + const navigateToReportOrUserDetail = (option) => { + if (option.accountID) { + const activeRoute = Navigation.getActiveRouteWithoutParams(); + + Navigation.navigate(ROUTES.PROFILE.getRoute(option.accountID, activeRoute)); + } else if (option.reportID) { + Navigation.navigate(ROUTES.REPORT_WITH_ID_DETAILS.getRoute(option.reportID)); + } + }; + + /** + * @param {String} paymentMethod + */ + const confirm = useCallback( + (paymentMethod) => { + if (_.isEmpty(selectedParticipantsFiltered)) { + return; + } + + if (iouType === CONST.IOU.TYPE.SEND) { + if (!paymentMethod) { + return; + } + + setDidConfirm(true); + + Log.info(`[IOU] Sending money via: ${paymentMethod}`); + onSendMoney(paymentMethod); + } else { + // validate the amount for distance requests + const decimals = CurrencyUtils.getCurrencyDecimals(iouCurrencyCode); + if (isDistanceRequest && !isDistanceRequestWithoutRoute && !MoneyRequestUtils.validateAmount(String(iouAmount), decimals)) { + setFormError('common.error.invalidAmount'); + return; + } + + if (isEditingSplitBill && TransactionUtils.areRequiredFieldsEmpty(transaction)) { + setDidConfirmSplit(true); + setFormError('iou.error.genericSmartscanFailureMessage'); + return; + } + + setDidConfirm(true); + onConfirm(selectedParticipantsFiltered); + } + }, + [selectedParticipantsFiltered, onSendMoney, onConfirm, isEditingSplitBill, iouType, isDistanceRequest, isDistanceRequestWithoutRoute, iouCurrencyCode, iouAmount, transaction], + ); + + const footerContent = useMemo(() => { + if (isReadOnly) { + return; + } + + const shouldShowSettlementButton = iouType === CONST.IOU.TYPE.SEND; + const shouldDisableButton = selectedParticipantsFiltered.length === 0; + + const button = shouldShowSettlementButton ? ( + + ) : ( + confirm(value)} + options={splitOrRequestOptions} + buttonSize={CONST.DROPDOWN_BUTTON_SIZE.LARGE} + /> + ); + + return ( + <> + {!_.isEmpty(formError) && ( + + )} + {button} + + ); + }, [confirm, bankAccountRoute, iouCurrencyCode, iouType, isReadOnly, policyID, selectedParticipantsFiltered, splitOrRequestOptions, translate, formError, styles.ph1, styles.mb2]); + + const {image: receiptImage, thumbnail: receiptThumbnail} = receiptPath && receiptFilename ? ReceiptUtils.getThumbnailAndImageURIs(transaction, receiptPath, receiptFilename) : {}; + return ( + + {isDistanceRequest && ( + + + + )} + {(receiptImage || receiptThumbnail) && ( + + )} + {shouldShowSmartScanFields && ( + { + if (isDistanceRequest) { + return; + } + if (isEditingSplitBill) { + Navigation.navigate(ROUTES.EDIT_SPLIT_BILL.getRoute(reportID, reportActionID, CONST.EDIT_REQUEST_FIELD.AMOUNT)); + return; + } + Navigation.navigate(ROUTES.MONEY_REQUEST_STEP_AMOUNT.getRoute(iouType, transaction.transactionID, reportID, Navigation.getActiveRouteWithoutParams())); + }} + style={[styles.moneyRequestMenuItem, styles.mt2]} + titleStyle={styles.moneyRequestConfirmationAmount} + disabled={didConfirm} + brickRoadIndicator={shouldDisplayFieldError && TransactionUtils.isAmountMissing(transaction) ? CONST.BRICK_ROAD_INDICATOR_STATUS.ERROR : ''} + error={shouldDisplayFieldError && TransactionUtils.isAmountMissing(transaction) ? translate('common.error.enterAmount') : ''} + /> + )} + { + if (isEditingSplitBill) { + Navigation.navigate(ROUTES.EDIT_SPLIT_BILL.getRoute(reportID, reportActionID, CONST.EDIT_REQUEST_FIELD.DESCRIPTION)); + return; + } + Navigation.navigate(ROUTES.MONEY_REQUEST_STEP_DESCRIPTION.getRoute(iouType, transaction.transactionID, reportID, Navigation.getActiveRouteWithoutParams())); + }} + style={[styles.moneyRequestMenuItem]} + titleStyle={styles.flex1} + disabled={didConfirm} + interactive={!isReadOnly} + numberOfLinesTitle={2} + /> + {!shouldShowAllFields && ( + + +