From 3f4df08e9622fa2c535bd15d2d5e2577738d115c Mon Sep 17 00:00:00 2001 From: Bartosz Grajdek Date: Wed, 22 Nov 2023 13:08:28 +0100 Subject: [PATCH 001/238] Add reason interstatial --- src/ROUTES.ts | 4 + src/components/MoneyRequestHeader.js | 9 ++ .../AppNavigator/ModalStackNavigators.js | 1 + src/libs/Navigation/linkingConfig.js | 1 + src/pages/iou/HoldReasonPage.js | 114 ++++++++++++++++++ 5 files changed, 129 insertions(+) create mode 100644 src/pages/iou/HoldReasonPage.js diff --git a/src/ROUTES.ts b/src/ROUTES.ts index 57d4eb8187ec..dd04b1844317 100644 --- a/src/ROUTES.ts +++ b/src/ROUTES.ts @@ -254,6 +254,10 @@ export default { route: ':iouType/new/category/:reportID?', getRoute: (iouType: string, reportID = '') => `${iouType}/new/category/${reportID}`, }, + MONEY_REQUEST_HOLD_REASON: { + route: ':iouType/edit/reason/:transactionID?', + getRoute: (iouType: string, transactionID: string) => `${iouType}/edit/reason/${transactionID}`, + }, MONEY_REQUEST_TAG: { route: ':iouType/new/tag/:reportID?', getRoute: (iouType: string, reportID = '') => `${iouType}/new/tag/${reportID}`, diff --git a/src/components/MoneyRequestHeader.js b/src/components/MoneyRequestHeader.js index 178163f6569f..82df425ee858 100644 --- a/src/components/MoneyRequestHeader.js +++ b/src/components/MoneyRequestHeader.js @@ -87,6 +87,10 @@ function MoneyRequestHeader({session, parentReport, report, parentReportAction, const canModifyRequest = isActionOwner && !isSettled && !isApproved && !ReportActionsUtils.isDeletedAction(parentReportAction); + const holdMoneyRequest = () => { + Navigation.navigate(ROUTES.MONEY_REQUEST_HOLD_REASON.getRoute(transaction.type, lodashGet(parentReportAction, 'originalMessage.IOUTransactionID'))) + }; + useEffect(() => { if (canModifyRequest) { return; @@ -103,6 +107,11 @@ function MoneyRequestHeader({session, parentReport, report, parentReportAction, onSelected: () => Navigation.navigate(ROUTES.EDIT_REQUEST.getRoute(report.reportID, CONST.EDIT_REQUEST_FIELD.RECEIPT)), }); } + threeDotsMenuItems.push({ + icon: Expensicons.Hourglass, + text: "Hold request", + onSelected: () => holdMoneyRequest(), + }); threeDotsMenuItems.push({ icon: Expensicons.Trashcan, text: translate('reportActionContextMenu.deleteAction', {action: parentReportAction}), diff --git a/src/libs/Navigation/AppNavigator/ModalStackNavigators.js b/src/libs/Navigation/AppNavigator/ModalStackNavigators.js index a2f9bdd7a903..4f8d83dec3bd 100644 --- a/src/libs/Navigation/AppNavigator/ModalStackNavigators.js +++ b/src/libs/Navigation/AppNavigator/ModalStackNavigators.js @@ -47,6 +47,7 @@ const MoneyRequestModalStackNavigator = createModalStackNavigator({ Money_Request_Date: () => require('../../../pages/iou/MoneyRequestDatePage').default, Money_Request_Description: () => require('../../../pages/iou/MoneyRequestDescriptionPage').default, Money_Request_Category: () => require('../../../pages/iou/MoneyRequestCategoryPage').default, + Money_Request_Hold_Reason: () => require('../../../pages/iou/HoldReasonPage').default, Money_Request_Tag: () => require('../../../pages/iou/MoneyRequestTagPage').default, Money_Request_Merchant: () => require('../../../pages/iou/MoneyRequestMerchantPage').default, IOU_Send_Add_Bank_Account: () => require('../../../pages/AddPersonalBankAccountPage').default, diff --git a/src/libs/Navigation/linkingConfig.js b/src/libs/Navigation/linkingConfig.js index 44473998ac62..77006ad1415d 100644 --- a/src/libs/Navigation/linkingConfig.js +++ b/src/libs/Navigation/linkingConfig.js @@ -366,6 +366,7 @@ export default { Money_Request_Currency: ROUTES.MONEY_REQUEST_CURRENCY.route, Money_Request_Description: ROUTES.MONEY_REQUEST_DESCRIPTION.route, Money_Request_Category: ROUTES.MONEY_REQUEST_CATEGORY.route, + Money_Request_Hold_Reason: ROUTES.MONEY_REQUEST_HOLD_REASON.route, Money_Request_Tag: ROUTES.MONEY_REQUEST_TAG.route, Money_Request_Merchant: ROUTES.MONEY_REQUEST_MERCHANT.route, Money_Request_Waypoint: ROUTES.MONEY_REQUEST_WAYPOINT.route, diff --git a/src/pages/iou/HoldReasonPage.js b/src/pages/iou/HoldReasonPage.js new file mode 100644 index 000000000000..c45d5fbb73ae --- /dev/null +++ b/src/pages/iou/HoldReasonPage.js @@ -0,0 +1,114 @@ +// import lodashGet from 'lodash/get'; +import PropTypes from 'prop-types'; +import React, {useCallback, useRef} from 'react'; +import {withOnyx} from 'react-native-onyx'; +import HeaderWithBackButton from '@components/HeaderWithBackButton'; +import ScreenWrapper from '@components/ScreenWrapper'; +import Text from '@components/Text'; +import useLocalize from '@hooks/useLocalize'; +import compose from '@libs/compose'; +import Navigation from '@libs/Navigation/Navigation'; +import useThemeStyles from '@styles/useThemeStyles'; +import * as IOU from '@userActions/IOU'; +import ONYXKEYS from '@src/ONYXKEYS'; +import Form from "@components/Form"; +import {View} from "react-native"; +import TextInput from "@components/TextInput"; +import CONST from "@src/CONST"; +import lodashGet from "lodash/get"; +import _ from "underscore"; +import * as API from '@libs/API'; + +const propTypes = { + /** Navigation route context info provided by react navigation */ + route: PropTypes.shape({ + /** Route specific parameters used on this screen via route :iouType/new/category/:reportID? */ + 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, + }), + }).isRequired +}; + +function HoldReasonPage({route}) { + const styles = useThemeStyles(); + const {translate} = useLocalize(); + const reasonRef = useRef(); + + const transactionID = lodashGet(route, 'params.transactionID', ''); + // const iouType = lodashGet(route, 'params.iouType', ''); + + const navigateBack = () => { + Navigation.goBack(); + }; + + const onSubmit = (values) => { + // eslint-disable-next-line rulesdir/no-api-in-views + API.write('HoldRequest', { + transactionID, + comment: values.reason + }) + } + + const validate = useCallback((value) => { + const errors = {}; + + if (_.isEmpty(value.reason)) { + errors.reason = 'common.error.fieldRequired'; + } + + return errors; + }, []); + + return ( + + +
+ Explain why you're holding this request + + (reasonRef.current = e)} + autoFocus + /> + +
+
+ ); +} + +HoldReasonPage.displayName = 'MoneyRequestHoldReasonPage'; +HoldReasonPage.propTypes = propTypes; + +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}`; + }, + }, + }), +)(HoldReasonPage); From f4297cd218cb7860b606d93b53edbaf8e9a6edd5 Mon Sep 17 00:00:00 2001 From: Bartosz Grajdek Date: Thu, 23 Nov 2023 12:01:21 +0100 Subject: [PATCH 002/238] Hold banner --- assets/images/stopwatch.svg | 3 + src/components/Icon/Expensicons.ts | 2 + src/components/MoneyRequestHeader.js | 23 +++++--- .../ReportActionItem/MoneyRequestView.js | 1 + src/components/TextPill.tsx | 22 ++++++++ src/languages/en.ts | 11 ++++ src/languages/es.ts | 11 ++++ src/libs/ReportUtils.js | 12 ++++ src/libs/TransactionUtils.ts | 9 +++ src/pages/iou/HoldReasonPage.js | 55 ++++++++++--------- 10 files changed, 117 insertions(+), 32 deletions(-) create mode 100644 assets/images/stopwatch.svg create mode 100644 src/components/TextPill.tsx diff --git a/assets/images/stopwatch.svg b/assets/images/stopwatch.svg new file mode 100644 index 000000000000..d27d6b0b7c36 --- /dev/null +++ b/assets/images/stopwatch.svg @@ -0,0 +1,3 @@ + + + diff --git a/src/components/Icon/Expensicons.ts b/src/components/Icon/Expensicons.ts index 3d4f0edb1656..f96ceb30f9c5 100644 --- a/src/components/Icon/Expensicons.ts +++ b/src/components/Icon/Expensicons.ts @@ -106,6 +106,7 @@ import Rotate from '@assets/images/rotate-image.svg'; import RotateLeft from '@assets/images/rotate-left.svg'; import Send from '@assets/images/send.svg'; import Shield from '@assets/images/shield.svg'; +import Stopwatch from '@assets/images/stopwatch.svg'; import AppleLogo from '@assets/images/signIn/apple-logo.svg'; import GoogleLogo from '@assets/images/signIn/google-logo.svg'; import Facebook from '@assets/images/social-facebook.svg'; @@ -240,6 +241,7 @@ export { RotateLeft, Send, Shield, + Stopwatch, Sync, Task, ThreeDots, diff --git a/src/components/MoneyRequestHeader.js b/src/components/MoneyRequestHeader.js index 82df425ee858..f55f93112365 100644 --- a/src/components/MoneyRequestHeader.js +++ b/src/components/MoneyRequestHeader.js @@ -3,6 +3,8 @@ import PropTypes from 'prop-types'; import React, {useCallback, useEffect, useState} from 'react'; import {View} from 'react-native'; import {withOnyx} from 'react-native-onyx'; +import Text from '@components/Text'; +import TextPill from '@components/TextPill'; import useLocalize from '@hooks/useLocalize'; import useWindowDimensions from '@hooks/useWindowDimensions'; import compose from '@libs/compose'; @@ -72,6 +74,7 @@ function MoneyRequestHeader({session, parentReport, report, parentReportAction, const moneyRequestReport = parentReport; const isSettled = ReportUtils.isSettled(moneyRequestReport.reportID); const isApproved = ReportUtils.isReportApproved(moneyRequestReport); + const isHold = true; const {isSmallScreenWidth, windowWidth} = useWindowDimensions(); // Only the requestor can take delete the request, admins can only edit it. @@ -87,8 +90,10 @@ function MoneyRequestHeader({session, parentReport, report, parentReportAction, const canModifyRequest = isActionOwner && !isSettled && !isApproved && !ReportActionsUtils.isDeletedAction(parentReportAction); + // console.log('MYTRANSACTION', transaction); + const holdMoneyRequest = () => { - Navigation.navigate(ROUTES.MONEY_REQUEST_HOLD_REASON.getRoute(transaction.type, lodashGet(parentReportAction, 'originalMessage.IOUTransactionID'))) + Navigation.navigate(ROUTES.MONEY_REQUEST_HOLD_REASON.getRoute(transaction.type, lodashGet(parentReportAction, 'originalMessage.IOUTransactionID'))); }; useEffect(() => { @@ -100,6 +105,11 @@ function MoneyRequestHeader({session, parentReport, report, parentReportAction, }, [canModifyRequest]); const threeDotsMenuItems = [HeaderUtils.getPinMenuItem(report)]; if (canModifyRequest) { + threeDotsMenuItems.push({ + icon: Expensicons.Stopwatch, + text: !isHold ? translate('iou.holdRequest') : translate('iou.unholdRequest'), + onSelected: () => holdMoneyRequest(), + }); if (!TransactionUtils.hasReceipt(transaction)) { threeDotsMenuItems.push({ icon: Expensicons.Receipt, @@ -107,11 +117,6 @@ function MoneyRequestHeader({session, parentReport, report, parentReportAction, onSelected: () => Navigation.navigate(ROUTES.EDIT_REQUEST.getRoute(report.reportID, CONST.EDIT_REQUEST_FIELD.RECEIPT)), }); } - threeDotsMenuItems.push({ - icon: Expensicons.Hourglass, - text: "Hold request", - onSelected: () => holdMoneyRequest(), - }); threeDotsMenuItems.push({ icon: Expensicons.Trashcan, text: translate('reportActionContextMenu.deleteAction', {action: parentReportAction}), @@ -123,7 +128,7 @@ function MoneyRequestHeader({session, parentReport, report, parentReportAction, <> )} + + {translate('iou.hold')} + {translate('iou.requestOnHold')} + {children}; +} + +TextPill.displayName = 'TextPill'; + +export default TextPill; diff --git a/src/languages/en.ts b/src/languages/en.ts index 4c6ea25eb2c8..5c9e2d7b27e3 100755 --- a/src/languages/en.ts +++ b/src/languages/en.ts @@ -599,6 +599,17 @@ export default { }, waitingOnEnabledWallet: ({submitterDisplayName}: WaitingOnBankAccountParams) => `Started settling up, payment is held until ${submitterDisplayName} enables their Wallet`, enableWallet: 'Enable Wallet', + hold: 'Hold', + holdRequest: 'Hold Request', + unholdRequest: 'Unhold Request', + explainHold: "Explain why you're holding this request.", + reason: 'Reason', + holdReasonRequired: 'A reason is required when holding.', + requestOnHold: 'This request was put on hold. Review the comments for next steps.', + confirmApprove: 'Confirm what to approve', + confirmApprovalAmount: 'Approve the entire report total or only the amount not on hold.', + confirmPay: 'Confirm what to pay', + confirmPayAmount: 'Pay all out-of-pocket spend or only the amount not on hold.', }, notificationPreferencesPage: { header: 'Notification preferences', diff --git a/src/languages/es.ts b/src/languages/es.ts index 85eab5c3f14d..90876a3fbc6d 100644 --- a/src/languages/es.ts +++ b/src/languages/es.ts @@ -593,6 +593,17 @@ export default { }, waitingOnEnabledWallet: ({submitterDisplayName}: WaitingOnBankAccountParams) => `Inició el pago, pero no se procesará hasta que ${submitterDisplayName} active su Billetera`, enableWallet: 'Habilitar Billetera', + hold: 'En espera', + holdRequest: 'Solicitud de retención', + unholdRequest: 'Solicitud de cancelación de retención', + explainHold: 'Explique por qué mantiene esta solicitud.', + reason: 'Razón', + holdReasonRequired: 'Se requiere una razón al sostener.', + requestOnHold: 'Esta solicitud quedó en suspenso. Revise los comentarios para los próximos pasos.', + confirmApprove: 'Confirmar qué aprobar', + confirmApprovalAmount: 'Aprobar el total del informe completo o solo el monto no retenido.', + confirmPay: 'Confirmar que pagar', + confirmPayAmount: 'Pague todos los gastos de bolsillo o solo el monto no retenido.', }, notificationPreferencesPage: { header: 'Preferencias de avisos', diff --git a/src/libs/ReportUtils.js b/src/libs/ReportUtils.js index db836549234d..ed3c6ad3e244 100644 --- a/src/libs/ReportUtils.js +++ b/src/libs/ReportUtils.js @@ -4315,6 +4315,18 @@ function shouldDisableWelcomeMessage(report, policy) { return isMoneyRequestReport(report) || isArchivedRoom(report) || !isChatRoom(report) || isChatThread(report) || !PolicyUtils.isPolicyAdmin(policy); } +/** + * Put money request on HOLD + * @param transactionID + * @param comment + */ +function putOnHold(transactionID, comment) { + API.write('HoldRequest', { + transactionID, // the money request being held + comment, // the reason given for the hold + }); +} + export { getReportParticipantsTitle, isReportMessageAttachment, diff --git a/src/libs/TransactionUtils.ts b/src/libs/TransactionUtils.ts index 00ce8c55dbd7..efa1fe84382c 100644 --- a/src/libs/TransactionUtils.ts +++ b/src/libs/TransactionUtils.ts @@ -450,6 +450,14 @@ function getRecentTransactions(transactions: Record, size = 2): .slice(0, size); } +/** + * Check if transaction is on hold + */ +function isOnHold(transaction: Transaction): boolean { + // TODO - add logic + return !!transaction; +} + export { buildOptimisticTransaction, getUpdatedTransaction, @@ -477,6 +485,7 @@ export { isExpensifyCardTransaction, isPending, isPosted, + isOnHold, getWaypoints, isAmountMissing, isMerchantMissing, diff --git a/src/pages/iou/HoldReasonPage.js b/src/pages/iou/HoldReasonPage.js index c45d5fbb73ae..449672ddc1ff 100644 --- a/src/pages/iou/HoldReasonPage.js +++ b/src/pages/iou/HoldReasonPage.js @@ -1,23 +1,22 @@ // 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 _ from 'underscore'; +import FormProvider from '@components/Form/FormProvider'; +import InputWrapper from '@components/Form/InputWrapper'; import HeaderWithBackButton from '@components/HeaderWithBackButton'; import ScreenWrapper from '@components/ScreenWrapper'; import Text from '@components/Text'; +import TextInput from '@components/TextInput'; import useLocalize from '@hooks/useLocalize'; import compose from '@libs/compose'; import Navigation from '@libs/Navigation/Navigation'; import useThemeStyles from '@styles/useThemeStyles'; import * as IOU from '@userActions/IOU'; +import CONST from '@src/CONST'; import ONYXKEYS from '@src/ONYXKEYS'; -import Form from "@components/Form"; -import {View} from "react-native"; -import TextInput from "@components/TextInput"; -import CONST from "@src/CONST"; -import lodashGet from "lodash/get"; -import _ from "underscore"; -import * as API from '@libs/API'; const propTypes = { /** Navigation route context info provided by react navigation */ @@ -30,7 +29,7 @@ const propTypes = { /** The report ID of the IOU */ reportID: PropTypes.string, }), - }).isRequired + }).isRequired, }; function HoldReasonPage({route}) { @@ -38,7 +37,7 @@ function HoldReasonPage({route}) { const {translate} = useLocalize(); const reasonRef = useRef(); - const transactionID = lodashGet(route, 'params.transactionID', ''); + // const transactionID = lodashGet(route, 'params.transactionID', ''); // const iouType = lodashGet(route, 'params.iouType', ''); const navigateBack = () => { @@ -46,17 +45,15 @@ function HoldReasonPage({route}) { }; const onSubmit = (values) => { + // TODO - add a helper function for API call // eslint-disable-next-line rulesdir/no-api-in-views - API.write('HoldRequest', { - transactionID, - comment: values.reason - }) - } + console.log(values); + }; const validate = useCallback((value) => { const errors = {}; - if (_.isEmpty(value.reason)) { + if (_.isEmpty(value.comment)) { errors.reason = 'common.error.fieldRequired'; } @@ -70,24 +67,32 @@ function HoldReasonPage({route}) { testID={HoldReasonPage.displayName} > -
- Explain why you're holding this request + + {translate('iou.explainHold')} - (reasonRef.current = e)} autoFocus /> - +
); } From 317f5fc08df54b987c534aad6c4dbcf2e77c097e Mon Sep 17 00:00:00 2001 From: Bartosz Grajdek Date: Tue, 5 Dec 2023 16:47:44 +0100 Subject: [PATCH 003/238] Add new translations --- src/languages/en.ts | 2 ++ src/languages/es.ts | 2 ++ 2 files changed, 4 insertions(+) diff --git a/src/languages/en.ts b/src/languages/en.ts index 5c9e2d7b27e3..9b3dd9f1944f 100755 --- a/src/languages/en.ts +++ b/src/languages/en.ts @@ -610,6 +610,8 @@ export default { confirmApprovalAmount: 'Approve the entire report total or only the amount not on hold.', confirmPay: 'Confirm what to pay', confirmPayAmount: 'Pay all out-of-pocket spend or only the amount not on hold.', + payOnly: 'Pay only', + approveOnly: 'Approve only' }, notificationPreferencesPage: { header: 'Notification preferences', diff --git a/src/languages/es.ts b/src/languages/es.ts index 90876a3fbc6d..f112aeb8a5fc 100644 --- a/src/languages/es.ts +++ b/src/languages/es.ts @@ -604,6 +604,8 @@ export default { confirmApprovalAmount: 'Aprobar el total del informe completo o solo el monto no retenido.', confirmPay: 'Confirmar que pagar', confirmPayAmount: 'Pague todos los gastos de bolsillo o solo el monto no retenido.', + payOnly: 'Paga solo', + approveOnly: 'Aprobar sólo' }, notificationPreferencesPage: { header: 'Preferencias de avisos', From da7e0a69921289525d8e5398f2c9265d40341cbb Mon Sep 17 00:00:00 2001 From: Bartosz Grajdek Date: Tue, 5 Dec 2023 16:48:15 +0100 Subject: [PATCH 004/238] Change Header styles --- src/components/Header.tsx | 9 ++++++--- 1 file changed, 6 insertions(+), 3 deletions(-) diff --git a/src/components/Header.tsx b/src/components/Header.tsx index 46fe1a25c920..b48883e25175 100644 --- a/src/components/Header.tsx +++ b/src/components/Header.tsx @@ -1,5 +1,5 @@ import React, {ReactElement} from 'react'; -import {StyleProp, TextStyle, View} from 'react-native'; +import {StyleProp, TextStyle, View, ViewStyle} from 'react-native'; import useThemeStyles from '@styles/useThemeStyles'; import EnvironmentBadge from './EnvironmentBadge'; import Text from './Text'; @@ -16,12 +16,15 @@ type HeaderProps = { /** Additional text styles */ textStyles?: StyleProp; + + /** Additional text styles */ + containerStyles?: StyleProp; }; -function Header({title = '', subtitle = '', textStyles = [], shouldShowEnvironmentBadge = false}: HeaderProps) { +function Header({title = '', subtitle = '', textStyles = [], containerStyles = [], shouldShowEnvironmentBadge = false}: HeaderProps) { const styles = useThemeStyles(); return ( - + {typeof title === 'string' ? Boolean(title) && ( From d38c0f69f3f059809d0021d3bc66f004f9a8b6cc Mon Sep 17 00:00:00 2001 From: Bartosz Grajdek Date: Tue, 5 Dec 2023 17:15:59 +0100 Subject: [PATCH 005/238] Add full boolean to IOU requests --- src/libs/actions/IOU.js | 14 ++++++++------ 1 file changed, 8 insertions(+), 6 deletions(-) diff --git a/src/libs/actions/IOU.js b/src/libs/actions/IOU.js index 939a11dad511..54e47fce8705 100644 --- a/src/libs/actions/IOU.js +++ b/src/libs/actions/IOU.js @@ -2440,9 +2440,10 @@ function getSendMoneyParams(report, amount, currency, comment, paymentMethodType * @param {Object} iouReport * @param {Object} recipient * @param {String} paymentMethodType + * @param {Boolean} full * @returns {Object} */ -function getPayMoneyRequestParams(chatReport, iouReport, recipient, paymentMethodType) { +function getPayMoneyRequestParams(chatReport, iouReport, recipient, paymentMethodType, full) { const optimisticIOUReportAction = ReportUtils.buildOptimisticIOUReportAction( CONST.IOU.REPORT_ACTION_TYPE.PAY, -iouReport.total, @@ -2557,6 +2558,7 @@ function getPayMoneyRequestParams(chatReport, iouReport, recipient, paymentMetho chatReportID: chatReport.reportID, reportActionID: optimisticIOUReportAction.reportActionID, paymentMethodType, + full }, optimisticData, successData, @@ -2600,7 +2602,7 @@ function sendMoneyWithWallet(report, amount, currency, comment, managerID, recip Report.notifyNewAction(params.chatReportID, managerID); } -function approveMoneyRequest(expenseReport) { +function approveMoneyRequest(expenseReport, full) { const optimisticApprovedReportAction = ReportUtils.buildOptimisticApprovedReportAction(expenseReport.total, expenseReport.currency, expenseReport.reportID); const optimisticReportActionsData = { @@ -2650,7 +2652,7 @@ function approveMoneyRequest(expenseReport) { }, ]; - API.write('ApproveMoneyRequest', {reportID: expenseReport.reportID, approvedReportActionID: optimisticApprovedReportAction.reportActionID}, {optimisticData, successData, failureData}); + API.write('ApproveMoneyRequest', {reportID: expenseReport.reportID, approvedReportActionID: optimisticApprovedReportAction.reportActionID, full}, {optimisticData, successData, failureData}); } /** @@ -2730,11 +2732,11 @@ function submitReport(expenseReport) { * @param {String} paymentType * @param {Object} chatReport * @param {Object} iouReport - * @param {String} reimbursementBankAccountState + * @param {Boolean} full */ -function payMoneyRequest(paymentType, chatReport, iouReport) { +function payMoneyRequest(paymentType, chatReport, iouReport, full) { const recipient = {accountID: iouReport.ownerAccountID}; - const {params, optimisticData, successData, failureData} = getPayMoneyRequestParams(chatReport, iouReport, recipient, paymentType); + const {params, optimisticData, successData, failureData} = getPayMoneyRequestParams(chatReport, iouReport, recipient, paymentType, full); // For now we need to call the PayMoneyRequestWithWallet API since PayMoneyRequest was not updated to work with // Expensify Wallets. From 818a84187c1cf33d3be5e063cee4c50479b05e73 Mon Sep 17 00:00:00 2001 From: Bartosz Grajdek Date: Tue, 5 Dec 2023 17:16:21 +0100 Subject: [PATCH 006/238] Mock Report & Transaction Utils --- src/libs/ReportUtils.js | 19 +++++++++++++------ src/libs/TransactionUtils.ts | 3 +-- 2 files changed, 14 insertions(+), 8 deletions(-) diff --git a/src/libs/ReportUtils.js b/src/libs/ReportUtils.js index ed3c6ad3e244..bbed6796eea3 100644 --- a/src/libs/ReportUtils.js +++ b/src/libs/ReportUtils.js @@ -4317,14 +4317,19 @@ function shouldDisableWelcomeMessage(report, policy) { /** * Put money request on HOLD - * @param transactionID - * @param comment + * @param {string} transactionID + * @param {string} comment */ function putOnHold(transactionID, comment) { - API.write('HoldRequest', { - transactionID, // the money request being held - comment, // the reason given for the hold - }); + return; +} + +/** + * Check if Report has any held expenses + * @param {Object} report + */ +function hasHeldExpenses(report) { + return true; } export { @@ -4492,4 +4497,6 @@ export { getRoom, shouldDisableWelcomeMessage, canEditWriteCapability, + hasHeldExpenses, + putOnHold }; diff --git a/src/libs/TransactionUtils.ts b/src/libs/TransactionUtils.ts index efa1fe84382c..3df67742ea88 100644 --- a/src/libs/TransactionUtils.ts +++ b/src/libs/TransactionUtils.ts @@ -454,8 +454,7 @@ function getRecentTransactions(transactions: Record, size = 2): * Check if transaction is on hold */ function isOnHold(transaction: Transaction): boolean { - // TODO - add logic - return !!transaction; + return true; } export { From ed3fe5eca317c348752aa82a8c2676fbc1526bbb Mon Sep 17 00:00:00 2001 From: Bartosz Grajdek Date: Thu, 14 Dec 2023 10:15:46 +0100 Subject: [PATCH 007/238] Add Offline Support --- src/ROUTES.ts | 2 +- src/components/DecisionModal.js | 102 +++++++++++++++ src/components/Icon/Expensicons.ts | 2 +- src/components/MoneyReportHeader.js | 47 ++++++- src/components/MoneyRequestHeader.js | 30 +++-- src/components/ProcessMoneyRequestHoldMenu.js | 92 ++++++++++++++ src/languages/en.ts | 2 +- src/languages/es.ts | 2 +- src/libs/ReportUtils.js | 31 +++-- src/libs/TransactionUtils.ts | 5 +- src/libs/actions/IOU.js | 116 +++++++++++++++++- src/pages/iou/HoldReasonPage.js | 15 +-- src/types/onyx/Transaction.ts | 1 + 13 files changed, 408 insertions(+), 39 deletions(-) create mode 100644 src/components/DecisionModal.js create mode 100644 src/components/ProcessMoneyRequestHoldMenu.js diff --git a/src/ROUTES.ts b/src/ROUTES.ts index 23da11673499..ad606cebad3c 100644 --- a/src/ROUTES.ts +++ b/src/ROUTES.ts @@ -272,7 +272,7 @@ export default { }, MONEY_REQUEST_HOLD_REASON: { route: ':iouType/edit/reason/:transactionID?', - getRoute: (iouType: string, transactionID: string) => `${iouType}/edit/reason/${transactionID}`, + getRoute: (iouType: string, transactionID: string, reportID: string, backTo: string) => `${iouType}/edit/reason/${transactionID}?reportID=${reportID}&backTo=${backTo}`, }, MONEY_REQUEST_TAG: { route: ':iouType/new/tag/:reportID?', diff --git a/src/components/DecisionModal.js b/src/components/DecisionModal.js new file mode 100644 index 000000000000..52c1974e6af9 --- /dev/null +++ b/src/components/DecisionModal.js @@ -0,0 +1,102 @@ +import PropTypes from 'prop-types'; +import React from 'react'; +import {View} from 'react-native'; +import Button from '@components/Button'; +import Header from '@components/Header'; +import Icon from '@components/Icon'; +import * as Expensicons from '@components/Icon/Expensicons'; +import Modal from '@components/Modal'; +import PressableWithoutFeedback from '@components/Pressable/PressableWithoutFeedback'; +import Text from '@components/Text'; +import Tooltip from '@components/Tooltip'; +import useLocalize from '@hooks/useLocalize'; +import useThemeStyles from '@styles/useThemeStyles'; +import CONST from '@src/CONST'; + +const propTypes = { + /** Title describing purpose of modal */ + title: PropTypes.string.isRequired, + + /** Modal subtitle/description */ + prompt: PropTypes.string, + + /** Text content used in first button */ + firstOptionText: PropTypes.string.isRequired, + + /** Text content used in second button */ + secondOptionText: PropTypes.string.isRequired, + + /** onSubmit callback fired after clicking on first button */ + onFirstOptionSubmit: PropTypes.func.isRequired, + + /** onSubmit callback fired after clicking on */ + onSecondOptionSubmit: PropTypes.func.isRequired, + + /** Is the window width narrow, like on a mobile device? */ + isSmallScreenWidth: PropTypes.bool.isRequired, + + /** Callback for closing modal */ + onClose: PropTypes.func.isRequired, + + /** Whether modal is visible */ + isVisible: PropTypes.bool.isRequired, +}; + +const defaultProps = { + prompt: '', +}; + +function DecisionModal({title, prompt, firstOptionText, secondOptionText, onFirstOptionSubmit, onSecondOptionSubmit, isSmallScreenWidth, onClose, isVisible}) { + const {translate} = useLocalize(); + const styles = useThemeStyles(); + + return ( + + + + +
+ + + + + + + + {prompt} + +