diff --git a/src/ROUTES.js b/src/ROUTES.js index f7d465be96a7..7e20bb7523f0 100644 --- a/src/ROUTES.js +++ b/src/ROUTES.js @@ -93,6 +93,8 @@ export default { getIouRequestCurrencyRoute: (reportID, currency, backTo) => `${IOU_REQUEST_CURRENCY}/${reportID}?currency=${currency}&backTo=${backTo}`, getIouBillCurrencyRoute: (reportID, currency, backTo) => `${IOU_BILL_CURRENCY}/${reportID}?currency=${currency}&backTo=${backTo}`, getIouSendCurrencyRoute: (reportID, currency, backTo) => `${IOU_SEND_CURRENCY}/${reportID}?currency=${currency}&backTo=${backTo}`, + SPLIT_BILL_DETAILS: `r/:reportID/split/:reportActionID`, + getSplitBillDetailsRoute: (reportID, reportActionID) => `r/${reportID}/split/${reportActionID}`, getNewTaskRoute: (reportID) => `${NEW_TASK}/${reportID}`, NEW_TASK_WITH_REPORT_ID: `${NEW_TASK}/:reportID?`, TASK_TITLE: 'r/:reportID/title', diff --git a/src/components/MoneyRequestConfirmationList.js b/src/components/MoneyRequestConfirmationList.js index 048744c9eca7..4e53f6967df0 100755 --- a/src/components/MoneyRequestConfirmationList.js +++ b/src/components/MoneyRequestConfirmationList.js @@ -23,10 +23,10 @@ import * as CurrencyUtils from '../libs/CurrencyUtils'; const propTypes = { /** Callback to inform parent modal of success */ - onConfirm: PropTypes.func.isRequired, + onConfirm: PropTypes.func, /** Callback to parent modal to send money */ - onSendMoney: PropTypes.func.isRequired, + onSendMoney: PropTypes.func, /** Should we request a single or multiple participant selection from user */ hasMultipleParticipants: PropTypes.bool.isRequired, @@ -40,11 +40,17 @@ const propTypes = { /** Selected participants from MoneyRequestModal with login */ participants: 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.isRequired, + bankAccountRoute: PropTypes.string, ...windowDimensionsPropTypes, @@ -69,18 +75,24 @@ const propTypes = { }), /** Callback function to navigate to a provided step in the MoneyRequestModal flow */ - navigateToStep: PropTypes.func.isRequired, + navigateToStep: PropTypes.func, /** The policyID of the request */ policyID: PropTypes.string, }; const defaultProps = { + onConfirm: () => {}, + onSendMoney: () => {}, + navigateToStep: () => {}, iou: { selectedCurrencyCode: CONST.CURRENCY.USD, }, iouType: CONST.IOU.MONEY_REQUEST_TYPE.REQUEST, + payeePersonalDetails: null, canModifyParticipants: false, + isReadOnly: false, + bankAccountRoute: '', session: { email: null, }, @@ -158,6 +170,15 @@ class MoneyRequestConfirmationList extends Component { return _.map(participants, (option) => _.omit(option, 'descriptiveText')); } + /** + * Returns the personalDetails object for the payee. Use the payee prop if passed, else fallback to current user + * + * @returns {Object} personalDetails + */ + getPayeePersonalDetails() { + return this.props.payeePersonalDetails || this.props.currentUserPersonalDetails; + } + /** * Returns the sections needed for the OptionsSelector * @@ -174,15 +195,15 @@ class MoneyRequestConfirmationList extends Component { const formattedParticipants = _.union(formattedSelectedParticipants, formattedUnselectedParticipants); const myIOUAmount = IOUUtils.calculateAmount(selectedParticipants.length, this.props.iouAmount, true); - const formattedMyPersonalDetails = OptionsListUtils.getIOUConfirmationOptionsFromMyPersonalDetail( - this.props.currentUserPersonalDetails, + const formattedPayeePersonalDetails = OptionsListUtils.getIOUConfirmationOptionsFromPayeePersonalDetail( + this.getPayeePersonalDetails(), CurrencyUtils.convertToDisplayString(myIOUAmount, this.props.iou.selectedCurrencyCode), ); sections.push( { title: this.props.translate('moneyRequestConfirmationList.whoPaid'), - data: [formattedMyPersonalDetails], + data: [formattedPayeePersonalDetails], shouldShow: true, indexOffset: 0, isDisabled: true, @@ -206,6 +227,36 @@ class MoneyRequestConfirmationList extends Component { return sections; } + getFooterContent() { + if (this.props.isReadOnly) { + return; + } + + const selectedParticipants = this.getSelectedParticipants(); + const shouldShowSettlementButton = this.props.iouType === CONST.IOU.MONEY_REQUEST_TYPE.SEND; + const shouldDisableButton = selectedParticipants.length === 0; + const recipient = this.state.participants[0]; + + return shouldShowSettlementButton ? ( + + ) : ( + this.confirm(value)} + options={this.getSplitOrRequestOptions()} + /> + ); + } + /** * Returns selected options -- there is checkmark for every row in List for split flow * @returns {Array} @@ -215,7 +266,7 @@ class MoneyRequestConfirmationList extends Component { return []; } const selectedParticipants = this.getSelectedParticipants(); - return [...selectedParticipants, OptionsListUtils.getIOUConfirmationOptionsFromMyPersonalDetail(this.props.currentUserPersonalDetails)]; + return [...selectedParticipants, OptionsListUtils.getIOUConfirmationOptionsFromPayeePersonalDetail(this.getPayeePersonalDetails())]; } /** @@ -263,11 +314,7 @@ class MoneyRequestConfirmationList extends Component { } render() { - const selectedParticipants = this.getSelectedParticipants(); - const shouldShowSettlementButton = this.props.iouType === CONST.IOU.MONEY_REQUEST_TYPE.SEND; - const shouldDisableButton = selectedParticipants.length === 0; - const recipient = this.state.participants[0]; - const canModifyParticipants = this.props.canModifyParticipants && this.props.hasMultipleParticipants; + const canModifyParticipants = !this.props.isReadOnly && this.props.canModifyParticipants && this.props.hasMultipleParticipants; const formattedAmount = CurrencyUtils.convertToDisplayString(this.props.iouAmount, this.props.iou.selectedCurrencyCode); return ( @@ -285,43 +332,24 @@ class MoneyRequestConfirmationList extends Component { shouldShowTextInput={false} shouldUseStyleForChildren={false} optionHoveredStyle={canModifyParticipants ? styles.hoveredComponentBG : {}} - footerContent={ - shouldShowSettlementButton ? ( - - ) : ( - this.confirm(value)} - options={this.getSplitOrRequestOptions()} - /> - ) - } + footerContent={this.getFooterContent()} > this.props.navigateToStep(0)} style={[styles.moneyRequestMenuItem, styles.mt2]} titleStyle={styles.moneyRequestConfirmationAmount} - disabled={this.state.didConfirm} + disabled={this.state.didConfirm || this.props.isReadOnly} /> Navigation.navigate(ROUTES.MONEY_REQUEST_DESCRIPTION)} style={[styles.moneyRequestMenuItem, styles.mb2]} - disabled={this.state.didConfirm} + disabled={this.state.didConfirm || this.props.isReadOnly} /> ); diff --git a/src/components/OptionsSelector/BaseOptionsSelector.js b/src/components/OptionsSelector/BaseOptionsSelector.js index f2ae4ee96718..312f5d386b61 100755 --- a/src/components/OptionsSelector/BaseOptionsSelector.js +++ b/src/components/OptionsSelector/BaseOptionsSelector.js @@ -275,7 +275,8 @@ class BaseOptionsSelector extends Component { } render() { - const shouldShowFooter = (this.props.shouldShowConfirmButton || this.props.footerContent) && !(this.props.canSelectMultipleOptions && _.isEmpty(this.props.selectedOptions)); + const shouldShowFooter = + !this.props.isReadOnly && (this.props.shouldShowConfirmButton || this.props.footerContent) && !(this.props.canSelectMultipleOptions && _.isEmpty(this.props.selectedOptions)); const defaultConfirmButtonText = _.isUndefined(this.props.confirmButtonText) ? this.props.translate('common.confirm') : this.props.confirmButtonText; const shouldShowDefaultConfirmButton = !this.props.footerContent && defaultConfirmButtonText; const textInput = ( diff --git a/src/components/ReportActionItem/MoneyRequestAction.js b/src/components/ReportActionItem/MoneyRequestAction.js index 174e4c922327..1f11eeae780e 100644 --- a/src/components/ReportActionItem/MoneyRequestAction.js +++ b/src/components/ReportActionItem/MoneyRequestAction.js @@ -91,12 +91,12 @@ const defaultProps = { }; const MoneyRequestAction = (props) => { - const hasMultipleParticipants = lodashGet(props.chatReport, 'participants', []).length > 1; const isSplitBillAction = lodashGet(props.action, 'originalMessage.type', '') === CONST.IOU.REPORT_ACTION_TYPE.SPLIT; const onIOUPreviewPressed = () => { - if (isSplitBillAction && hasMultipleParticipants) { - Navigation.navigate(ROUTES.getReportParticipantsRoute(props.chatReportID)); + if (isSplitBillAction) { + const reportActionID = lodashGet(props.action, 'reportActionID', '0'); + Navigation.navigate(ROUTES.getSplitBillDetailsRoute(props.chatReportID, reportActionID)); return; } diff --git a/src/libs/Navigation/AppNavigator/AuthScreens.js b/src/libs/Navigation/AppNavigator/AuthScreens.js index 6a00bc750e9a..0e523002a939 100644 --- a/src/libs/Navigation/AppNavigator/AuthScreens.js +++ b/src/libs/Navigation/AppNavigator/AuthScreens.js @@ -340,6 +340,12 @@ class AuthScreens extends React.Component { component={ModalStackNavigators.EnablePaymentsStackNavigator} listeners={modalScreenListeners} /> + { + const SplitBillDetailsPage = require('../../../pages/iou/SplitBillDetailsPage').default; + return SplitBillDetailsPage; + }, + name: 'SplitDetails_Root', + }, +]); + const DetailsModalStackNavigator = createModalStackNavigator([ { getComponent: () => { @@ -696,6 +706,7 @@ export { IOUBillStackNavigator, IOURequestModalStackNavigator, IOUSendModalStackNavigator, + SplitDetailsModalStackNavigator, DetailsModalStackNavigator, ReportDetailsModalStackNavigator, TaskModalStackNavigator, diff --git a/src/libs/Navigation/linkingConfig.js b/src/libs/Navigation/linkingConfig.js index ed34421d4ace..4659477c2df9 100644 --- a/src/libs/Navigation/linkingConfig.js +++ b/src/libs/Navigation/linkingConfig.js @@ -295,6 +295,11 @@ export default { IOU_Send_Add_Debit_Card: ROUTES.IOU_SEND_ADD_DEBIT_CARD, }, }, + SplitDetails: { + screens: { + SplitDetails_Root: ROUTES.SPLIT_BILL_DETAILS, + }, + }, Task_Details: { screens: { Task_Title: ROUTES.TASK_TITLE, diff --git a/src/libs/OptionsListUtils.js b/src/libs/OptionsListUtils.js index 8513ce55f13a..d2a0aa0aace4 100644 --- a/src/libs/OptionsListUtils.js +++ b/src/libs/OptionsListUtils.js @@ -794,25 +794,25 @@ function getSearchOptions(reports, personalDetails, searchValue = '', betas) { } /** - * Build the IOUConfirmation options for showing MyPersonalDetail + * Build the IOUConfirmation options for showing the payee personalDetail * - * @param {Object} myPersonalDetail + * @param {Object} personalDetail * @param {String} amountText * @returns {Object} */ -function getIOUConfirmationOptionsFromMyPersonalDetail(myPersonalDetail, amountText) { +function getIOUConfirmationOptionsFromPayeePersonalDetail(personalDetail, amountText) { return { - text: myPersonalDetail.displayName, - alternateText: myPersonalDetail.login, + text: personalDetail.displayName ? personalDetail.displayName : personalDetail.login, + alternateText: personalDetail.login, icons: [ { - source: ReportUtils.getAvatar(myPersonalDetail.avatar, myPersonalDetail.login), - name: myPersonalDetail.login, + source: ReportUtils.getAvatar(personalDetail.avatar, personalDetail.login), + name: personalDetail.login, type: CONST.ICON_TYPE_AVATAR, }, ], descriptiveText: amountText, - login: myPersonalDetail.login, + login: personalDetail.login, }; } @@ -953,7 +953,7 @@ export { getMemberInviteOptions, getHeaderMessage, getPersonalDetailsForLogins, - getIOUConfirmationOptionsFromMyPersonalDetail, + getIOUConfirmationOptionsFromPayeePersonalDetail, getIOUConfirmationOptionsFromParticipants, getSearchText, getAllReportErrors, diff --git a/src/pages/iou/SplitBillDetailsPage.js b/src/pages/iou/SplitBillDetailsPage.js new file mode 100644 index 000000000000..19178582b6ba --- /dev/null +++ b/src/pages/iou/SplitBillDetailsPage.js @@ -0,0 +1,117 @@ +import React from 'react'; +import _ from 'underscore'; +import {View} from 'react-native'; +import PropTypes from 'prop-types'; +import {withOnyx} from 'react-native-onyx'; +import lodashGet from 'lodash/get'; +import styles from '../../styles/styles'; +import ONYXKEYS from '../../ONYXKEYS'; +import * as OptionsListUtils from '../../libs/OptionsListUtils'; +import ModalHeader from './ModalHeader'; +import ScreenWrapper from '../../components/ScreenWrapper'; +import MoneyRequestConfirmationList from '../../components/MoneyRequestConfirmationList'; +import personalDetailsPropType from '../personalDetailsPropType'; +import withLocalize, {withLocalizePropTypes} from '../../components/withLocalize'; +import compose from '../../libs/compose'; +import reportActionPropTypes from '../home/report/reportActionPropTypes'; +import reportPropTypes from '../reportPropTypes'; +import withReportOrNotFound from '../home/report/withReportOrNotFound'; +import FullPageNotFoundView from '../../components/BlockingViews/FullPageNotFoundView'; +import CONST from '../../CONST'; + +const propTypes = { + /* Onyx Props */ + + /** The personal details of the person who is logged in */ + personalDetails: personalDetailsPropType, + + /** The active report */ + report: reportPropTypes.isRequired, + + /** Array of report actions for this report */ + reportActions: PropTypes.shape(reportActionPropTypes), + + /** Route params */ + route: PropTypes.shape({ + params: PropTypes.shape({ + /** Report ID passed via route r/:reportID/split/details */ + reportID: PropTypes.string, + + /** ReportActionID passed via route r/split/:reportActionID */ + reportActionID: PropTypes.string, + }), + }).isRequired, + + ...withLocalizePropTypes, +}; + +const defaultProps = { + personalDetails: {}, + reportActions: {}, +}; + +/** + * Get the reportID for the associated chatReport + * + * @param {Object} route + * @param {Object} route.params + * @param {String} route.params.reportID + * @returns {String} + */ +function getReportID(route) { + return route.params.reportID.toString(); +} + +const SplitBillDetailsPage = (props) => { + const reportAction = props.reportActions[`${props.route.params.reportActionID.toString()}`]; + const personalDetails = OptionsListUtils.getPersonalDetailsForLogins(reportAction.originalMessage.participants, props.personalDetails); + const participants = OptionsListUtils.getParticipantsOptions(reportAction.originalMessage, personalDetails); + const payeePersonalDetails = _.filter(participants, (participant) => participant.login === reportAction.actorEmail)[0]; + const participantsExcludingPayee = _.filter(participants, (participant) => participant.login !== reportAction.actorEmail); + const splitAmount = parseInt(lodashGet(reportAction, 'originalMessage.amount', 0), 10); + + return ( + + + + + {Boolean(participants.length) && ( + + )} + + + + ); +}; + +SplitBillDetailsPage.propTypes = propTypes; +SplitBillDetailsPage.defaultProps = defaultProps; +SplitBillDetailsPage.displayName = 'SplitBillDetailsPage'; + +export default compose( + withLocalize, + withReportOrNotFound, + withOnyx({ + personalDetails: { + key: ONYXKEYS.PERSONAL_DETAILS, + }, + reportActions: { + key: ({route}) => `${ONYXKEYS.COLLECTION.REPORT_ACTIONS}${getReportID(route)}`, + canEvict: false, + }, + }), +)(SplitBillDetailsPage); diff --git a/src/pages/iou/steps/MoneyRequestConfirmPage.js b/src/pages/iou/steps/MoneyRequestConfirmPage.js index 0ab9aea75cf3..e86edadf5223 100644 --- a/src/pages/iou/steps/MoneyRequestConfirmPage.js +++ b/src/pages/iou/steps/MoneyRequestConfirmPage.js @@ -57,7 +57,7 @@ const MoneyRequestConfirmPage = (props) => ( /> ); -MoneyRequestConfirmPage.displayName = 'IOUConfirmPage'; +MoneyRequestConfirmPage.displayName = 'MoneyRequestConfirmPage'; MoneyRequestConfirmPage.propTypes = propTypes; MoneyRequestConfirmPage.defaultProps = defaultProps;