From b4b68f87fb2e3d47eb32c520ad774c4a8745a7b7 Mon Sep 17 00:00:00 2001 From: Ana Margarida Silva Date: Tue, 10 Oct 2023 15:30:45 +0100 Subject: [PATCH 1/8] feat: show next steps for draft reports --- src/ONYXKEYS.ts | 1 + src/components/MoneyReportHeader.js | 24 +++++++-- src/components/MoneyReportHeaderStatusBar.js | 57 ++++++++++++++++++++ src/languages/en.ts | 1 + src/languages/es.ts | 1 + src/pages/nextStepPropTypes.js | 44 +++++++++++++++ 6 files changed, 125 insertions(+), 3 deletions(-) create mode 100644 src/components/MoneyReportHeaderStatusBar.js create mode 100644 src/pages/nextStepPropTypes.js diff --git a/src/ONYXKEYS.ts b/src/ONYXKEYS.ts index 0a17d3a1d2f7..fd398ee6ec41 100755 --- a/src/ONYXKEYS.ts +++ b/src/ONYXKEYS.ts @@ -252,6 +252,7 @@ const ONYXKEYS = { REPORT_USER_IS_LEAVING_ROOM: 'reportUserIsLeavingRoom_', SECURITY_GROUP: 'securityGroup_', TRANSACTION: 'transactions_', + NEXT_STEP: 'reportNextStep_', // Manual request tab selector SELECTED_TAB: 'selectedTab_', diff --git a/src/components/MoneyReportHeader.js b/src/components/MoneyReportHeader.js index fbd686fac692..801ba4c87adc 100644 --- a/src/components/MoneyReportHeader.js +++ b/src/components/MoneyReportHeader.js @@ -1,4 +1,5 @@ import React, {useMemo} from 'react'; +import _ from 'underscore'; import {withOnyx} from 'react-native-onyx'; import {View} from 'react-native'; import PropTypes from 'prop-types'; @@ -15,11 +16,13 @@ import Navigation from '../libs/Navigation/Navigation'; import ROUTES from '../ROUTES'; import ONYXKEYS from '../ONYXKEYS'; import CONST from '../CONST'; +import MoneyReportHeaderStatusBar from './MoneyReportHeaderStatusBar'; import SettlementButton from './SettlementButton'; import Button from './Button'; import * as IOU from '../libs/actions/IOU'; import * as CurrencyUtils from '../libs/CurrencyUtils'; import reportPropTypes from '../pages/reportPropTypes'; +import nextStepPropTypes from '../pages/nextStepPropTypes'; const propTypes = { /** The report currently being looked at */ @@ -40,6 +43,9 @@ const propTypes = { /** The chat report this report is linked to */ chatReport: reportPropTypes, + /** The next step for the report */ + nextStep: nextStepPropTypes, + /** Personal details so we can get the ones for the report participants */ personalDetails: PropTypes.objectOf(participantPropTypes).isRequired, @@ -54,13 +60,14 @@ const propTypes = { const defaultProps = { chatReport: {}, + nextStep: {}, session: { email: null, }, policy: {}, }; -function MoneyReportHeader({session, personalDetails, policy, chatReport, report: moneyRequestReport, isSmallScreenWidth}) { +function MoneyReportHeader({session, personalDetails, policy, chatReport, nextStep, report: moneyRequestReport, isSmallScreenWidth}) { const {translate} = useLocalize(); const reportTotal = ReportUtils.getMoneyRequestTotal(moneyRequestReport); const isApproved = ReportUtils.isReportApproved(moneyRequestReport); @@ -81,7 +88,8 @@ function MoneyReportHeader({session, personalDetails, policy, chatReport, report return isManager && !isDraft && !isApproved && !isSettled; }, [policyType, isManager, isDraft, isApproved, isSettled]); const shouldShowSubmitButton = isDraft; - const shouldShowAnyButton = shouldShowSettlementButton || shouldShowApproveButton || shouldShowSubmitButton; + const shouldShowNextSteps = isDraft && nextStep && (!_.isEmpty(nextStep.message) || !_.isEmpty(nextStep.expenseMessage)); + const shouldShowAnyButton = shouldShowSettlementButton || shouldShowApproveButton || shouldShowSubmitButton || shouldShowNextSteps; const bankAccountRoute = ReportUtils.getBankAccountRoute(chatReport); const formattedAmount = CurrencyUtils.convertToDisplayString(reportTotal, moneyRequestReport.currency); @@ -96,7 +104,8 @@ function MoneyReportHeader({session, personalDetails, policy, chatReport, report personalDetails={personalDetails} shouldShowBackButton={isSmallScreenWidth} onBackButtonPress={() => Navigation.goBack(ROUTES.HOME, false, true)} - shouldShowBorderBottom={!shouldShowAnyButton || !isSmallScreenWidth} + // Shows border if no buttons or next steps are showing below the header + shouldShowBorderBottom={!(shouldShowAnyButton && isSmallScreenWidth) && !(shouldShowNextSteps && !isSmallScreenWidth)} > {shouldShowSettlementButton && !isSmallScreenWidth && ( @@ -141,6 +150,12 @@ function MoneyReportHeader({session, personalDetails, policy, chatReport, report )} + {shouldShowNextSteps && ( + + )} {shouldShowSettlementButton && isSmallScreenWidth && ( `${ONYXKEYS.COLLECTION.REPORT}${report.chatReportID}`, }, + nextStep: { + key: ({report}) => `${ONYXKEYS.COLLECTION.NEXT_STEP}${report.reportID}`, + }, session: { key: ONYXKEYS.SESSION, }, diff --git a/src/components/MoneyReportHeaderStatusBar.js b/src/components/MoneyReportHeaderStatusBar.js new file mode 100644 index 000000000000..aa2cca18ac4c --- /dev/null +++ b/src/components/MoneyReportHeaderStatusBar.js @@ -0,0 +1,57 @@ +import React, {useMemo} from 'react'; +import {Text, View} from 'react-native'; +import _ from 'underscore'; +import Str from 'expensify-common/lib/str'; +import PropTypes from 'prop-types'; +import styles from '../styles/styles'; +import useLocalize from '../hooks/useLocalize'; +import nextStepPropTypes from '../pages/nextStepPropTypes'; +import RenderHTML from './RenderHTML'; + +const propTypes = { + /** The next step for the report */ + nextStep: nextStepPropTypes, + + showBorderBottom: PropTypes.bool, +}; + +const defaultProps = { + nextStep: {}, + showBorderBottom: true, +}; + +function MoneyReportHeaderStatusBar({nextStep, showBorderBottom}) { + const {translate} = useLocalize(); + + const messageContent = useMemo(() => { + let nextStepHTML = ''; + + const messageArray = _.isEmpty(nextStep.expenseMessage) ? nextStep.message : nextStep.expenseMessage; + _.each(messageArray, (part) => { + const tagType = part.type || 'span'; + nextStepHTML += `<${tagType}>${Str.safeEscape(part.text)}`; + }); + + return nextStepHTML + .replace(/%expenses/g, 'this expense') + .replace(/%Expenses/g, 'This expense') + .replace(/%tobe/g, 'is'); + }, [nextStep.expenseMessage, nextStep.message]); + + return ( + + + {translate('iou.nextSteps')} + + + + + + ); +} + +MoneyReportHeaderStatusBar.displayName = 'MoneyReportHeaderStatusBar'; +MoneyReportHeaderStatusBar.propTypes = propTypes; +MoneyReportHeaderStatusBar.defaultProps = defaultProps; + +export default MoneyReportHeaderStatusBar; diff --git a/src/languages/en.ts b/src/languages/en.ts index dc0080619eda..1be59c353f56 100755 --- a/src/languages/en.ts +++ b/src/languages/en.ts @@ -534,6 +534,7 @@ export default { settledElsewhere: 'Paid elsewhere', settleExpensify: ({formattedAmount}: SettleExpensifyCardParams) => `Pay ${formattedAmount} with Expensify`, payElsewhere: 'Pay elsewhere', + nextSteps: 'Next Steps', requestAmount: ({amount}: RequestAmountParams) => `request ${amount}`, requestedAmount: ({formattedAmount, comment}: RequestedAmountMessageParams) => `requested ${formattedAmount}${comment ? ` for ${comment}` : ''}`, splitAmount: ({amount}: SplitAmountParams) => `split ${amount}`, diff --git a/src/languages/es.ts b/src/languages/es.ts index a21fc884e867..4b325df0ef35 100644 --- a/src/languages/es.ts +++ b/src/languages/es.ts @@ -526,6 +526,7 @@ export default { settledElsewhere: 'Pagado de otra forma', settleExpensify: ({formattedAmount}: SettleExpensifyCardParams) => `Pagar ${formattedAmount} con Expensify`, payElsewhere: 'Pagar de otra forma', + nextSteps: 'Pasos Siguientes', requestAmount: ({amount}: RequestAmountParams) => `solicitar ${amount}`, requestedAmount: ({formattedAmount, comment}: RequestedAmountMessageParams) => `solicité ${formattedAmount}${comment ? ` para ${comment}` : ''}`, splitAmount: ({amount}: SplitAmountParams) => `dividir ${amount}`, diff --git a/src/pages/nextStepPropTypes.js b/src/pages/nextStepPropTypes.js new file mode 100644 index 000000000000..f515c3bcad72 --- /dev/null +++ b/src/pages/nextStepPropTypes.js @@ -0,0 +1,44 @@ +import PropTypes from 'prop-types'; + +const messagePropType = PropTypes.shape({text: PropTypes.string, type: PropTypes.string, action: PropTypes.string}); + +export default PropTypes.shape({ + /** The message parts of the next step */ + message: PropTypes.arrayOf(messagePropType), + + /** The title for the next step */ + title: PropTypes.string, + + /** Whether the the user must take some sort of action in order to unblock the report */ + requiresUserAction: PropTypes.bool, + + /** The type of next step */ + type: PropTypes.oneOf(['neutral', 'alert', null]), + + /** If the "Undo submit" button should be visible */ + showUndoSubmit: PropTypes.bool, + + /** If the next step should be displayed on mobile */ + showForMobile: PropTypes.bool, + + /** If the next step should be displayed at the expense level */ + showForExpense: PropTypes.bool, + + /** An optional alternate message to display on expenses instead of what is provided in the "message" field */ + expenseMessage: PropTypes.arrayOf(messagePropType), + + /** The next person in the approval chain of the report */ + nextReceiver: PropTypes.string, + + /** An array of buttons to be displayed next to the next step */ + buttons: PropTypes.objectOf( + PropTypes.shape({ + text: PropTypes.string, + tooltip: PropTypes.string, + disabled: PropTypes.bool, + hidden: PropTypes.bool, + // eslint-disable-next-line react/forbid-prop-types + data: PropTypes.array, + }), + ), +}); From b6a9476a9bb1b8ba35069d7e2fc65c001d011b1f Mon Sep 17 00:00:00 2001 From: Ana Margarida Silva Date: Wed, 11 Oct 2023 14:10:11 +0100 Subject: [PATCH 2/8] fix: text wrap in native devices --- src/components/MoneyReportHeader.js | 15 ++++++++------- src/components/MoneyReportHeaderStatusBar.js | 10 +++------- src/pages/nextStepPropTypes.js | 2 +- 3 files changed, 12 insertions(+), 15 deletions(-) diff --git a/src/components/MoneyReportHeader.js b/src/components/MoneyReportHeader.js index 801ba4c87adc..472cdb0d3c8c 100644 --- a/src/components/MoneyReportHeader.js +++ b/src/components/MoneyReportHeader.js @@ -150,14 +150,14 @@ function MoneyReportHeader({session, personalDetails, policy, chatReport, nextSt )} + {shouldShowNextSteps && ( - + + + )} {shouldShowSettlementButton && isSmallScreenWidth && ( - + )} {shouldShowApproveButton && isSmallScreenWidth && ( - +