From a3b0dd238e5e6a1375355d4bba11c510bb02e560 Mon Sep 17 00:00:00 2001 From: Andrew Li Date: Mon, 7 Aug 2023 16:23:21 -0400 Subject: [PATCH 01/61] Disable copy to clipboard for report previews --- src/pages/home/report/ContextMenu/ContextMenuActions.js | 1 + 1 file changed, 1 insertion(+) diff --git a/src/pages/home/report/ContextMenu/ContextMenuActions.js b/src/pages/home/report/ContextMenu/ContextMenuActions.js index 79e49efb6a03..449e10d35992 100644 --- a/src/pages/home/report/ContextMenu/ContextMenuActions.js +++ b/src/pages/home/report/ContextMenu/ContextMenuActions.js @@ -169,6 +169,7 @@ export default [ shouldShow: (type, reportAction) => type === CONTEXT_MENU_TYPES.REPORT_ACTION && reportAction.actionName !== CONST.REPORT.ACTIONS.TYPE.IOU && + reportAction.actionName !== CONST.REPORT.ACTIONS.TYPE.REPORTPREVIEW && reportAction.actionName !== CONST.REPORT.ACTIONS.TYPE.TASKCANCELLED && reportAction.actionName !== CONST.REPORT.ACTIONS.TYPE.TASKCOMPLETED && reportAction.actionName !== CONST.REPORT.ACTIONS.TYPE.TASKREOPENED && From f143e1fa72d6988fa294513117252a06ab184f42 Mon Sep 17 00:00:00 2001 From: Andrew Li Date: Tue, 8 Aug 2023 16:25:23 -0400 Subject: [PATCH 02/61] Show receipt image --- src/components/ReportActionItem/ReportPreview.js | 9 +++++++++ 1 file changed, 9 insertions(+) diff --git a/src/components/ReportActionItem/ReportPreview.js b/src/components/ReportActionItem/ReportPreview.js index d5d85df5e7ee..37731d76aaf2 100644 --- a/src/components/ReportActionItem/ReportPreview.js +++ b/src/components/ReportActionItem/ReportPreview.js @@ -25,6 +25,7 @@ import refPropTypes from '../refPropTypes'; import PressableWithoutFeedback from '../Pressable/PressableWithoutFeedback'; import themeColors from '../../styles/themes/default'; import reportPropTypes from '../../pages/reportPropTypes'; +import RenderHTML from '../RenderHTML'; const propTypes = { /** All the data of the action */ @@ -116,6 +117,10 @@ function ReportPreview(props) { const previewMessage = props.translate(ReportUtils.isSettled(props.iouReportID) || props.iouReport.isWaitingOnBankAccount ? 'iou.payerPaid' : 'iou.payerOwes', {payer: managerName}); const shouldShowSettlementButton = !_.isEmpty(props.iouReport) && isCurrentUserManager && !ReportUtils.isSettled(props.iouReportID) && !props.iouReport.isWaitingOnBankAccount && reportTotal !== 0; + + const receiptIDs = lodashGet(props.action, 'childLastReceiptTransactionIDs', '').split(','); + const receipts = _.filter(props.receipts, (transaction) => receiptIDs.includes(transaction.transactionID)); + return ( + `} /> @@ -179,6 +185,9 @@ export default compose( iouReport: { key: ({iouReportID}) => `${ONYXKEYS.COLLECTION.REPORT}${iouReportID}`, }, + receipts: { + key: ONYXKEYS.COLLECTION.TRANSACTION, + }, session: { key: ONYXKEYS.SESSION, }, From 04d0c3ba35f0753960ea7a6e9e25c5178f0d7c0b Mon Sep 17 00:00:00 2001 From: Andrew Li Date: Tue, 8 Aug 2023 17:21:28 -0400 Subject: [PATCH 03/61] Fix receipt image loading --- .../HTMLEngineProvider/HTMLRenderers/ImageRenderer.js | 8 ++++---- src/components/ReportActionItem/ReportPreview.js | 8 +++++++- src/libs/ReceiptUtils.js | 4 +++- src/pages/iou/ReceiptSelector/index.js | 2 +- 4 files changed, 15 insertions(+), 7 deletions(-) diff --git a/src/components/HTMLEngineProvider/HTMLRenderers/ImageRenderer.js b/src/components/HTMLEngineProvider/HTMLRenderers/ImageRenderer.js index 103e653d7d88..643785ab09d1 100644 --- a/src/components/HTMLEngineProvider/HTMLRenderers/ImageRenderer.js +++ b/src/components/HTMLEngineProvider/HTMLRenderers/ImageRenderer.js @@ -33,11 +33,11 @@ function ImageRenderer(props) { // Concierge responder attachments are uploaded to S3 without any access // control and thus require no authToken to verify access. // - const isAttachment = Boolean(htmlAttribs[CONST.ATTACHMENT_SOURCE_ATTRIBUTE]); + const isAttachmentOrReceipt = Boolean(htmlAttribs[CONST.ATTACHMENT_SOURCE_ATTRIBUTE]); // Files created/uploaded/hosted by App should resolve from API ROOT. Other URLs aren't modified const previewSource = tryResolveUrlFromApiRoot(htmlAttribs.src); - const source = tryResolveUrlFromApiRoot(isAttachment ? htmlAttribs[CONST.ATTACHMENT_SOURCE_ATTRIBUTE] : htmlAttribs.src); + const source = tryResolveUrlFromApiRoot(isAttachmentOrReceipt ? htmlAttribs[CONST.ATTACHMENT_SOURCE_ATTRIBUTE] : htmlAttribs.src); const imageWidth = htmlAttribs['data-expensify-width'] ? parseInt(htmlAttribs['data-expensify-width'], 10) : undefined; const imageHeight = htmlAttribs['data-expensify-height'] ? parseInt(htmlAttribs['data-expensify-height'], 10) : undefined; @@ -47,7 +47,7 @@ function ImageRenderer(props) { @@ -67,7 +67,7 @@ function ImageRenderer(props) { diff --git a/src/components/ReportActionItem/ReportPreview.js b/src/components/ReportActionItem/ReportPreview.js index 37731d76aaf2..3931e2248dcd 100644 --- a/src/components/ReportActionItem/ReportPreview.js +++ b/src/components/ReportActionItem/ReportPreview.js @@ -26,6 +26,7 @@ import PressableWithoutFeedback from '../Pressable/PressableWithoutFeedback'; import themeColors from '../../styles/themes/default'; import reportPropTypes from '../../pages/reportPropTypes'; import RenderHTML from '../RenderHTML'; +import * as ReceiptUtils from '../../libs/ReceiptUtils'; const propTypes = { /** All the data of the action */ @@ -134,7 +135,12 @@ function ReportPreview(props) { accessibilityRole="button" accessibilityLabel={props.translate('iou.viewDetails')} > - `} /> + + `} /> diff --git a/src/libs/ReceiptUtils.js b/src/libs/ReceiptUtils.js index b8ec54c0e899..42114914f084 100644 --- a/src/libs/ReceiptUtils.js +++ b/src/libs/ReceiptUtils.js @@ -25,4 +25,6 @@ const validateReceipt = (file) => { return true; }; -export default {validateReceipt}; +export { + validateReceipt, +}; diff --git a/src/pages/iou/ReceiptSelector/index.js b/src/pages/iou/ReceiptSelector/index.js index c674878c2c73..392e96887f8b 100644 --- a/src/pages/iou/ReceiptSelector/index.js +++ b/src/pages/iou/ReceiptSelector/index.js @@ -19,7 +19,7 @@ import Receipt from '../../../libs/actions/Receipt'; import useWindowDimensions from '../../../hooks/useWindowDimensions'; import useLocalize from '../../../hooks/useLocalize'; import {DragAndDropContext} from '../../../components/DragAndDrop/Provider'; -import ReceiptUtils from '../../../libs/ReceiptUtils'; +import * as ReceiptUtils from '../../../libs/ReceiptUtils'; const propTypes = { /** Information shown to the user when a receipt is not valid */ From 7466501076ff0380907e993999e7d7f17d92e533 Mon Sep 17 00:00:00 2001 From: Andrew Li Date: Tue, 8 Aug 2023 17:59:28 -0400 Subject: [PATCH 04/61] Begin styling for multiple receipts --- .../ReportActionItem/ReportPreview.js | 50 +++++++++++-------- src/styles/styles.js | 11 ++++ 2 files changed, 40 insertions(+), 21 deletions(-) diff --git a/src/components/ReportActionItem/ReportPreview.js b/src/components/ReportActionItem/ReportPreview.js index 3931e2248dcd..1519e4116d2e 100644 --- a/src/components/ReportActionItem/ReportPreview.js +++ b/src/components/ReportActionItem/ReportPreview.js @@ -135,29 +135,37 @@ function ReportPreview(props) { accessibilityRole="button" accessibilityLabel={props.translate('iou.viewDetails')} > - - `} /> - + - - {previewMessage} - - - - - {displayAmount} - {ReportUtils.isSettled(props.iouReportID) && ( - - ( + + - - )} + `} /> + + ))} + + + + + {previewMessage} + + + + + {displayAmount} + {ReportUtils.isSettled(props.iouReportID) && ( + + + + )} + {shouldShowSettlementButton && ( diff --git a/src/styles/styles.js b/src/styles/styles.js index f7517cd3acb7..4b2db3483d71 100644 --- a/src/styles/styles.js +++ b/src/styles/styles.js @@ -3645,6 +3645,17 @@ const styles = { borderRadius: 16, margin: 20, }, + + reportPreviewBox: { + backgroundColor: themeColors.cardBG, + borderRadius: variables.componentBorderRadiusLarge, + maxWidth: variables.sideBarWidth, + width: '100%', + }, + + reportPreviewBoxText: { + padding: 16, + } }; export default styles; From 941d730532ec090e4e4e2f5d91d0388f1ac63864 Mon Sep 17 00:00:00 2001 From: Andrew Li Date: Wed, 9 Aug 2023 09:29:09 -0400 Subject: [PATCH 05/61] Fit images to container --- .../HTMLEngineProvider/BaseHTMLEngineProvider.js | 2 +- .../HTMLEngineProvider/HTMLRenderers/ImageRenderer.js | 11 ++++++++--- src/components/ReportActionItem/ReportPreview.js | 7 ++++--- src/components/ThumbnailImage.js | 8 +++++++- src/styles/styles.js | 2 +- 5 files changed, 21 insertions(+), 9 deletions(-) diff --git a/src/components/HTMLEngineProvider/BaseHTMLEngineProvider.js b/src/components/HTMLEngineProvider/BaseHTMLEngineProvider.js index 5417f7af6820..e156c8bda3f4 100755 --- a/src/components/HTMLEngineProvider/BaseHTMLEngineProvider.js +++ b/src/components/HTMLEngineProvider/BaseHTMLEngineProvider.js @@ -48,7 +48,7 @@ const customHTMLElementModels = { 'mention-here': defaultHTMLElementModels.span.extend({tagName: 'mention-here'}), }; -const defaultViewProps = {style: [styles.alignItemsStart, styles.userSelectText]}; +const defaultViewProps = {style: [styles.alignItemsStart, styles.userSelectText, styles.w100, styles.h100]}; // We are using the explicit composite architecture for performance gains. // Configuration for RenderHTML is handled in a top-level component providing diff --git a/src/components/HTMLEngineProvider/HTMLRenderers/ImageRenderer.js b/src/components/HTMLEngineProvider/HTMLRenderers/ImageRenderer.js index 643785ab09d1..0ddd6c4657d7 100644 --- a/src/components/HTMLEngineProvider/HTMLRenderers/ImageRenderer.js +++ b/src/components/HTMLEngineProvider/HTMLRenderers/ImageRenderer.js @@ -43,19 +43,23 @@ function ImageRenderer(props) { const imageHeight = htmlAttribs['data-expensify-height'] ? parseInt(htmlAttribs['data-expensify-height'], 10) : undefined; const imagePreviewModalDisabled = htmlAttribs['data-expensify-preview-modal-disabled'] === 'true'; + const shouldFitContainer = htmlAttribs['data-expensify-fit-container'] === 'true'; + const fitContainerStyle = shouldFitContainer ? [styles.w100, styles.h100] : []; + return imagePreviewModalDisabled ? ( ) : ( {({anchor, report, action, checkIfContextMenuActive}) => ( { const route = ROUTES.getReportAttachmentRoute(report.reportID, source); Navigation.navigate(route); @@ -66,10 +70,11 @@ function ImageRenderer(props) { > )} diff --git a/src/components/ReportActionItem/ReportPreview.js b/src/components/ReportActionItem/ReportPreview.js index 1519e4116d2e..4011c325fb71 100644 --- a/src/components/ReportActionItem/ReportPreview.js +++ b/src/components/ReportActionItem/ReportPreview.js @@ -136,13 +136,14 @@ function ReportPreview(props) { accessibilityLabel={props.translate('iou.viewDetails')} > - + {_.map(receipts, ({receipt, transactionID}) => ( - + `} /> diff --git a/src/components/ThumbnailImage.js b/src/components/ThumbnailImage.js index 970227636e46..5c24cbd82bbc 100644 --- a/src/components/ThumbnailImage.js +++ b/src/components/ThumbnailImage.js @@ -25,6 +25,8 @@ const propTypes = { /** Height of the thumbnail image */ imageHeight: PropTypes.number, + shouldDynamicallyResize: PropTypes.bool, + ...windowDimensionsPropTypes, }; @@ -32,6 +34,7 @@ const defaultProps = { style: {}, imageWidth: 200, imageHeight: 200, + shouldDynamicallyResize: true, }; class ThumbnailImage extends PureComponent { @@ -91,9 +94,12 @@ class ThumbnailImage extends PureComponent { } render() { + const sizeStyles = this.props.shouldDynamicallyResize + ? [StyleUtils.getWidthAndHeightStyle(this.state.thumbnailWidth, this.state.thumbnailHeight)] + : [styles.w100, styles.h100]; return ( - + Date: Wed, 9 Aug 2023 11:53:46 -0400 Subject: [PATCH 06/61] Polish preview box on hover --- .../HTMLRenderers/ImageRenderer.js | 5 +-- .../MoneyRequestConfirmationList.js | 39 +------------------ .../ReportActionItem/ReportPreview.js | 36 +++++++++++------ src/libs/ReceiptUtils.js | 37 ++++++++++++++++++ src/styles/styles.js | 22 +++++++++++ 5 files changed, 87 insertions(+), 52 deletions(-) diff --git a/src/components/HTMLEngineProvider/HTMLRenderers/ImageRenderer.js b/src/components/HTMLEngineProvider/HTMLRenderers/ImageRenderer.js index 0ddd6c4657d7..9a7f02220068 100644 --- a/src/components/HTMLEngineProvider/HTMLRenderers/ImageRenderer.js +++ b/src/components/HTMLEngineProvider/HTMLRenderers/ImageRenderer.js @@ -44,12 +44,11 @@ function ImageRenderer(props) { const imagePreviewModalDisabled = htmlAttribs['data-expensify-preview-modal-disabled'] === 'true'; const shouldFitContainer = htmlAttribs['data-expensify-fit-container'] === 'true'; - const fitContainerStyle = shouldFitContainer ? [styles.w100, styles.h100] : []; return imagePreviewModalDisabled ? ( { - const {fileExtension} = FileUtils.splitExtensionFromFileName(receiptSource); - const isReceiptImage = Str.isImage(props.receiptSource); - - if (isReceiptImage) { - return receiptPath; - } - - if (fileExtension === CONST.IOU.FILE_TYPES.HTML) { - return ReceiptHTML; - } - - if (fileExtension === CONST.IOU.FILE_TYPES.DOC || fileExtension === CONST.IOU.FILE_TYPES.DOCX) { - return ReceiptDoc; - } - - if (fileExtension === CONST.IOU.FILE_TYPES.SVG) { - return ReceiptSVG; - } - - return ReceiptGeneric; - }; - return ( ) : ( - - {_.map(receipts, ({receipt, transactionID}) => ( - - - `} /> - - ))} + + {_.map(receipts, ({receipt, filename, transactionID}) => { + const thumbnailURI = ReceiptUtils.getImageURI(`${receipt.source.replace('.jpg', '')}.1024.jpg`, filename); + const uri = ReceiptUtils.getImageURI(receipt.source, filename); + + return ( + + {uri === receipt.source + ? + `} /> + : + } + + ); + })} diff --git a/src/libs/ReceiptUtils.js b/src/libs/ReceiptUtils.js index 42114914f084..fdb2e180bba2 100644 --- a/src/libs/ReceiptUtils.js +++ b/src/libs/ReceiptUtils.js @@ -1,9 +1,14 @@ import lodashGet from 'lodash/get'; import _ from 'underscore'; +import Str from 'expensify-common/lib/str'; import * as FileUtils from './fileDownload/FileUtils'; import CONST from '../CONST'; import Receipt from './actions/Receipt'; import * as Localize from './Localize'; +import ReceiptHTML from '../../assets/images/receipt-html.png'; +import ReceiptDoc from '../../assets/images/receipt-doc.png'; +import ReceiptGeneric from '../../assets/images/receipt-generic.png'; +import ReceiptSVG from '../../assets/images/receipt-svg.png'; const validateReceipt = (file) => { const {fileExtension} = FileUtils.splitExtensionFromFileName(lodashGet(file, 'name', '')); @@ -25,6 +30,38 @@ const validateReceipt = (file) => { return true; }; + +/** + * Grab the appropriate receipt image URI based on file type + * + * @param {String} path URI to image, i.e. blob://new.expensify.com/9ef3a018-4067-47c6-b29f-5f1bd35f213d or expensify.com/receipts/w_e616108497ef940b7210ec6beb5a462d01a878f4.jpg + * @param {String} filename of uploaded image or last part of remote URI + * @returns {*} + */ +const getImageURI = (path, filename) => { + const {fileExtension} = FileUtils.splitExtensionFromFileName(filename); + const isReceiptImage = Str.isImage(filename); + + if (isReceiptImage) { + return path; + } + + if (fileExtension === CONST.IOU.FILE_TYPES.HTML) { + return ReceiptHTML; + } + + if (fileExtension === CONST.IOU.FILE_TYPES.DOC || fileExtension === CONST.IOU.FILE_TYPES.DOCX) { + return ReceiptDoc; + } + + if (fileExtension === CONST.IOU.FILE_TYPES.SVG) { + return ReceiptSVG; + } + + return ReceiptGeneric; +}; + export { validateReceipt, + getImageURI, }; diff --git a/src/styles/styles.js b/src/styles/styles.js index dd4860047d2a..f5b2d883d845 100644 --- a/src/styles/styles.js +++ b/src/styles/styles.js @@ -3653,6 +3653,28 @@ const styles = { width: '100%', }, + reportPreviewBoxImages: { + flexDirection: 'row', + borderWidth: 2, + borderColor: themeColors.cardBG, + borderTopLeftRadius: variables.componentBorderRadiusLarge, + borderTopRightRadius: variables.componentBorderRadiusLarge, + overflow: 'hidden', + height: 200, + }, + + reportPreviewBoxImage: { + borderWidth: 1, + borderColor: themeColors.cardBG, + flex: 1, + width: '100%', + height: '100%', + }, + + reportPreviewBoxHoverBorder: { + borderColor: themeColors.border, + }, + reportPreviewBoxText: { padding: 16, }, From f0ced3a749768b051648b9efc7b9ebaf13ef7cab Mon Sep 17 00:00:00 2001 From: Andrew Li Date: Wed, 9 Aug 2023 12:01:40 -0400 Subject: [PATCH 07/61] Only apply full width when fitting container --- .../HTMLEngineProvider/HTMLRenderers/ImageRenderer.js | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/src/components/HTMLEngineProvider/HTMLRenderers/ImageRenderer.js b/src/components/HTMLEngineProvider/HTMLRenderers/ImageRenderer.js index 9a7f02220068..2f4ee7780346 100644 --- a/src/components/HTMLEngineProvider/HTMLRenderers/ImageRenderer.js +++ b/src/components/HTMLEngineProvider/HTMLRenderers/ImageRenderer.js @@ -44,6 +44,7 @@ function ImageRenderer(props) { const imagePreviewModalDisabled = htmlAttribs['data-expensify-preview-modal-disabled'] === 'true'; const shouldFitContainer = htmlAttribs['data-expensify-fit-container'] === 'true'; + const sizingStyle = shouldFitContainer ? [styles.w100, styles.h100] : []; return imagePreviewModalDisabled ? ( {({anchor, report, action, checkIfContextMenuActive}) => ( { const route = ROUTES.getReportAttachmentRoute(report.reportID, source); Navigation.navigate(route); From e6954010031970e42f2e736931f3af4828011e74 Mon Sep 17 00:00:00 2001 From: Andrew Li Date: Wed, 9 Aug 2023 13:14:03 -0400 Subject: [PATCH 08/61] Add scanning in progress --- src/CONST.js | 3 ++ .../ReportActionItem/ReportPreview.js | 29 +++++++++++++------ src/languages/en.js | 2 ++ src/languages/es.js | 2 ++ src/libs/ReceiptUtils.js | 13 ++++++--- 5 files changed, 36 insertions(+), 13 deletions(-) diff --git a/src/CONST.js b/src/CONST.js index 37a56b69f6d2..b2d5ff2b2956 100755 --- a/src/CONST.js +++ b/src/CONST.js @@ -1073,6 +1073,9 @@ const CONST = { AMOUNT_MAX_LENGTH: 10, RECEIPT_STATE: { SCANREADY: 'SCANREADY', + SCANNING: 'SCANNING', + SCANCOMPLETE: 'SCANCOMPLETE', + SCANFAILED: 'SCANFAILED', }, FILE_TYPES: { HTML: 'html', diff --git a/src/components/ReportActionItem/ReportPreview.js b/src/components/ReportActionItem/ReportPreview.js index 8ec7da4e57ff..4e57a806eb93 100644 --- a/src/components/ReportActionItem/ReportPreview.js +++ b/src/components/ReportActionItem/ReportPreview.js @@ -1,4 +1,4 @@ -import React from 'react'; +import React, {useMemo} from 'react'; import _ from 'underscore'; import {View} from 'react-native'; import PropTypes from 'prop-types'; @@ -99,9 +99,17 @@ function ReportPreview(props) { const managerID = props.iouReport.managerID || props.action.actorAccountID || 0; const isCurrentUserManager = managerID === lodashGet(props.session, 'accountID'); const reportTotal = ReportUtils.getMoneyRequestTotal(props.iouReport); + + const iouSettled = ReportUtils.isSettled(props.iouReportID); + const receiptIDs = lodashGet(props.action, 'childLastReceiptTransactionIDs', '').split(','); + const receipts = _.filter(props.receipts, (transaction) => receiptIDs.includes(transaction.transactionID)); + const isScanning = _.some(receipts, ({receipt}) => ReceiptUtils.isBeingScanned(receipt)); + let displayAmount; if (reportTotal) { displayAmount = CurrencyUtils.convertToDisplayString(reportTotal, props.iouReport.currency); + } else if (isScanning) { + displayAmount = props.translate('iou.receiptScanning'); } else { // If iouReport is not available, get amount from the action message (Ex: "Domain20821's Workspace owes $33.00" or "paid ₫60" or "paid -₫60 elsewhere") displayAmount = ''; @@ -114,14 +122,17 @@ function ReportPreview(props) { } } + let previewMessage; const managerName = ReportUtils.isPolicyExpenseChat(props.chatReport) ? ReportUtils.getPolicyName(props.chatReport) : ReportUtils.getDisplayNameForParticipant(managerID, true); + if (_.some(receipts, ({receipt}) => ReceiptUtils.isBeingScanned(receipt))) { + previewMessage = props.translate('iou.receipt'); + } else { + previewMessage = props.translate(iouSettled || props.iouReport.isWaitingOnBankAccount ? 'iou.payerPaid' : 'iou.payerOwes', {payer: managerName}); + } + const bankAccountRoute = ReportUtils.getBankAccountRoute(props.chatReport); - const previewMessage = props.translate(ReportUtils.isSettled(props.iouReportID) || props.iouReport.isWaitingOnBankAccount ? 'iou.payerPaid' : 'iou.payerOwes', {payer: managerName}); const shouldShowSettlementButton = - !_.isEmpty(props.iouReport) && isCurrentUserManager && !ReportUtils.isSettled(props.iouReportID) && !props.iouReport.isWaitingOnBankAccount && reportTotal !== 0; - - const receiptIDs = lodashGet(props.action, 'childLastReceiptTransactionIDs', '').split(','); - const receipts = _.filter(props.receipts, (transaction) => receiptIDs.includes(transaction.transactionID)); + !_.isEmpty(props.iouReport) && isCurrentUserManager && !iouSettled && !props.iouReport.isWaitingOnBankAccount && reportTotal !== 0; return ( @@ -139,18 +150,18 @@ function ReportPreview(props) { {_.map(receipts, ({receipt, filename, transactionID}) => { - const thumbnailURI = ReceiptUtils.getImageURI(`${receipt.source.replace('.jpg', '')}.1024.jpg`, filename); const uri = ReceiptUtils.getImageURI(receipt.source, filename); + const hasNoFallback = uri === receipt.source; return ( - {uri === receipt.source + {hasNoFallback ? diff --git a/src/languages/en.js b/src/languages/en.js index f532512dca1e..810b60d4ec5a 100755 --- a/src/languages/en.js +++ b/src/languages/en.js @@ -379,6 +379,8 @@ export default { pay: 'Pay', viewDetails: 'View details', pending: 'Pending', + receipt: 'Receipt', + receiptScanning: 'Scanning in progress...', settledExpensify: 'Paid', settledElsewhere: 'Paid elsewhere', settledPaypalMe: 'Paid using Paypal.me', diff --git a/src/languages/es.js b/src/languages/es.js index 4cdfc4e32f14..656723c59ab0 100644 --- a/src/languages/es.js +++ b/src/languages/es.js @@ -378,6 +378,8 @@ export default { pay: 'Pagar', viewDetails: 'Ver detalles', pending: 'Pendiente', + receipt: 'Recibo', + receiptScanning: 'Escaneo en progreso...', settledExpensify: 'Pagado', settledElsewhere: 'Pagado de otra forma', settledPaypalMe: 'Pagado con PayPal.me', diff --git a/src/libs/ReceiptUtils.js b/src/libs/ReceiptUtils.js index fdb2e180bba2..9106e9af2be7 100644 --- a/src/libs/ReceiptUtils.js +++ b/src/libs/ReceiptUtils.js @@ -10,7 +10,7 @@ import ReceiptDoc from '../../assets/images/receipt-doc.png'; import ReceiptGeneric from '../../assets/images/receipt-generic.png'; import ReceiptSVG from '../../assets/images/receipt-svg.png'; -const validateReceipt = (file) => { +function validateReceipt(file) { const {fileExtension} = FileUtils.splitExtensionFromFileName(lodashGet(file, 'name', '')); if (_.contains(CONST.API_ATTACHMENT_VALIDATIONS.UNALLOWED_EXTENSIONS, fileExtension.toLowerCase())) { Receipt.setUploadReceiptError(true, Localize.translateLocal('attachmentPicker.wrongFileType'), Localize.translateLocal('attachmentPicker.notAllowedExtension')); @@ -34,11 +34,11 @@ const validateReceipt = (file) => { /** * Grab the appropriate receipt image URI based on file type * - * @param {String} path URI to image, i.e. blob://new.expensify.com/9ef3a018-4067-47c6-b29f-5f1bd35f213d or expensify.com/receipts/w_e616108497ef940b7210ec6beb5a462d01a878f4.jpg - * @param {String} filename of uploaded image or last part of remote URI + * @param {String} path URI to image, i.e. blob://new.expensify.com/9ef3a018-4067-47c6-b29f-5f1bd35f213d or expensify.com/receipts/w_e616108497ef940b7210ec6beb5a462d01a878f4.jpg + * @param {String} filename of uploaded image or last part of remote URI * @returns {*} */ -const getImageURI = (path, filename) => { +function getImageURI(path, filename) { const {fileExtension} = FileUtils.splitExtensionFromFileName(filename); const isReceiptImage = Str.isImage(filename); @@ -61,7 +61,12 @@ const getImageURI = (path, filename) => { return ReceiptGeneric; }; +function isBeingScanned(receipt) { + return receipt.state === CONST.IOU.RECEIPT_STATE.SCANREADY || receipt.state === CONST.IOU.RECEIPT_STATE.SCANNING; +} + export { validateReceipt, getImageURI, + isBeingScanned, }; From 4ae0c8960bfc534d346cfca821aaeaaf52f57e75 Mon Sep 17 00:00:00 2001 From: Andrew Li Date: Wed, 9 Aug 2023 15:51:24 -0400 Subject: [PATCH 09/61] Fix thumbnail and add correct optimistic filename --- src/components/ReportActionItem/ReportPreview.js | 2 +- src/libs/TransactionUtils.js | 3 ++- src/libs/actions/IOU.js | 2 +- src/libs/fileDownload/FileUtils.js | 4 +++- 4 files changed, 7 insertions(+), 4 deletions(-) diff --git a/src/components/ReportActionItem/ReportPreview.js b/src/components/ReportActionItem/ReportPreview.js index 4e57a806eb93..6e40f32c35a0 100644 --- a/src/components/ReportActionItem/ReportPreview.js +++ b/src/components/ReportActionItem/ReportPreview.js @@ -161,7 +161,7 @@ function ReportPreview(props) { {hasNoFallback ? diff --git a/src/libs/TransactionUtils.js b/src/libs/TransactionUtils.js index f88f53467ae8..3e89d0fe87ef 100644 --- a/src/libs/TransactionUtils.js +++ b/src/libs/TransactionUtils.js @@ -15,7 +15,7 @@ import * as NumberUtils from './NumberUtils'; * @param {Object} [receipt] * @returns {Object} */ -function buildOptimisticTransaction(amount, currency, reportID, comment = '', source = '', originalTransactionID = '', merchant = CONST.REPORT.TYPE.IOU, receipt = {}) { +function buildOptimisticTransaction(amount, currency, reportID, comment = '', source = '', originalTransactionID = '', merchant = CONST.REPORT.TYPE.IOU, receipt = {}, filename = '') { // transactionIDs are random, positive, 64-bit numeric strings. // Because JS can only handle 53-bit numbers, transactionIDs are strings in the front-end (just like reportActionID) const transactionID = NumberUtils.rand64(); @@ -38,6 +38,7 @@ function buildOptimisticTransaction(amount, currency, reportID, comment = '', so created: DateUtils.getDBTime(), pendingAction: CONST.RED_BRICK_ROAD_PENDING_ACTION.ADD, receipt, + filename, }; } diff --git a/src/libs/actions/IOU.js b/src/libs/actions/IOU.js index 45fcd35eb839..65a8315f05bf 100644 --- a/src/libs/actions/IOU.js +++ b/src/libs/actions/IOU.js @@ -363,7 +363,7 @@ function requestMoney(report, amount, currency, payeeEmail, payeeAccountID, part receiptObject.source = receipt.source; receiptObject.state = CONST.IOU.RECEIPT_STATE.SCANREADY; } - const optimisticTransaction = TransactionUtils.buildOptimisticTransaction(amount, currency, iouReport.reportID, comment, '', '', undefined, receiptObject); + const optimisticTransaction = TransactionUtils.buildOptimisticTransaction(amount, currency, iouReport.reportID, comment, '', '', undefined, receiptObject, receipt.filename); // STEP 4: Build optimistic reportActions. We need: // 1. CREATED action for the chatReport diff --git a/src/libs/fileDownload/FileUtils.js b/src/libs/fileDownload/FileUtils.js index 4d714fff4f24..d194e7d6cdeb 100644 --- a/src/libs/fileDownload/FileUtils.js +++ b/src/libs/fileDownload/FileUtils.js @@ -152,8 +152,10 @@ const readFileAsync = (path, fileName) => return res.blob(); }) .then((blob) => { - const file = new File([blob], cleanFileName(fileName)); + const cleanName = cleanFileName(fileName) + const file = new File([blob], cleanName); file.source = path; + file.filename = cleanName; resolve(file); }) .catch((e) => { From 5140fede8076c1469f280d44e4d54b7406fe8ce3 Mon Sep 17 00:00:00 2001 From: Andrew Li Date: Wed, 9 Aug 2023 23:47:51 -0400 Subject: [PATCH 10/61] Fix filename when not present --- src/components/ReportActionItem/ReportPreview.js | 6 +++--- src/libs/actions/IOU.js | 4 +++- 2 files changed, 6 insertions(+), 4 deletions(-) diff --git a/src/components/ReportActionItem/ReportPreview.js b/src/components/ReportActionItem/ReportPreview.js index 6e40f32c35a0..7d30900bed62 100644 --- a/src/components/ReportActionItem/ReportPreview.js +++ b/src/components/ReportActionItem/ReportPreview.js @@ -147,8 +147,8 @@ function ReportPreview(props) { accessibilityRole="button" accessibilityLabel={props.translate('iou.viewDetails')} > - - + + {_.map(receipts, ({receipt, filename, transactionID}) => { const uri = ReceiptUtils.getImageURI(receipt.source, filename); const hasNoFallback = uri === receipt.source; @@ -156,7 +156,7 @@ function ReportPreview(props) { return ( {hasNoFallback ? Date: Thu, 10 Aug 2023 00:30:26 -0400 Subject: [PATCH 11/61] Rename IOUPreview to MoneyRequestPreview --- src/ONYXKEYS.js | 2 +- .../ReportActionItem/MoneyRequestAction.js | 10 +++++----- .../{IOUPreview.js => MoneyRequestPreview.js} | 14 +++++++------- src/components/ReportActionItem/ReportPreview.js | 2 +- src/libs/actions/IOU.js | 2 +- src/pages/home/report/ReportActionItem.js | 2 +- src/styles/styles.js | 8 ++++---- 7 files changed, 20 insertions(+), 20 deletions(-) rename src/components/ReportActionItem/{IOUPreview.js => MoneyRequestPreview.js} (96%) diff --git a/src/ONYXKEYS.js b/src/ONYXKEYS.js index 4a255538c786..f60ca9f3120f 100755 --- a/src/ONYXKEYS.js +++ b/src/ONYXKEYS.js @@ -33,7 +33,7 @@ export default { // Credentials to authenticate the user CREDENTIALS: 'credentials', - // Contains loading data for the IOU feature (MoneyRequestModal, IOUDetail, & IOUPreview Components) + // Contains loading data for the IOU feature (MoneyRequestModal, IOUDetail, & MoneyRequestPreview Components) IOU: 'iou', // Keeps track if there is modal currently visible or not diff --git a/src/components/ReportActionItem/MoneyRequestAction.js b/src/components/ReportActionItem/MoneyRequestAction.js index 5790e55b2c78..9bb342a6b735 100644 --- a/src/components/ReportActionItem/MoneyRequestAction.js +++ b/src/components/ReportActionItem/MoneyRequestAction.js @@ -10,7 +10,7 @@ import compose from '../../libs/compose'; import reportActionPropTypes from '../../pages/home/report/reportActionPropTypes'; import networkPropTypes from '../networkPropTypes'; import iouReportPropTypes from '../../pages/iouReportPropTypes'; -import IOUPreview from './IOUPreview'; +import MoneyRequestPreview from './MoneyRequestPreview'; import Navigation from '../../libs/Navigation/Navigation'; import ROUTES from '../../ROUTES'; import styles from '../../styles/styles'; @@ -87,7 +87,7 @@ const defaultProps = { function MoneyRequestAction(props) { const isSplitBillAction = lodashGet(props.action, 'originalMessage.type', '') === CONST.IOU.REPORT_ACTION_TYPE.SPLIT; - const onIOUPreviewPressed = () => { + const onMoneyRequestPreviewPressed = () => { if (isSplitBillAction) { const reportActionID = lodashGet(props.action, 'reportActionID', '0'); Navigation.navigate(ROUTES.getSplitBillDetailsRoute(props.chatReportID, reportActionID)); @@ -141,7 +141,7 @@ function MoneyRequestAction(props) { return isDeletedParentAction ? ( ${props.translate('parentReportAction.deletedRequest')}`} /> ) : ( - ); diff --git a/src/components/ReportActionItem/IOUPreview.js b/src/components/ReportActionItem/MoneyRequestPreview.js similarity index 96% rename from src/components/ReportActionItem/IOUPreview.js rename to src/components/ReportActionItem/MoneyRequestPreview.js index 85a0b22ac327..5488df581335 100644 --- a/src/components/ReportActionItem/IOUPreview.js +++ b/src/components/ReportActionItem/MoneyRequestPreview.js @@ -124,7 +124,7 @@ const defaultProps = { shouldShowPendingConversionMessage: false, }; -function IOUPreview(props) { +function MoneyRequestPreview(props) { if (_.isEmpty(props.iouReport) && !props.isBillSplit) { return null; } @@ -186,7 +186,7 @@ function IOUPreview(props) { errorRowStyles={[styles.mbn1]} needsOffscreenAlphaCompositing > - + {getPreviewHeaderText()} @@ -216,7 +216,7 @@ function IOUPreview(props) { )} {props.isBillSplit && ( - + - + {_.map(receipts, ({receipt, filename, transactionID}) => { const uri = ReceiptUtils.getImageURI(receipt.source, filename); diff --git a/src/libs/actions/IOU.js b/src/libs/actions/IOU.js index 0f8ac015d5a1..85022a503071 100644 --- a/src/libs/actions/IOU.js +++ b/src/libs/actions/IOU.js @@ -809,7 +809,7 @@ function deleteMoneyRequest(transactionID, reportAction, isSingleTransactionView // STEP 2: Decide if we need to: // 1. Delete the transactionThread - delete if there are no visible comments in the thread - // 2. Update the iouPreview to show [Deleted request] - update if the transactionThread exists AND it isn't being deleted + // 2. Update the moneyRequestPreview to show [Deleted request] - update if the transactionThread exists AND it isn't being deleted const shouldDeleteTransactionThread = transactionThreadID ? ReportActionsUtils.getLastVisibleMessage(transactionThreadID).lastMessageText.length === 0 : false; const shouldShowDeletedRequestMessage = transactionThreadID && !shouldDeleteTransactionThread; diff --git a/src/pages/home/report/ReportActionItem.js b/src/pages/home/report/ReportActionItem.js index 05e57c592b9f..b25efda7ebc8 100644 --- a/src/pages/home/report/ReportActionItem.js +++ b/src/pages/home/report/ReportActionItem.js @@ -238,7 +238,7 @@ function ReportActionItem(props) { // IOUDetails only exists when we are sending money const isSendingMoney = originalMessage.type === CONST.IOU.REPORT_ACTION_TYPE.PAY && _.has(originalMessage, 'IOUDetails'); - // Show the IOUPreview for when request was created, bill was split or money was sent + // Show the MoneyRequestPreview for when request was created, bill was split or money was sent if ( props.action.actionName === CONST.REPORT.ACTIONS.TYPE.IOU && originalMessage && diff --git a/src/styles/styles.js b/src/styles/styles.js index f5b2d883d845..42c75f510435 100644 --- a/src/styles/styles.js +++ b/src/styles/styles.js @@ -2640,7 +2640,7 @@ const styles = { maxWidth: variables.sideBarWidth, }, - iouPreviewBox: { + moneyRequestPreviewBox: { backgroundColor: themeColors.cardBG, borderRadius: variables.componentBorderRadiusLarge, padding: 16, @@ -2648,11 +2648,11 @@ const styles = { width: '100%', }, - iouPreviewBoxHover: { + moneyRequestPreviewBoxHover: { backgroundColor: themeColors.border, }, - iouPreviewBoxLoading: { + moneyRequestPreviewBoxLoading: { // When a new IOU request arrives it is very briefly in a loading state, so set the minimum height of the container to 94 to match the rendered height after loading. // Otherwise, the IOU request pay button will not be fully visible and the user will have to scroll up to reveal the entire IOU request container. // See https://github.com/Expensify/App/issues/10283. @@ -2660,7 +2660,7 @@ const styles = { width: '100%', }, - iouPreviewBoxAvatar: { + moneyRequestPreviewBoxAvatar: { marginRight: -10, marginBottom: 0, }, From 3f7acbeb98382dba26fb5e9c89384c0d1b310c4a Mon Sep 17 00:00:00 2001 From: Andrew Li Date: Thu, 10 Aug 2023 14:44:03 -0400 Subject: [PATCH 12/61] Implement whispers --- .../ReportActionItem/ReportPreview.js | 7 +- src/libs/ReportActionsUtils.js | 64 +++++++++++++++++++ src/libs/TransactionUtils.js | 12 +++- src/libs/actions/IOU.js | 2 +- src/pages/home/report/ReportActionItem.js | 2 +- 5 files changed, 79 insertions(+), 8 deletions(-) diff --git a/src/components/ReportActionItem/ReportPreview.js b/src/components/ReportActionItem/ReportPreview.js index 6475ddc99b2c..b2f6822d9585 100644 --- a/src/components/ReportActionItem/ReportPreview.js +++ b/src/components/ReportActionItem/ReportPreview.js @@ -27,6 +27,7 @@ import themeColors from '../../styles/themes/default'; import reportPropTypes from '../../pages/reportPropTypes'; import RenderHTML from '../RenderHTML'; import * as ReceiptUtils from '../../libs/ReceiptUtils'; +import * as ReportActionUtils from '../../libs/ReportActionsUtils'; import Image from '../Image'; const propTypes = { @@ -101,8 +102,7 @@ function ReportPreview(props) { const reportTotal = ReportUtils.getMoneyRequestTotal(props.iouReport); const iouSettled = ReportUtils.isSettled(props.iouReportID); - const receiptIDs = lodashGet(props.action, 'childLastReceiptTransactionIDs', '').split(','); - const receipts = _.filter(props.receipts, (transaction) => receiptIDs.includes(transaction.transactionID)); + const receipts = ReportActionUtils.getReportPreviewTransactions(props.action); const isScanning = _.some(receipts, ({receipt}) => ReceiptUtils.isBeingScanned(receipt)); let displayAmount; @@ -223,9 +223,6 @@ export default compose( iouReport: { key: ({iouReportID}) => `${ONYXKEYS.COLLECTION.REPORT}${iouReportID}`, }, - receipts: { - key: ONYXKEYS.COLLECTION.TRANSACTION, - }, session: { key: ONYXKEYS.SESSION, }, diff --git a/src/libs/ReportActionsUtils.js b/src/libs/ReportActionsUtils.js index 3cd5621e5e32..4fe5f1929fcc 100644 --- a/src/libs/ReportActionsUtils.js +++ b/src/libs/ReportActionsUtils.js @@ -10,6 +10,8 @@ import ONYXKEYS from '../ONYXKEYS'; import Log from './Log'; import * as CurrencyUtils from './CurrencyUtils'; import isReportMessageAttachment from './isReportMessageAttachment'; +import * as TransactionUtils from './TransactionUtils'; +import * as ReceiptUtils from './ReceiptUtils'; const allReports = {}; Onyx.connect({ @@ -37,6 +39,19 @@ Onyx.connect({ }, }); +const allTransactions = {}; +Onyx.connect({ + key: ONYXKEYS.COLLECTION.TRANSACTION, + callback: (actions, key) => { + if (!key || !actions) { + return; + } + + const transactionID = CollectionUtils.extractCollectionItemID(key); + allTransactions[transactionID] = actions; + }, +}); + let isNetworkOffline = false; Onyx.connect({ key: ONYXKEYS.NETWORK, @@ -561,6 +576,53 @@ function isMessageDeleted(reportAction) { return lodashGet(reportAction, ['message', 0, 'isDeletedParentAction'], false); } +/** + * Get the transactions related to a report preview + * + * @param {Object} reportPreviewAction + * @returns {Object} + */ +function getReportPreviewTransactions(reportPreviewAction) { + const transactionIDs = lodashGet(reportPreviewAction, ['childLastReceiptTransactionIDs'], '').split(','); + return _.reduce(transactionIDs, (transactions, transactionID) => { + const transaction = allTransactions[transactionID]; + if (transaction) { + transactions.push(transaction); + } + return transactions; + }, []); +} + +/** + * @param {Object} iouReportAction + * @returns {Object} + */ +function getTransaction(iouReportAction) { + const transactionID = lodashGet(iouReportAction, ['originalMessage', 'IOUTransactionID']); + return allTransactions[transactionID] || {}; +} + + +/** + * Checks if the IOU or expense report has either no smartscanned receipts or at least one is already done scanning + * + * @param {Object|null} reportAction + * @returns {Boolean} + */ +function hasReadyMoneyRequests(reportAction) { + if (isReportPreviewAction(reportAction)) { + const transactions = getReportPreviewTransactions(reportAction); + return _.some(transactions, (transaction) => !TransactionUtils.hasReceipt(transaction) || !ReceiptUtils.isBeingScanned(transaction.receipt)); + } + + if (isMoneyRequestAction(reportAction)) { + const transaction = getTransaction(reportAction); + return !TransactionUtils.hasReceipt(transaction) || !ReceiptUtils.isBeingScanned(transaction.receipt); + } + + return true; +} + export { getSortedReportActions, getLastVisibleAction, @@ -593,4 +655,6 @@ export { isWhisperAction, isPendingRemove, getReportAction, + getReportPreviewTransactions, + hasReadyMoneyRequests, }; diff --git a/src/libs/TransactionUtils.js b/src/libs/TransactionUtils.js index 3e89d0fe87ef..71881236696f 100644 --- a/src/libs/TransactionUtils.js +++ b/src/libs/TransactionUtils.js @@ -1,3 +1,4 @@ +import _ from 'underscore'; import CONST from '../CONST'; import DateUtils from './DateUtils'; import * as NumberUtils from './NumberUtils'; @@ -42,6 +43,15 @@ function buildOptimisticTransaction(amount, currency, reportID, comment = '', so }; } -export default { +/** + * @param {Object|null} transaction + * @returns {Boolean} + */ +function hasReceipt(transaction) { + return _.has(transaction, 'receipt'); +} + +export { buildOptimisticTransaction, + hasReceipt, }; diff --git a/src/libs/actions/IOU.js b/src/libs/actions/IOU.js index 85022a503071..fda08ab912df 100644 --- a/src/libs/actions/IOU.js +++ b/src/libs/actions/IOU.js @@ -16,7 +16,7 @@ import * as ReportActionsUtils from '../ReportActionsUtils'; import * as IOUUtils from '../IOUUtils'; import * as OptionsListUtils from '../OptionsListUtils'; import DateUtils from '../DateUtils'; -import TransactionUtils from '../TransactionUtils'; +import * as TransactionUtils from '../TransactionUtils'; import * as ErrorUtils from '../ErrorUtils'; import * as UserUtils from '../UserUtils'; import * as Report from './Report'; diff --git a/src/pages/home/report/ReportActionItem.js b/src/pages/home/report/ReportActionItem.js index b25efda7ebc8..42a9998e0ae1 100644 --- a/src/pages/home/report/ReportActionItem.js +++ b/src/pages/home/report/ReportActionItem.js @@ -495,7 +495,7 @@ function ReportActionItem(props) { const hasErrors = !_.isEmpty(props.action.errors); const whisperedToAccountIDs = props.action.whisperedToAccountIDs || []; - const isWhisper = whisperedToAccountIDs.length > 0; + const isWhisper = whisperedToAccountIDs.length > 0 || !ReportActionsUtils.hasReadyMoneyRequests(props.action); const isMultipleParticipant = whisperedToAccountIDs.length > 1; const isWhisperOnlyVisibleByUser = isWhisper && ReportUtils.isCurrentUserTheOnlyParticipant(whisperedToAccountIDs); const whisperedToPersonalDetails = isWhisper ? _.filter(props.personalDetailsList, (details) => _.includes(whisperedToAccountIDs, details.accountID)) : []; From 0f25a34ba31477a72d92d91d45e26f6c79486343 Mon Sep 17 00:00:00 2001 From: Andrew Li Date: Thu, 10 Aug 2023 15:34:35 -0400 Subject: [PATCH 13/61] Update MoneyRequestPreview for receipts --- .../ReportActionItem/MoneyRequestPreview.js | 140 ++++++++++++------ .../ReportActionItem/ReportPreview.js | 6 +- src/libs/ReportActionsUtils.js | 1 + src/libs/TransactionUtils.js | 2 +- src/styles/styles.js | 19 ++- 5 files changed, 114 insertions(+), 54 deletions(-) diff --git a/src/components/ReportActionItem/MoneyRequestPreview.js b/src/components/ReportActionItem/MoneyRequestPreview.js index 5488df581335..0932ef47dfb5 100644 --- a/src/components/ReportActionItem/MoneyRequestPreview.js +++ b/src/components/ReportActionItem/MoneyRequestPreview.js @@ -28,6 +28,11 @@ import * as IOUUtils from '../../libs/IOUUtils'; import * as ReportUtils from '../../libs/ReportUtils'; import refPropTypes from '../refPropTypes'; import PressableWithFeedback from '../Pressable/PressableWithoutFeedback'; +import * as ReportActionUtils from '../../libs/ReportActionsUtils'; +import * as TransactionUtils from '../../libs/TransactionUtils'; +import * as ReceiptUtils from '../../libs/ReceiptUtils'; +import RenderHTML from '../RenderHTML'; +import Image from '../Image'; const propTypes = { /** The active IOUReport, used for Onyx subscription */ @@ -143,6 +148,10 @@ function MoneyRequestPreview(props) { const requestCurrency = moneyRequestAction.currency; const requestComment = moneyRequestAction.comment.trim(); + const transaction = ReportActionUtils.getTransaction(props.action); + const hasReceipt = TransactionUtils.hasReceipt(transaction); + const isScanning = !ReportActionUtils.hasReadyMoneyRequests(props.action); + const getSettledMessage = () => { switch (lodashGet(props.action, 'originalMessage.paymentType', '')) { case CONST.IOU.PAYMENT_TYPE.PAYPAL_ME: @@ -161,6 +170,10 @@ function MoneyRequestPreview(props) { }; const getPreviewHeaderText = () => { + if (isScanning) { + return props.translate('iou.receipt'); + } + if (props.isBillSplit) { return props.translate('iou.split'); } @@ -174,6 +187,32 @@ function MoneyRequestPreview(props) { return message; }; + const getDisplayAmountText = () => { + if (isScanning) { + return props.translate('iou.receiptScanning'); + } + + return CurrencyUtils.convertToDisplayString(requestAmount, requestCurrency); + } + + const renderReceipt = ({receipt, filename}) => { + const uri = ReceiptUtils.getImageURI(receipt.source, filename); + const hasNoFallback = uri === receipt.source; + + if (hasNoFallback) { + return ( + + `} /> + ); + } + return (); + } + const childContainer = ( - - - - {getPreviewHeaderText()} - {Boolean(getSettledMessage()) && ( - <> - - {getSettledMessage()} - - )} + + {hasReceipt && ( + + {renderReceipt(transaction)} - - - - {CurrencyUtils.convertToDisplayString(requestAmount, requestCurrency)} - {ReportUtils.isSettled(props.iouReport.reportID) && !props.isBillSplit && ( - - + + + {getPreviewHeaderText()} + {Boolean(getSettledMessage()) && ( + <> + + {getSettledMessage()} + + )} + + + + + {getDisplayAmountText()} + {ReportUtils.isSettled(props.iouReport.reportID) && !props.isBillSplit && ( + + + + )} + + {props.isBillSplit && ( + + )} - {props.isBillSplit && ( - - + + + {!isCurrentUserManager && props.shouldShowPendingConversionMessage && ( + {props.translate('iou.pendingConversionMessage')} + )} + {!_.isEmpty(requestComment) && {requestComment}} - )} - - - - {!isCurrentUserManager && props.shouldShowPendingConversionMessage && ( - {props.translate('iou.pendingConversionMessage')} + {props.isBillSplit && !_.isEmpty(participantAccountIDs) && ( + + {props.translate('iou.amountEach', { + amount: CurrencyUtils.convertToDisplayString(IOUUtils.calculateAmount(participantAccountIDs.length - 1, requestAmount), requestCurrency), + })} + )} - {!_.isEmpty(requestComment) && {requestComment}} - {props.isBillSplit && !_.isEmpty(participantAccountIDs) && ( - - {props.translate('iou.amountEach', { - amount: CurrencyUtils.convertToDisplayString(IOUUtils.calculateAmount(participantAccountIDs.length - 1, requestAmount), requestCurrency), - })} - - )} diff --git a/src/components/ReportActionItem/ReportPreview.js b/src/components/ReportActionItem/ReportPreview.js index b2f6822d9585..a2010ccecf71 100644 --- a/src/components/ReportActionItem/ReportPreview.js +++ b/src/components/ReportActionItem/ReportPreview.js @@ -103,7 +103,7 @@ function ReportPreview(props) { const iouSettled = ReportUtils.isSettled(props.iouReportID); const receipts = ReportActionUtils.getReportPreviewTransactions(props.action); - const isScanning = _.some(receipts, ({receipt}) => ReceiptUtils.isBeingScanned(receipt)); + const isScanning = !ReportActionUtils.hasReadyMoneyRequests(props.action); let displayAmount; if (reportTotal) { @@ -148,7 +148,7 @@ function ReportPreview(props) { accessibilityLabel={props.translate('iou.viewDetails')} > - + {_.map(receipts, ({receipt, filename, transactionID}) => { const uri = ReceiptUtils.getImageURI(receipt.source, filename); const hasNoFallback = uri === receipt.source; @@ -156,7 +156,7 @@ function ReportPreview(props) { return ( {hasNoFallback ? Date: Thu, 10 Aug 2023 17:43:25 -0400 Subject: [PATCH 14/61] Refactor thumbnail/image for receipts --- .../MoneyRequestConfirmationList.js | 2 +- .../ReportActionItem/MoneyRequestPreview.js | 13 +++++----- .../ReportActionItem/ReportPreview.js | 12 ++++------ src/libs/ReceiptUtils.js | 24 +++++++++++-------- src/libs/actions/IOU.js | 2 +- src/libs/fileDownload/FileUtils.js | 1 - src/pages/iou/ReceiptSelector/index.js | 3 +-- 7 files changed, 28 insertions(+), 29 deletions(-) diff --git a/src/components/MoneyRequestConfirmationList.js b/src/components/MoneyRequestConfirmationList.js index 5b12c8da4052..94b7a53dc795 100755 --- a/src/components/MoneyRequestConfirmationList.js +++ b/src/components/MoneyRequestConfirmationList.js @@ -332,7 +332,7 @@ function MoneyRequestConfirmationList(props) { {!_.isEmpty(props.receiptPath) ? ( ) : ( { - const uri = ReceiptUtils.getImageURI(receipt.source, filename); - const hasNoFallback = uri === receipt.source; - - if (hasNoFallback) { + const {thumbnail, image} = ReceiptUtils.getThumbnailAndImageURIs(receipt.source, filename); + console.log(thumbnail, image); + if (thumbnail) { return ( `} /> ); } - return (); + return (); } const childContainer = ( diff --git a/src/components/ReportActionItem/ReportPreview.js b/src/components/ReportActionItem/ReportPreview.js index a2010ccecf71..0abb6d824f91 100644 --- a/src/components/ReportActionItem/ReportPreview.js +++ b/src/components/ReportActionItem/ReportPreview.js @@ -150,23 +150,21 @@ function ReportPreview(props) { {_.map(receipts, ({receipt, filename, transactionID}) => { - const uri = ReceiptUtils.getImageURI(receipt.source, filename); - const hasNoFallback = uri === receipt.source; - + const {thumbnail, image} = ReceiptUtils.getThumbnailAndImageURIs(receipt.source, filename); return ( - {hasNoFallback + {thumbnail ? `} /> - : + : } ); diff --git a/src/libs/ReceiptUtils.js b/src/libs/ReceiptUtils.js index 9106e9af2be7..9dd83ed48e87 100644 --- a/src/libs/ReceiptUtils.js +++ b/src/libs/ReceiptUtils.js @@ -32,33 +32,37 @@ function validateReceipt(file) { /** - * Grab the appropriate receipt image URI based on file type + * Grab the appropriate receipt image and thumbnail URIs based on file type * * @param {String} path URI to image, i.e. blob://new.expensify.com/9ef3a018-4067-47c6-b29f-5f1bd35f213d or expensify.com/receipts/w_e616108497ef940b7210ec6beb5a462d01a878f4.jpg * @param {String} filename of uploaded image or last part of remote URI - * @returns {*} + * @returns {Object} */ -function getImageURI(path, filename) { +function getThumbnailAndImageURIs(path, filename) { + if (path.startsWith('blob://')) { + return {thumbnail: null, image: path}; + } + const {fileExtension} = FileUtils.splitExtensionFromFileName(filename); const isReceiptImage = Str.isImage(filename); if (isReceiptImage) { - return path; + return {thumbnail: `${path}.1024.jpg`, image: path}; } + let image = ReceiptGeneric; if (fileExtension === CONST.IOU.FILE_TYPES.HTML) { - return ReceiptHTML; + image = ReceiptHTML; } if (fileExtension === CONST.IOU.FILE_TYPES.DOC || fileExtension === CONST.IOU.FILE_TYPES.DOCX) { - return ReceiptDoc; + image = ReceiptDoc; } if (fileExtension === CONST.IOU.FILE_TYPES.SVG) { - return ReceiptSVG; + image = ReceiptSVG; } - - return ReceiptGeneric; + return {thumbnail: null, image}; }; function isBeingScanned(receipt) { @@ -67,6 +71,6 @@ function isBeingScanned(receipt) { export { validateReceipt, - getImageURI, + getThumbnailAndImageURIs, isBeingScanned, }; diff --git a/src/libs/actions/IOU.js b/src/libs/actions/IOU.js index fda08ab912df..6b9190c999c9 100644 --- a/src/libs/actions/IOU.js +++ b/src/libs/actions/IOU.js @@ -363,7 +363,7 @@ function requestMoney(report, amount, currency, payeeEmail, payeeAccountID, part if (receipt && receipt.source) { receiptObject.source = receipt.source; receiptObject.state = CONST.IOU.RECEIPT_STATE.SCANREADY; - filename = receipt.filename; + filename = receipt.name; } const optimisticTransaction = TransactionUtils.buildOptimisticTransaction(amount, currency, iouReport.reportID, comment, '', '', undefined, receiptObject, filename); diff --git a/src/libs/fileDownload/FileUtils.js b/src/libs/fileDownload/FileUtils.js index d194e7d6cdeb..962923241b62 100644 --- a/src/libs/fileDownload/FileUtils.js +++ b/src/libs/fileDownload/FileUtils.js @@ -155,7 +155,6 @@ const readFileAsync = (path, fileName) => const cleanName = cleanFileName(fileName) const file = new File([blob], cleanName); file.source = path; - file.filename = cleanName; resolve(file); }) .catch((e) => { diff --git a/src/pages/iou/ReceiptSelector/index.js b/src/pages/iou/ReceiptSelector/index.js index 392e96887f8b..b693b299eac3 100644 --- a/src/pages/iou/ReceiptSelector/index.js +++ b/src/pages/iou/ReceiptSelector/index.js @@ -99,8 +99,7 @@ function ReceiptSelector(props) { return; } - const filePath = URL.createObjectURL(file); - IOU.setMoneyRequestReceipt(filePath, file.name); + IOU.setMoneyRequestReceipt(file.uri, file.name); IOU.navigateToNextPage(iou, iouType, reportID, report); }; From fb4120ee101b29c28570729df1f4c31a253a7eb9 Mon Sep 17 00:00:00 2001 From: Andrew Li Date: Thu, 10 Aug 2023 18:32:55 -0400 Subject: [PATCH 15/61] Update request header, disable receipt modal, update image URI blob check --- src/components/ReportActionItem/MoneyRequestPreview.js | 2 +- src/components/ReportActionItem/ReportPreview.js | 1 + src/libs/ReceiptUtils.js | 4 ++-- src/libs/ReportUtils.js | 4 ++++ 4 files changed, 8 insertions(+), 3 deletions(-) diff --git a/src/components/ReportActionItem/MoneyRequestPreview.js b/src/components/ReportActionItem/MoneyRequestPreview.js index f1b34ae652b9..7be5a6cbe7d2 100644 --- a/src/components/ReportActionItem/MoneyRequestPreview.js +++ b/src/components/ReportActionItem/MoneyRequestPreview.js @@ -197,7 +197,6 @@ function MoneyRequestPreview(props) { const renderReceipt = ({receipt, filename}) => { const {thumbnail, image} = ReceiptUtils.getThumbnailAndImageURIs(receipt.source, filename); - console.log(thumbnail, image); if (thumbnail) { return ( `} /> ); diff --git a/src/components/ReportActionItem/ReportPreview.js b/src/components/ReportActionItem/ReportPreview.js index 0abb6d824f91..7676bff77a40 100644 --- a/src/components/ReportActionItem/ReportPreview.js +++ b/src/components/ReportActionItem/ReportPreview.js @@ -162,6 +162,7 @@ function ReportPreview(props) { src="${thumbnail}" data-expensify-source="${image}" data-expensify-fit-container="true" + data-expensify-preview-modal-disabled="true" /> `} /> : diff --git a/src/libs/ReceiptUtils.js b/src/libs/ReceiptUtils.js index 9dd83ed48e87..e973c85f02d8 100644 --- a/src/libs/ReceiptUtils.js +++ b/src/libs/ReceiptUtils.js @@ -34,12 +34,12 @@ function validateReceipt(file) { /** * Grab the appropriate receipt image and thumbnail URIs based on file type * - * @param {String} path URI to image, i.e. blob://new.expensify.com/9ef3a018-4067-47c6-b29f-5f1bd35f213d or expensify.com/receipts/w_e616108497ef940b7210ec6beb5a462d01a878f4.jpg + * @param {String} path URI to image, i.e. blob:new.expensify.com/9ef3a018-4067-47c6-b29f-5f1bd35f213d or expensify.com/receipts/w_e616108497ef940b7210ec6beb5a462d01a878f4.jpg * @param {String} filename of uploaded image or last part of remote URI * @returns {Object} */ function getThumbnailAndImageURIs(path, filename) { - if (path.startsWith('blob://')) { + if (path.startsWith('blob:')) { return {thumbnail: null, image: path}; } diff --git a/src/libs/ReportUtils.js b/src/libs/ReportUtils.js index 3fd1236e26ae..bf9f263c6c57 100644 --- a/src/libs/ReportUtils.js +++ b/src/libs/ReportUtils.js @@ -1259,6 +1259,10 @@ function getTransactionReportName(reportAction) { return Localize.translateLocal('parentReportAction.deletedRequest'); } + if (!ReportActionsUtils.hasReadyMoneyRequests(reportAction)) { + return Localize.translateLocal('iou.receiptScanning'); + } + return Localize.translateLocal(ReportActionsUtils.isSentMoneyReportAction(reportAction) ? 'iou.threadSentMoneyReportName' : 'iou.threadRequestReportName', { formattedAmount: ReportActionsUtils.getFormattedAmount(reportAction), comment: lodashGet(reportAction, 'originalMessage.comment'), From b6280f2e5a7563b59f0ce19c8adedd8948790405 Mon Sep 17 00:00:00 2001 From: Andrew Li Date: Thu, 10 Aug 2023 18:34:22 -0400 Subject: [PATCH 16/61] Update scan in progress string --- src/languages/en.js | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/languages/en.js b/src/languages/en.js index 810b60d4ec5a..f6e445537aeb 100755 --- a/src/languages/en.js +++ b/src/languages/en.js @@ -380,7 +380,7 @@ export default { viewDetails: 'View details', pending: 'Pending', receipt: 'Receipt', - receiptScanning: 'Scanning in progress...', + receiptScanning: 'Scan in progress...', settledExpensify: 'Paid', settledElsewhere: 'Paid elsewhere', settledPaypalMe: 'Paid using Paypal.me', From a7fe6c8d2dc17c141a6fc6d37ffc1fdbcc34ded3 Mon Sep 17 00:00:00 2001 From: Andrew Li Date: Thu, 10 Aug 2023 19:10:03 -0400 Subject: [PATCH 17/61] Add receipt in money request view --- .../ReportActionItem/MoneyRequestView.js | 22 +++++++++++++++++++ 1 file changed, 22 insertions(+) diff --git a/src/components/ReportActionItem/MoneyRequestView.js b/src/components/ReportActionItem/MoneyRequestView.js index dc8916bdaecb..26333b9ca3db 100644 --- a/src/components/ReportActionItem/MoneyRequestView.js +++ b/src/components/ReportActionItem/MoneyRequestView.js @@ -20,6 +20,9 @@ import DateUtils from '../../libs/DateUtils'; import * as CurrencyUtils from '../../libs/CurrencyUtils'; import EmptyStateBackgroundImage from '../../../assets/images/empty-state_background-fade.png'; import useLocalize from '../../hooks/useLocalize'; +import * as TransactionUtils from '../../libs/TransactionUtils'; +import * as ReceiptUtils from '../../libs/ReceiptUtils'; +import RenderHTML from '../RenderHTML'; const propTypes = { /** The report currently being looked at */ @@ -49,6 +52,13 @@ function MoneyRequestView(props) { const isSettled = ReportUtils.isSettled(moneyRequestReport.reportID); const {translate} = useLocalize(); + const transaction = ReportActionsUtils.getTransaction(parentReportAction) + const hasReceipt = TransactionUtils.hasReceipt(transaction); + let receiptUris; + if (hasReceipt) { + receiptUris = ReceiptUtils.getThumbnailAndImageURIs(transaction.receipt.source, transaction.filename); + } + return ( @@ -58,6 +68,18 @@ function MoneyRequestView(props) { style={[StyleUtils.getReportWelcomeBackgroundImageStyle(true)]} /> + {hasReceipt && ( + + + `} /> + + )} Date: Thu, 10 Aug 2023 20:05:42 -0400 Subject: [PATCH 18/61] Add receipt whisper to details --- .../ReportActionItem/MoneyRequestView.js | 35 +++++++++++++------ .../ReportActionItem/ReportPreview.js | 2 +- src/languages/en.js | 4 ++- src/languages/es.js | 4 ++- src/styles/styles.js | 21 +++++++++++ 5 files changed, 53 insertions(+), 13 deletions(-) diff --git a/src/components/ReportActionItem/MoneyRequestView.js b/src/components/ReportActionItem/MoneyRequestView.js index 26333b9ca3db..45187cc092d5 100644 --- a/src/components/ReportActionItem/MoneyRequestView.js +++ b/src/components/ReportActionItem/MoneyRequestView.js @@ -23,6 +23,8 @@ import useLocalize from '../../hooks/useLocalize'; import * as TransactionUtils from '../../libs/TransactionUtils'; import * as ReceiptUtils from '../../libs/ReceiptUtils'; import RenderHTML from '../RenderHTML'; +import withLocalize from '../withLocalize'; +import Text from '../Text'; const propTypes = { /** The report currently being looked at */ @@ -69,16 +71,28 @@ function MoneyRequestView(props) { /> {hasReceipt && ( - - - `} /> - + <> + + + `} /> + + + + + {props.translate('iou.receiptWhisperTitle')} + + + {props.translate('iou.receiptWhisperText')} + + + + )} `${ONYXKEYS.COLLECTION.REPORT}${props.report.parentReportID}`, diff --git a/src/components/ReportActionItem/ReportPreview.js b/src/components/ReportActionItem/ReportPreview.js index 7676bff77a40..d3f4e66bcfeb 100644 --- a/src/components/ReportActionItem/ReportPreview.js +++ b/src/components/ReportActionItem/ReportPreview.js @@ -1,4 +1,4 @@ -import React, {useMemo} from 'react'; +import React from 'react'; import _ from 'underscore'; import {View} from 'react-native'; import PropTypes from 'prop-types'; diff --git a/src/languages/en.js b/src/languages/en.js index f6e445537aeb..04138cbde8df 100755 --- a/src/languages/en.js +++ b/src/languages/en.js @@ -380,7 +380,9 @@ export default { viewDetails: 'View details', pending: 'Pending', receipt: 'Receipt', - receiptScanning: 'Scan in progress...', + receiptScanning: 'Scan in progress…', + receiptWhisperTitle: 'Receipt scanning…', + receiptWhisperText: 'Only you can see this receipt when it\'s scanning. Check back later or enter the details now.', settledExpensify: 'Paid', settledElsewhere: 'Paid elsewhere', settledPaypalMe: 'Paid using Paypal.me', diff --git a/src/languages/es.js b/src/languages/es.js index 656723c59ab0..7828acb0e084 100644 --- a/src/languages/es.js +++ b/src/languages/es.js @@ -379,7 +379,9 @@ export default { viewDetails: 'Ver detalles', pending: 'Pendiente', receipt: 'Recibo', - receiptScanning: 'Escaneo en progreso...', + receiptScanning: 'Escaneo en progreso…', + receiptWhisperTitle: 'Escaneo de recibos…', + receiptWhisperText: 'Solo tú puedes ver este recibo cuando se está escaneando. Vuelve más tarde o ingresa los detalles ahora.', settledExpensify: 'Pagado', settledElsewhere: 'Pagado de otra forma', settledPaypalMe: 'Pagado con PayPal.me', diff --git a/src/styles/styles.js b/src/styles/styles.js index c945f7fee990..9ac0559b1105 100644 --- a/src/styles/styles.js +++ b/src/styles/styles.js @@ -3691,6 +3691,27 @@ const styles = { reportPreviewBoxText: { padding: 16, }, + + moneyRequestViewReceipt: { + marginHorizontal: 20, + marginVertical: 8, + borderWidth: 2, + borderColor: themeColors.border, + borderRadius: variables.componentBorderRadiusCard, + overflow: 'hidden', + height: 300, + }, + + moneyRequestViewReceiptWhisper: { + flexDirection: 'row', + marginHorizontal: 20, + marginVertical: 8, + paddingHorizontal: 20, + paddingVertical: 12, + justifyContent: 'space-between', + backgroundColor: themeColors.border, + borderRadius: variables.componentBorderRadiusCard, + } }; export default styles; From 1c989829d99de5a16bbb65b5c4c03850047b5493 Mon Sep 17 00:00:00 2001 From: Andrew Li Date: Fri, 11 Aug 2023 13:39:41 -0400 Subject: [PATCH 19/61] Move receipt whisper to header --- src/components/HeaderWithBackButton/index.js | 194 ++++++++++-------- src/components/MoneyRequestHeader.js | 5 + .../ReportActionItem/MoneyRequestView.js | 32 +-- src/languages/en.js | 4 +- src/languages/es.js | 4 +- src/pages/home/report/ReportActionItem.js | 2 +- src/styles/styles.js | 13 +- 7 files changed, 130 insertions(+), 124 deletions(-) diff --git a/src/components/HeaderWithBackButton/index.js b/src/components/HeaderWithBackButton/index.js index 6e6164f4c4fc..6577a91b24cd 100755 --- a/src/components/HeaderWithBackButton/index.js +++ b/src/components/HeaderWithBackButton/index.js @@ -18,6 +18,8 @@ import headerWithBackButtonPropTypes from './headerWithBackButtonPropTypes'; import useThrottledButtonState from '../../hooks/useThrottledButtonState'; import useLocalize from '../../hooks/useLocalize'; import useKeyboardState from '../../hooks/useKeyboardState'; +import themeColors from '../../styles/themes/default'; +import Text from '../Text'; function HeaderWithBackButton({ iconFill = undefined, @@ -37,6 +39,10 @@ function HeaderWithBackButton({ shouldShowGetAssistanceButton = false, shouldShowPinButton = false, shouldShowThreeDotsButton = false, + shouldShowStatusBar = false, + statusBarBadgeColor = themeColors.border, + statusBarBadgeText = '', + statusBarDescription = '', stepCounter = null, subtitle = '', title = '', @@ -52,111 +58,123 @@ function HeaderWithBackButton({ const {translate} = useLocalize(); const {isKeyboardShown} = useKeyboardState(); return ( - - - {shouldShowBackButton && ( - - { - if (isKeyboardShown) { - Keyboard.dismiss(); - } - onBackButtonPress(); - }} - style={[styles.touchableButtonImage]} - accessibilityRole="button" - accessibilityLabel={translate('common.back')} - > - - - - )} - {shouldShowAvatarWithDisplay && ( - - )} - {!shouldShowAvatarWithDisplay && ( -
- )} - - {children} - {shouldShowDownloadButton && ( - + <> + + + {shouldShowBackButton && ( + { - // Blur the pressable in case this button triggers a Growl notification - // We do not want to overlap Growl with the Tooltip (#15271) - e.currentTarget.blur(); - - if (!isDownloadButtonActive) { - return; + onPress={() => { + if (isKeyboardShown) { + Keyboard.dismiss(); } - - onDownloadButtonPress(); - temporarilyDisableDownloadButton(true); + onBackButtonPress(); }} style={[styles.touchableButtonImage]} accessibilityRole="button" - accessibilityLabel={translate('common.download')} + accessibilityLabel={translate('common.back')} > - - - )} - {shouldShowGetAssistanceButton && ( - - Navigation.navigate(ROUTES.getGetAssistanceRoute(guidesCallTaskID))} - style={[styles.touchableButtonImage]} - accessibilityRole="button" - accessibilityLabel={translate('getAssistancePage.questionMarkButtonTooltip')} - > - )} - {shouldShowPinButton && } - {shouldShowThreeDotsButton && ( - )} - {shouldShowCloseButton && ( - - - - - + {!shouldShowAvatarWithDisplay && ( +
)} + + {children} + {shouldShowDownloadButton && ( + + { + // Blur the pressable in case this button triggers a Growl notification + // We do not want to overlap Growl with the Tooltip (#15271) + e.currentTarget.blur(); + + if (!isDownloadButtonActive) { + return; + } + + onDownloadButtonPress(); + temporarilyDisableDownloadButton(true); + }} + style={[styles.touchableButtonImage]} + accessibilityRole="button" + accessibilityLabel={translate('common.download')} + > + + + + )} + {shouldShowGetAssistanceButton && ( + + Navigation.navigate(ROUTES.getGetAssistanceRoute(guidesCallTaskID))} + style={[styles.touchableButtonImage]} + accessibilityRole="button" + accessibilityLabel={translate('getAssistancePage.questionMarkButtonTooltip')} + > + + + + )} + {shouldShowPinButton && } + {shouldShowThreeDotsButton && ( + + )} + {shouldShowCloseButton && ( + + + + + + )} + - + {shouldShowStatusBar && ( + + + {statusBarBadgeText} + + + {statusBarDescription} + + + )} + ); } diff --git a/src/components/MoneyRequestHeader.js b/src/components/MoneyRequestHeader.js index e73185bd39a4..0b00afabff38 100644 --- a/src/components/MoneyRequestHeader.js +++ b/src/components/MoneyRequestHeader.js @@ -66,6 +66,8 @@ function MoneyRequestHeader(props) { IOU.deleteMoneyRequest(parentReportAction.originalMessage.IOUTransactionID, parentReportAction, true); }, [parentReportAction]); + const isScanning = !ReportActionsUtils.hasReadyMoneyRequests(parentReportAction); + return ( Navigation.goBack(ROUTES.HOME, false, true)} shouldShowBorderBottom + shouldShowStatusBar={isScanning} + statusBarBadgeText={props.translate('iou.receiptStatusTitle')} + statusBarDescription={props.translate('iou.receiptStatusText')} /> ); diff --git a/src/components/ReportActionItem/MoneyRequestView.js b/src/components/ReportActionItem/MoneyRequestView.js index 45187cc092d5..d24b68f55729 100644 --- a/src/components/ReportActionItem/MoneyRequestView.js +++ b/src/components/ReportActionItem/MoneyRequestView.js @@ -71,28 +71,16 @@ function MoneyRequestView(props) { /> {hasReceipt && ( - <> - - - `} /> - - - - - {props.translate('iou.receiptWhisperTitle')} - - - {props.translate('iou.receiptWhisperText')} - - - - + + + `} /> + )} 0 || !ReportActionsUtils.hasReadyMoneyRequests(props.action); + const isWhisper = whisperedToAccountIDs.length > 0; const isMultipleParticipant = whisperedToAccountIDs.length > 1; const isWhisperOnlyVisibleByUser = isWhisper && ReportUtils.isCurrentUserTheOnlyParticipant(whisperedToAccountIDs); const whisperedToPersonalDetails = isWhisper ? _.filter(props.personalDetailsList, (details) => _.includes(whisperedToAccountIDs, details.accountID)) : []; diff --git a/src/styles/styles.js b/src/styles/styles.js index 9ac0559b1105..e96a3c5831df 100644 --- a/src/styles/styles.js +++ b/src/styles/styles.js @@ -3702,15 +3702,10 @@ const styles = { height: 300, }, - moneyRequestViewReceiptWhisper: { - flexDirection: 'row', - marginHorizontal: 20, - marginVertical: 8, - paddingHorizontal: 20, - paddingVertical: 12, - justifyContent: 'space-between', - backgroundColor: themeColors.border, - borderRadius: variables.componentBorderRadiusCard, + headerStatusBarBadge: { + padding: 8, + borderRadius: variables.componentBorderRadiusMedium, + marginRight: 16, } }; From b2b9529116a6c33857de53ae8610f22c8b5d65bf Mon Sep 17 00:00:00 2001 From: Andrew Li Date: Fri, 11 Aug 2023 14:08:21 -0400 Subject: [PATCH 20/61] Show merchant and number of requests in report preview --- .../ReportActionItem/ReportPreview.js | 63 +++++++++++-------- src/languages/en.js | 3 +- src/languages/es.js | 3 +- src/libs/ReportActionsUtils.js | 17 ++++- 4 files changed, 56 insertions(+), 30 deletions(-) diff --git a/src/components/ReportActionItem/ReportPreview.js b/src/components/ReportActionItem/ReportPreview.js index d3f4e66bcfeb..4d12a974b64c 100644 --- a/src/components/ReportActionItem/ReportPreview.js +++ b/src/components/ReportActionItem/ReportPreview.js @@ -102,8 +102,12 @@ function ReportPreview(props) { const reportTotal = ReportUtils.getMoneyRequestTotal(props.iouReport); const iouSettled = ReportUtils.isSettled(props.iouReportID); - const receipts = ReportActionUtils.getReportPreviewTransactions(props.action); - const isScanning = !ReportActionUtils.hasReadyMoneyRequests(props.action); + const numberOfRequests = ReportActionUtils.getNumberOfMoneyRequests(props.action); + + const receipts = ReportActionUtils.getReportPreviewReceipts(props.action); + const hasReceipts = receipts.length > 0; + const isScanning = hasReceipts && !ReportActionUtils.hasReadyMoneyRequests(props.action); + const hasOnlyOneReceiptRequest = numberOfRequests === 1 && hasReceipts; let displayAmount; if (reportTotal) { @@ -148,29 +152,31 @@ function ReportPreview(props) { accessibilityLabel={props.translate('iou.viewDetails')} > - - {_.map(receipts, ({receipt, filename, transactionID}) => { - const {thumbnail, image} = ReceiptUtils.getThumbnailAndImageURIs(receipt.source, filename); - return ( - - {thumbnail - ? - `} /> - : - } - - ); - })} - + {hasReceipts && ( + + {_.map(receipts, ({receipt, filename, transactionID}) => { + const {thumbnail, image} = ReceiptUtils.getThumbnailAndImageURIs(receipt.source, filename); + return ( + + {thumbnail + ? + `} /> + : + } + + ); + })} + + )} @@ -190,6 +196,13 @@ function ReportPreview(props) { )} + {hasReceipts && !isScanning && ( + + + {hasOnlyOneReceiptRequest ? receipts[0].merchant : props.translate('iou.requestCount', { count: numberOfRequests })} + + + )} {shouldShowSettlementButton && ( `${count} requests`, settledExpensify: 'Paid', settledElsewhere: 'Paid elsewhere', settledPaypalMe: 'Paid using Paypal.me', diff --git a/src/languages/es.js b/src/languages/es.js index b0171c445a38..2e35608f8996 100644 --- a/src/languages/es.js +++ b/src/languages/es.js @@ -379,9 +379,10 @@ export default { viewDetails: 'Ver detalles', pending: 'Pendiente', receipt: 'Recibo', - receiptScanning: 'Escaneo en progreso…', + receiptScanning: 'Escaneo de recibo en curso…', receiptStatusTitle: 'Escaneado…', receiptStatusText: 'Solo tú puedes ver este recibo cuando se está escaneando. Vuelve más tarde o ingresa los detalles ahora.', + requestCount: ({count}) => `${count} solicitudes`, settledExpensify: 'Pagado', settledElsewhere: 'Pagado de otra forma', settledPaypalMe: 'Pagado con PayPal.me', diff --git a/src/libs/ReportActionsUtils.js b/src/libs/ReportActionsUtils.js index 0d015189410e..7988ec552fd9 100644 --- a/src/libs/ReportActionsUtils.js +++ b/src/libs/ReportActionsUtils.js @@ -582,7 +582,7 @@ function isMessageDeleted(reportAction) { * @param {Object} reportPreviewAction * @returns {Object} */ -function getReportPreviewTransactions(reportPreviewAction) { +function getReportPreviewReceipts(reportPreviewAction) { const transactionIDs = lodashGet(reportPreviewAction, ['childLastReceiptTransactionIDs'], '').split(','); return _.reduce(transactionIDs, (transactions, transactionID) => { const transaction = allTransactions[transactionID]; @@ -611,7 +611,7 @@ function getTransaction(iouReportAction) { */ function hasReadyMoneyRequests(reportAction) { if (isReportPreviewAction(reportAction)) { - const transactions = getReportPreviewTransactions(reportAction); + const transactions = getReportPreviewReceipts(reportAction); return _.some(transactions, (transaction) => !TransactionUtils.hasReceipt(transaction) || !ReceiptUtils.isBeingScanned(transaction.receipt)); } @@ -623,6 +623,16 @@ function hasReadyMoneyRequests(reportAction) { return true; } +/** + * Returns the number of money requests associated with a report preview + * + * @param {Object|null} reportPreviewAction + * @returns {Number} + */ +function getNumberOfMoneyRequests(reportPreviewAction) { + return lodashGet(reportPreviewAction, 'childMoneyRequestCount', 0); +} + export { getSortedReportActions, getLastVisibleAction, @@ -655,7 +665,8 @@ export { isWhisperAction, isPendingRemove, getReportAction, - getReportPreviewTransactions, + getReportPreviewReceipts, hasReadyMoneyRequests, getTransaction, + getNumberOfMoneyRequests, }; From 2c9768f06925648bb6c7a7696fcfe5f795e1c4fb Mon Sep 17 00:00:00 2001 From: Andrew Li Date: Fri, 11 Aug 2023 14:52:17 -0400 Subject: [PATCH 21/61] Add receipt preview modal to money request view --- .../extractAttachmentsFromReport.js | 17 +++++++++++++++++ .../ReportActionItem/MoneyRequestView.js | 3 +-- src/pages/home/report/ReportActionItem.js | 17 +++++++++++++---- 3 files changed, 31 insertions(+), 6 deletions(-) diff --git a/src/components/Attachments/AttachmentCarousel/extractAttachmentsFromReport.js b/src/components/Attachments/AttachmentCarousel/extractAttachmentsFromReport.js index 047a016674b7..25ed63a121dc 100644 --- a/src/components/Attachments/AttachmentCarousel/extractAttachmentsFromReport.js +++ b/src/components/Attachments/AttachmentCarousel/extractAttachmentsFromReport.js @@ -1,6 +1,8 @@ import {Parser as HtmlParser} from 'htmlparser2'; import _ from 'underscore'; import * as ReportActionsUtils from '../../../libs/ReportActionsUtils'; +import * as TransactionUtils from '../../../libs/TransactionUtils'; +import * as ReceiptUtils from '../../../libs/ReceiptUtils'; import CONST from '../../../CONST'; import tryResolveUrlFromApiRoot from '../../../libs/tryResolveUrlFromApiRoot'; import Navigation from '../../../libs/Navigation/Navigation'; @@ -38,6 +40,21 @@ function extractAttachmentsFromReport(report, reportActions, source) { if (!ReportActionsUtils.shouldReportActionBeVisible(action, key)) { return; } + + // We're handling receipts differently here because receipt images are not + // part of the report action message, the images are constructed client-side + if (ReportActionsUtils.isMoneyRequestAction(action)) { + const transaction = ReportActionsUtils.getTransaction(action); + if (TransactionUtils.hasReceipt(transaction)) { + const {image} = ReceiptUtils.getThumbnailAndImageURIs(transaction.receipt.source, transaction.filename); + attachments.unshift({ + source: tryResolveUrlFromApiRoot(image), + isAuthTokenRequired: true, + }); + return; + } + } + htmlParser.write(_.get(action, ['message', 0, 'html'])); }); htmlParser.end(); diff --git a/src/components/ReportActionItem/MoneyRequestView.js b/src/components/ReportActionItem/MoneyRequestView.js index d24b68f55729..876b75ae4224 100644 --- a/src/components/ReportActionItem/MoneyRequestView.js +++ b/src/components/ReportActionItem/MoneyRequestView.js @@ -54,7 +54,7 @@ function MoneyRequestView(props) { const isSettled = ReportUtils.isSettled(moneyRequestReport.reportID); const {translate} = useLocalize(); - const transaction = ReportActionsUtils.getTransaction(parentReportAction) + const transaction = ReportActionsUtils.getTransaction(parentReportAction); const hasReceipt = TransactionUtils.hasReceipt(transaction); let receiptUris; if (hasReceipt) { @@ -77,7 +77,6 @@ function MoneyRequestView(props) { src="${receiptUris.thumbnail}" data-expensify-source="${receiptUris.image}" data-expensify-fit-container="true" - data-expensify-preview-modal-disabled="true" /> `} /> diff --git a/src/pages/home/report/ReportActionItem.js b/src/pages/home/report/ReportActionItem.js index b25efda7ebc8..cc0a6ce294f5 100644 --- a/src/pages/home/report/ReportActionItem.js +++ b/src/pages/home/report/ReportActionItem.js @@ -452,10 +452,19 @@ function ReportActionItem(props) { const parentReport = ReportActionsUtils.getParentReportAction(props.report); if (ReportActionsUtils.isTransactionThread(parentReport)) { return ( - + + + ); } if (ReportUtils.isTaskReport(props.report)) { From 570a5da4edd04004d2b3716a50ed58e8b1a5a7e3 Mon Sep 17 00:00:00 2001 From: Andrew Li Date: Fri, 11 Aug 2023 15:34:47 -0400 Subject: [PATCH 22/61] Add receipt title and options for preview modal --- src/components/AttachmentModal.js | 24 ++++++++++++++++++- .../extractAttachmentsFromReport.js | 3 +++ .../ReportActionItem/MoneyRequestPreview.js | 2 +- .../ReportActionItem/ReportPreview.js | 2 +- src/languages/en.js | 4 +++- src/languages/es.js | 4 +++- 6 files changed, 34 insertions(+), 5 deletions(-) diff --git a/src/components/AttachmentModal.js b/src/components/AttachmentModal.js index 7a371e73439f..9edda1e58eb7 100755 --- a/src/components/AttachmentModal.js +++ b/src/components/AttachmentModal.js @@ -25,6 +25,7 @@ import HeaderGap from './HeaderGap'; import SafeAreaConsumer from './SafeAreaConsumer'; import addEncryptedAuthTokenToURL from '../libs/addEncryptedAuthTokenToURL'; import reportPropTypes from '../pages/reportPropTypes'; +import * as Expensicons from './Icon/Expensicons'; /** * Modal render prop component that exposes modal launching triggers that can be used @@ -93,6 +94,7 @@ function AttachmentModal(props) { const [shouldLoadAttachment, setShouldLoadAttachment] = useState(false); const [isAttachmentInvalid, setIsAttachmentInvalid] = useState(false); const [isAuthTokenRequired, setIsAuthTokenRequired] = useState(props.isAuthTokenRequired); + const [isAttachmentReceipt, setIsAttachmentReceipt] = useState(false); const [attachmentInvalidReasonTitle, setAttachmentInvalidReasonTitle] = useState(''); const [attachmentInvalidReason, setAttachmentInvalidReason] = useState(null); const [source, setSource] = useState(props.source); @@ -118,6 +120,7 @@ function AttachmentModal(props) { (attachment) => { setSource(attachment.source); setFile(attachment.file); + setIsAttachmentReceipt(attachment.receipt); setIsAuthTokenRequired(attachment.isAuthTokenRequired); onCarouselAttachmentChange(attachment); }, @@ -308,6 +311,7 @@ function AttachmentModal(props) { }, []); const sourceForAttachmentView = props.source || source; + return ( <> {props.isSmallScreenWidth && } downloadAttachment(source)} shouldShowCloseButton={!props.isSmallScreenWidth} shouldShowBackButton={props.isSmallScreenWidth} + shouldShowThreeDotsButton={isAttachmentReceipt} + threeDotsMenuItems={[ + { + icon: Expensicons.Camera, + text: props.translate('common.replace'), + onSelected: () => {}, // TODO + }, + { + icon: Expensicons.Download, + text: props.translate('common.download'), + onSelected: () => downloadAttachment(source), + }, + { + icon: Expensicons.Trashcan, + text: props.translate('iou.deleteReceipt'), + onSelected: () => {}, // TODO + }, + ]} onBackButtonPress={closeModal} onCloseButtonPress={closeModal} /> diff --git a/src/components/Attachments/AttachmentCarousel/extractAttachmentsFromReport.js b/src/components/Attachments/AttachmentCarousel/extractAttachmentsFromReport.js index 25ed63a121dc..8cdfb53b132b 100644 --- a/src/components/Attachments/AttachmentCarousel/extractAttachmentsFromReport.js +++ b/src/components/Attachments/AttachmentCarousel/extractAttachmentsFromReport.js @@ -32,6 +32,7 @@ function extractAttachmentsFromReport(report, reportActions, source) { source: tryResolveUrlFromApiRoot(expensifySource || attribs.src), isAuthTokenRequired: Boolean(expensifySource), file: {name: attribs[CONST.ATTACHMENT_ORIGINAL_FILENAME_ATTRIBUTE]}, + receipt: false, }); }, }); @@ -50,6 +51,8 @@ function extractAttachmentsFromReport(report, reportActions, source) { attachments.unshift({ source: tryResolveUrlFromApiRoot(image), isAuthTokenRequired: true, + file: {name: transaction.filename}, + receipt: true, }); return; } diff --git a/src/components/ReportActionItem/MoneyRequestPreview.js b/src/components/ReportActionItem/MoneyRequestPreview.js index 7be5a6cbe7d2..2edf6445c3c1 100644 --- a/src/components/ReportActionItem/MoneyRequestPreview.js +++ b/src/components/ReportActionItem/MoneyRequestPreview.js @@ -171,7 +171,7 @@ function MoneyRequestPreview(props) { const getPreviewHeaderText = () => { if (isScanning) { - return props.translate('iou.receipt'); + return props.translate('common.receipt'); } if (props.isBillSplit) { diff --git a/src/components/ReportActionItem/ReportPreview.js b/src/components/ReportActionItem/ReportPreview.js index 4d12a974b64c..af23b5b1319c 100644 --- a/src/components/ReportActionItem/ReportPreview.js +++ b/src/components/ReportActionItem/ReportPreview.js @@ -129,7 +129,7 @@ function ReportPreview(props) { let previewMessage; const managerName = ReportUtils.isPolicyExpenseChat(props.chatReport) ? ReportUtils.getPolicyName(props.chatReport) : ReportUtils.getDisplayNameForParticipant(managerID, true); if (_.some(receipts, ({receipt}) => ReceiptUtils.isBeingScanned(receipt))) { - previewMessage = props.translate('iou.receipt'); + previewMessage = props.translate('common.receipt'); } else { previewMessage = props.translate(iouSettled || props.iouReport.isWaitingOnBankAccount ? 'iou.payerPaid' : 'iou.payerOwes', {payer: managerName}); } diff --git a/src/languages/en.js b/src/languages/en.js index 5637f1696a10..88d13b1795be 100755 --- a/src/languages/en.js +++ b/src/languages/en.js @@ -151,6 +151,8 @@ export default { edit: 'Edit', showMore: 'Show more', merchant: 'Merchant', + receipt: 'Receipt', + replace: 'Replace', }, anonymousReportFooter: { logoTagline: 'Join in on the discussion.', @@ -379,7 +381,7 @@ export default { pay: 'Pay', viewDetails: 'View details', pending: 'Pending', - receipt: 'Receipt', + deleteReceipt: 'Delete receipt', receiptScanning: 'Receipt scan in progress…', receiptStatusTitle: 'Scanning…', receiptStatusText: 'Only you can see this receipt when it\'s scanning. Check back later or enter the details now.', diff --git a/src/languages/es.js b/src/languages/es.js index 2e35608f8996..026fec47cc37 100644 --- a/src/languages/es.js +++ b/src/languages/es.js @@ -150,6 +150,8 @@ export default { edit: 'Editar', showMore: 'Mostrar más', merchant: 'Comerciante', + receipt: 'Recibo', + replace: 'Sustituir', }, anonymousReportFooter: { logoTagline: 'Únete a la discussion.', @@ -378,7 +380,7 @@ export default { pay: 'Pagar', viewDetails: 'Ver detalles', pending: 'Pendiente', - receipt: 'Recibo', + deleteReceipt: 'Eliminar recibo', receiptScanning: 'Escaneo de recibo en curso…', receiptStatusTitle: 'Escaneado…', receiptStatusText: 'Solo tú puedes ver este recibo cuando se está escaneando. Vuelve más tarde o ingresa los detalles ahora.', From 904edcebfb78321dce59f8fda1d0921aac73b3e3 Mon Sep 17 00:00:00 2001 From: Andrew Li Date: Fri, 11 Aug 2023 16:45:24 -0400 Subject: [PATCH 23/61] Add additional money requests indicator and refactor --- src/components/ReceiptImages.js | 75 +++++++++++++++++++ .../ReportActionItem/ReportPreview.js | 31 ++------ src/styles/styles.js | 15 ++++ 3 files changed, 97 insertions(+), 24 deletions(-) create mode 100644 src/components/ReceiptImages.js diff --git a/src/components/ReceiptImages.js b/src/components/ReceiptImages.js new file mode 100644 index 000000000000..53e0cf99958c --- /dev/null +++ b/src/components/ReceiptImages.js @@ -0,0 +1,75 @@ +import React from 'react'; +import {View} from 'react-native'; +import PropTypes from 'prop-types'; +import _ from 'underscore'; +import styles from '../styles/styles'; +import * as ReceiptUtils from '../libs/ReceiptUtils'; +import RenderHTML from './RenderHTML'; +import stylePropTypes from '../styles/stylePropTypes'; +import Text from './Text'; + +const propTypes = { + /** array of image URIs */ + images: PropTypes.arrayOf(PropTypes.shape({ + source: PropTypes.string, + filename: PropTypes.string, + })), + + /** max number of images to show in the row */ + size: PropTypes.number, + + /** optional: total number of images */ + total: PropTypes.number, + + hoverStyle: stylePropTypes, +}; + +const defaultProps = { + images: [], + size: 0, + total: 0, + hoverStyle: {}, +}; + +function ReceiptImages(props) { + const images = props.images.slice(0, props.size); + const remaining = props.total - props.size; + + return ( + + {_.map(images, ({source, filename}, index) => { + const {thumbnail, image} = ReceiptUtils.getThumbnailAndImageURIs(source, filename); + const isLastImage = index === props.size - 1; + return ( + + {thumbnail + ? + `} /> + : + } + {isLastImage && remaining > 0 && ( + + +{remaining} + + )} + + ); + })} + + ) +} + +ReceiptImages.propTypes = propTypes; +ReceiptImages.defaultProps = defaultProps; +ReceiptImages.displayName = 'ReceiptImages'; + +export default ReceiptImages; \ No newline at end of file diff --git a/src/components/ReportActionItem/ReportPreview.js b/src/components/ReportActionItem/ReportPreview.js index af23b5b1319c..48588c4e52c4 100644 --- a/src/components/ReportActionItem/ReportPreview.js +++ b/src/components/ReportActionItem/ReportPreview.js @@ -28,7 +28,7 @@ import reportPropTypes from '../../pages/reportPropTypes'; import RenderHTML from '../RenderHTML'; import * as ReceiptUtils from '../../libs/ReceiptUtils'; import * as ReportActionUtils from '../../libs/ReportActionsUtils'; -import Image from '../Image'; +import ReceiptImages from '../ReceiptImages'; const propTypes = { /** All the data of the action */ @@ -153,29 +153,12 @@ function ReportPreview(props) { > {hasReceipts && ( - - {_.map(receipts, ({receipt, filename, transactionID}) => { - const {thumbnail, image} = ReceiptUtils.getThumbnailAndImageURIs(receipt.source, filename); - return ( - - {thumbnail - ? - `} /> - : - } - - ); - })} - + ({source: receipt.source, filename}))} + size={3} + total={ReportActionUtils.getNumberOfMoneyRequests(props.action)} + hoverStyle={props.isHovered || isScanning ? styles.reportPreviewBoxHoverBorder : undefined} + /> )} diff --git a/src/styles/styles.js b/src/styles/styles.js index e96a3c5831df..bf2dfa945622 100644 --- a/src/styles/styles.js +++ b/src/styles/styles.js @@ -3682,10 +3682,25 @@ const styles = { flex: 1, width: '100%', height: '100%', + display: 'flex', + justifyContent: 'center', + alignItems: 'center', + }, + + reportPreviewBoxReceiptsMore: { + position: 'absolute', + borderRadius: '50%', + backgroundColor: themeColors.cardBG, + width: 36, + height: 36, + display: 'flex', + justifyContent: 'center', + alignItems: 'center', }, reportPreviewBoxHoverBorder: { borderColor: themeColors.border, + backgroundColor: themeColors.border, }, reportPreviewBoxText: { From ca2efcd470c45b8404b660c9aeed36b2e926f2ad Mon Sep 17 00:00:00 2001 From: Andrew Li Date: Fri, 11 Aug 2023 17:18:31 -0400 Subject: [PATCH 24/61] Add number of requests scanning in report preview subtitle --- .../ReportActionItem/ReportPreview.js | 18 ++++++++---- src/languages/en.js | 2 +- src/languages/es.js | 2 +- src/libs/ReportActionsUtils.js | 28 ++++++++++++++++--- 4 files changed, 39 insertions(+), 11 deletions(-) diff --git a/src/components/ReportActionItem/ReportPreview.js b/src/components/ReportActionItem/ReportPreview.js index 48588c4e52c4..3f39b9cb2dd1 100644 --- a/src/components/ReportActionItem/ReportPreview.js +++ b/src/components/ReportActionItem/ReportPreview.js @@ -103,9 +103,10 @@ function ReportPreview(props) { const iouSettled = ReportUtils.isSettled(props.iouReportID); const numberOfRequests = ReportActionUtils.getNumberOfMoneyRequests(props.action); + const numberOfScanningReceipts = ReportActionUtils.getNumberOfScanningReceipts(props.iouReport); - const receipts = ReportActionUtils.getReportPreviewReceipts(props.action); - const hasReceipts = receipts.length > 0; + const transactions = ReportActionUtils.getReportPreviewTransactionsWithReceipts(props.action); + const hasReceipts = transactions.length > 0; const isScanning = hasReceipts && !ReportActionUtils.hasReadyMoneyRequests(props.action); const hasOnlyOneReceiptRequest = numberOfRequests === 1 && hasReceipts; @@ -128,7 +129,7 @@ function ReportPreview(props) { let previewMessage; const managerName = ReportUtils.isPolicyExpenseChat(props.chatReport) ? ReportUtils.getPolicyName(props.chatReport) : ReportUtils.getDisplayNameForParticipant(managerID, true); - if (_.some(receipts, ({receipt}) => ReceiptUtils.isBeingScanned(receipt))) { + if (isScanning) { previewMessage = props.translate('common.receipt'); } else { previewMessage = props.translate(iouSettled || props.iouReport.isWaitingOnBankAccount ? 'iou.payerPaid' : 'iou.payerOwes', {payer: managerName}); @@ -154,7 +155,7 @@ function ReportPreview(props) { {hasReceipts && ( ({source: receipt.source, filename}))} + images={_.map(transactions, ({receipt, filename}) => ({source: receipt.source, filename}))} size={3} total={ReportActionUtils.getNumberOfMoneyRequests(props.action)} hoverStyle={props.isHovered || isScanning ? styles.reportPreviewBoxHoverBorder : undefined} @@ -182,7 +183,14 @@ function ReportPreview(props) { {hasReceipts && !isScanning && ( - {hasOnlyOneReceiptRequest ? receipts[0].merchant : props.translate('iou.requestCount', { count: numberOfRequests })} + { + hasOnlyOneReceiptRequest + ? transactions[0].merchant + : props.translate('iou.requestCount', { + count: numberOfRequests, + scanningReceipts: numberOfScanningReceipts, + }) + } )} diff --git a/src/languages/en.js b/src/languages/en.js index 88d13b1795be..04516abd02d2 100755 --- a/src/languages/en.js +++ b/src/languages/en.js @@ -385,7 +385,7 @@ export default { receiptScanning: 'Receipt scan in progress…', receiptStatusTitle: 'Scanning…', receiptStatusText: 'Only you can see this receipt when it\'s scanning. Check back later or enter the details now.', - requestCount: ({count}) => `${count} requests`, + requestCount: ({count, scanningReceipts = 0}) => `${count} requests${scanningReceipts > 0 ? `, ${scanningReceipts} scanning` : ''}`, settledExpensify: 'Paid', settledElsewhere: 'Paid elsewhere', settledPaypalMe: 'Paid using Paypal.me', diff --git a/src/languages/es.js b/src/languages/es.js index 026fec47cc37..819b91d32201 100644 --- a/src/languages/es.js +++ b/src/languages/es.js @@ -384,7 +384,7 @@ export default { receiptScanning: 'Escaneo de recibo en curso…', receiptStatusTitle: 'Escaneado…', receiptStatusText: 'Solo tú puedes ver este recibo cuando se está escaneando. Vuelve más tarde o ingresa los detalles ahora.', - requestCount: ({count}) => `${count} solicitudes`, + requestCount: ({count, scanningReceipts = 0}) => `${count} solicitudes${scanningReceipts > 0 ? `, ${scanningReceipts} escaneo` : ''}`, settledExpensify: 'Pagado', settledElsewhere: 'Pagado de otra forma', settledPaypalMe: 'Pagado con PayPal.me', diff --git a/src/libs/ReportActionsUtils.js b/src/libs/ReportActionsUtils.js index 7988ec552fd9..187429f33d33 100644 --- a/src/libs/ReportActionsUtils.js +++ b/src/libs/ReportActionsUtils.js @@ -582,7 +582,7 @@ function isMessageDeleted(reportAction) { * @param {Object} reportPreviewAction * @returns {Object} */ -function getReportPreviewReceipts(reportPreviewAction) { +function getReportPreviewTransactionsWithReceipts(reportPreviewAction) { const transactionIDs = lodashGet(reportPreviewAction, ['childLastReceiptTransactionIDs'], '').split(','); return _.reduce(transactionIDs, (transactions, transactionID) => { const transaction = allTransactions[transactionID]; @@ -611,8 +611,8 @@ function getTransaction(iouReportAction) { */ function hasReadyMoneyRequests(reportAction) { if (isReportPreviewAction(reportAction)) { - const transactions = getReportPreviewReceipts(reportAction); - return _.some(transactions, (transaction) => !TransactionUtils.hasReceipt(transaction) || !ReceiptUtils.isBeingScanned(transaction.receipt)); + const transactions = getReportPreviewTransactionsWithReceipts(reportAction); + return _.some(transactions, (transaction) => !ReceiptUtils.isBeingScanned(transaction.receipt)); } if (isMoneyRequestAction(reportAction)) { @@ -633,6 +633,25 @@ function getNumberOfMoneyRequests(reportPreviewAction) { return lodashGet(reportPreviewAction, 'childMoneyRequestCount', 0); } +/** + * Returns the number of receipts associated with an IOU report that are still being scanned. + * Note that we search the IOU report for this number, since scanning receipts will be whispers + * in the IOU report for only the submitter and we can get an accurate count. + * + * @param {Object|null} iouReport + * @returns {Number} + */ +function getNumberOfScanningReceipts(iouReport) { + const reportActions = lodashGet(allReportActions, lodashGet(iouReport, 'reportID'), []); + return _.reduce(reportActions, (count, reportAction) => { + if (!isMoneyRequestAction(reportAction)) { + return count; + } + const transaction = getTransaction(reportAction); + return count + Number(TransactionUtils.hasReceipt(transaction) && ReceiptUtils.isBeingScanned(transaction.receipt)); + }, 0); +} + export { getSortedReportActions, getLastVisibleAction, @@ -665,8 +684,9 @@ export { isWhisperAction, isPendingRemove, getReportAction, - getReportPreviewReceipts, + getReportPreviewTransactionsWithReceipts, hasReadyMoneyRequests, getTransaction, getNumberOfMoneyRequests, + getNumberOfScanningReceipts, }; From 2d2e5846dc392a99d11ebf3f81069d540d645143 Mon Sep 17 00:00:00 2001 From: Andrew Li Date: Mon, 14 Aug 2023 13:05:53 -0400 Subject: [PATCH 25/61] Refactor image component and reuse --- .../ReportActionItem/MoneyRequestView.js | 15 ++------ .../ReportActionItem/ReportPreview.js | 7 ++-- ...iptImages.js => ReportActionItemImages.js} | 34 +++++++++---------- src/styles/styles.js | 34 +++++++------------ 4 files changed, 33 insertions(+), 57 deletions(-) rename src/components/{ReceiptImages.js => ReportActionItemImages.js} (65%) diff --git a/src/components/ReportActionItem/MoneyRequestView.js b/src/components/ReportActionItem/MoneyRequestView.js index 876b75ae4224..2b8d895fc63f 100644 --- a/src/components/ReportActionItem/MoneyRequestView.js +++ b/src/components/ReportActionItem/MoneyRequestView.js @@ -22,9 +22,8 @@ import EmptyStateBackgroundImage from '../../../assets/images/empty-state_backgr import useLocalize from '../../hooks/useLocalize'; import * as TransactionUtils from '../../libs/TransactionUtils'; import * as ReceiptUtils from '../../libs/ReceiptUtils'; -import RenderHTML from '../RenderHTML'; import withLocalize from '../withLocalize'; -import Text from '../Text'; +import ReportActionItemImages from '../ReportActionItemImages'; const propTypes = { /** The report currently being looked at */ @@ -70,17 +69,7 @@ function MoneyRequestView(props) { style={[StyleUtils.getReportWelcomeBackgroundImageStyle(true)]} /> - {hasReceipt && ( - - - `} /> - - )} + {hasReceipt && } {hasReceipts && ( - ({source: receipt.source, filename}))} + ReceiptUtils.getThumbnailAndImageURIs(receipt.source, filename))} size={3} total={ReportActionUtils.getNumberOfMoneyRequests(props.action)} hoverStyle={props.isHovered || isScanning ? styles.reportPreviewBoxHoverBorder : undefined} diff --git a/src/components/ReceiptImages.js b/src/components/ReportActionItemImages.js similarity index 65% rename from src/components/ReceiptImages.js rename to src/components/ReportActionItemImages.js index 53e0cf99958c..ae63360fbf80 100644 --- a/src/components/ReceiptImages.js +++ b/src/components/ReportActionItemImages.js @@ -3,22 +3,21 @@ import {View} from 'react-native'; import PropTypes from 'prop-types'; import _ from 'underscore'; import styles from '../styles/styles'; -import * as ReceiptUtils from '../libs/ReceiptUtils'; import RenderHTML from './RenderHTML'; import stylePropTypes from '../styles/stylePropTypes'; import Text from './Text'; const propTypes = { - /** array of image URIs */ + /** array of image and thumbnail URIs */ images: PropTypes.arrayOf(PropTypes.shape({ - source: PropTypes.string, - filename: PropTypes.string, + thumbnail: PropTypes.string, + image: PropTypes.string, })), /** max number of images to show in the row */ size: PropTypes.number, - /** optional: total number of images */ + /** optional: total number of images if different than images prop length */ total: PropTypes.number, hoverStyle: stylePropTypes, @@ -26,24 +25,23 @@ const propTypes = { const defaultProps = { images: [], - size: 0, + size: 3, total: 0, hoverStyle: {}, }; -function ReceiptImages(props) { +function ReportActionItemImages(props) { const images = props.images.slice(0, props.size); - const remaining = props.total - props.size; + const remaining = (props.total || props.images.length) - props.size; return ( - - {_.map(images, ({source, filename}, index) => { - const {thumbnail, image} = ReceiptUtils.getThumbnailAndImageURIs(source, filename); + + {_.map(images, ({thumbnail, image}, index) => { const isLastImage = index === props.size - 1; return ( {thumbnail ? } {isLastImage && remaining > 0 && ( - + +{remaining} )} @@ -68,8 +66,8 @@ function ReceiptImages(props) { ) } -ReceiptImages.propTypes = propTypes; -ReceiptImages.defaultProps = defaultProps; -ReceiptImages.displayName = 'ReceiptImages'; +ReportActionItemImages.propTypes = propTypes; +ReportActionItemImages.defaultProps = defaultProps; +ReportActionItemImages.displayName = 'ReportActionItemImages'; -export default ReceiptImages; \ No newline at end of file +export default ReportActionItemImages; \ No newline at end of file diff --git a/src/styles/styles.js b/src/styles/styles.js index bf2dfa945622..ac720e9c038a 100644 --- a/src/styles/styles.js +++ b/src/styles/styles.js @@ -3666,7 +3666,16 @@ const styles = { width: '100%', }, - reportPreviewBoxReceipts: { + reportPreviewBoxHoverBorder: { + borderColor: themeColors.border, + backgroundColor: themeColors.border, + }, + + reportPreviewBoxText: { + padding: 16, + }, + + reportActionItemImages: { flexDirection: 'row', borderWidth: 2, borderColor: themeColors.cardBG, @@ -3676,7 +3685,7 @@ const styles = { height: 200, }, - reportPreviewBoxReceipt: { + reportActionItemImage: { borderWidth: 1, borderColor: themeColors.cardBG, flex: 1, @@ -3687,7 +3696,7 @@ const styles = { alignItems: 'center', }, - reportPreviewBoxReceiptsMore: { + reportActionItemImagesMore: { position: 'absolute', borderRadius: '50%', backgroundColor: themeColors.cardBG, @@ -3698,25 +3707,6 @@ const styles = { alignItems: 'center', }, - reportPreviewBoxHoverBorder: { - borderColor: themeColors.border, - backgroundColor: themeColors.border, - }, - - reportPreviewBoxText: { - padding: 16, - }, - - moneyRequestViewReceipt: { - marginHorizontal: 20, - marginVertical: 8, - borderWidth: 2, - borderColor: themeColors.border, - borderRadius: variables.componentBorderRadiusCard, - overflow: 'hidden', - height: 300, - }, - headerStatusBarBadge: { padding: 8, borderRadius: variables.componentBorderRadiusMedium, From ee8e83da7ecd3d8de78d80e4d9ccc4dd3fce0cc4 Mon Sep 17 00:00:00 2001 From: Andrew Li Date: Mon, 14 Aug 2023 13:55:07 -0400 Subject: [PATCH 26/61] Fix refactor of MoneyRequestImage --- .../ReportActionItem/MoneyRequestImage.js | 32 +++++++++++++++++++ .../ReportActionItem/MoneyRequestPreview.js | 27 ++++------------ .../ReportActionItem/MoneyRequestView.js | 4 +-- .../ReportActionItemImages.js | 8 ++--- .../ReportActionItem/ReportPreview.js | 4 +-- src/styles/styles.js | 19 +++++------ 6 files changed, 57 insertions(+), 37 deletions(-) create mode 100644 src/components/ReportActionItem/MoneyRequestImage.js rename src/components/{ => ReportActionItem}/ReportActionItemImages.js (93%) diff --git a/src/components/ReportActionItem/MoneyRequestImage.js b/src/components/ReportActionItem/MoneyRequestImage.js new file mode 100644 index 000000000000..3186e0108291 --- /dev/null +++ b/src/components/ReportActionItem/MoneyRequestImage.js @@ -0,0 +1,32 @@ +import React from 'react'; +import PropTypes from 'prop-types'; +import {View} from 'react-native'; +import styles from '../../styles/styles'; +import RenderHTML from '../RenderHTML'; + +const propTypes = { + /** array of image and thumbnail URIs */ + image: PropTypes.shape({ + thumbnail: PropTypes.string, + image: PropTypes.string, + }).isRequired, +}; + +function MoneyRequestImage(props) { + return ( + + + `} /> + + ) +} + +MoneyRequestImage.propTypes = propTypes; +MoneyRequestImage.displayName = 'MoneyRequestImage'; + +export default MoneyRequestImage; \ No newline at end of file diff --git a/src/components/ReportActionItem/MoneyRequestPreview.js b/src/components/ReportActionItem/MoneyRequestPreview.js index 2edf6445c3c1..6182583023e9 100644 --- a/src/components/ReportActionItem/MoneyRequestPreview.js +++ b/src/components/ReportActionItem/MoneyRequestPreview.js @@ -31,6 +31,7 @@ import PressableWithFeedback from '../Pressable/PressableWithoutFeedback'; import * as ReportActionUtils from '../../libs/ReportActionsUtils'; import * as TransactionUtils from '../../libs/TransactionUtils'; import * as ReceiptUtils from '../../libs/ReceiptUtils'; +import ReportActionItemImages from './ReportActionItemImages'; import RenderHTML from '../RenderHTML'; import Image from '../Image'; @@ -195,23 +196,6 @@ function MoneyRequestPreview(props) { return CurrencyUtils.convertToDisplayString(requestAmount, requestCurrency); } - const renderReceipt = ({receipt, filename}) => { - const {thumbnail, image} = ReceiptUtils.getThumbnailAndImageURIs(receipt.source, filename); - if (thumbnail) { - return ( - - `} /> - ); - } - return (); - } - const childContainer = ( {hasReceipt && ( - - {renderReceipt(transaction)} - + )} diff --git a/src/components/ReportActionItem/MoneyRequestView.js b/src/components/ReportActionItem/MoneyRequestView.js index 5bccbbdd90a0..b01226df0d35 100644 --- a/src/components/ReportActionItem/MoneyRequestView.js +++ b/src/components/ReportActionItem/MoneyRequestView.js @@ -25,8 +25,8 @@ import useLocalize from '../../hooks/useLocalize'; import * as TransactionUtils from '../../libs/TransactionUtils'; import * as ReceiptUtils from '../../libs/ReceiptUtils'; import withLocalize from '../withLocalize'; -import ReportActionItemImages from '../ReportActionItemImages'; import useWindowDimensions from '../../hooks/useWindowDimensions'; +import MoneyRequestImage from './MoneyRequestImage'; const propTypes = { /** The report currently being looked at */ @@ -103,7 +103,7 @@ function MoneyRequestView({report, parentReport, shouldShowHorizontalRule, polic style={[StyleUtils.getReportWelcomeBackgroundImageStyle(true)]} /> - {hasReceipt && } + {hasReceipt && } - + {hasReceipts && ( ReceiptUtils.getThumbnailAndImageURIs(receipt.source, filename))} diff --git a/src/styles/styles.js b/src/styles/styles.js index 3c92a5563431..bf63fa07568f 100644 --- a/src/styles/styles.js +++ b/src/styles/styles.js @@ -2650,15 +2650,6 @@ const styles = { width: '100%', }, - moneyRequestPreviewReceipt: { - borderWidth: 2, - borderColor: themeColors.cardBG, - borderTopLeftRadius: variables.componentBorderRadiusLarge, - borderTopRightRadius: variables.componentBorderRadiusLarge, - overflow: 'hidden', - height: 200, - }, - moneyRequestPreviewBoxText: { padding: 16, }, @@ -3744,6 +3735,16 @@ const styles = { rotate90: { transform: [{rotate: '90deg'}], }, + + moneyRequestViewImage: { + ...spacing.mh5, + ...spacing.mv3, + overflow: 'hidden', + borderWidth: 2, + borderColor: themeColors.cardBG, + borderRadius: variables.componentBorderRadiusLarge, + height: 200, + } }; export default styles; From acb2956ff975a2da464aed99e14b554966adeee8 Mon Sep 17 00:00:00 2001 From: Andrew Li Date: Mon, 14 Aug 2023 14:52:35 -0400 Subject: [PATCH 27/61] Show comment and request count info correctly --- .../ReportActionItem/MoneyRequestPreview.js | 5 ++++ .../ReportActionItemImages.js | 1 + .../ReportActionItem/ReportPreview.js | 29 ++++++------------- src/languages/en.js | 1 - src/languages/es.js | 1 - src/libs/TransactionUtils.js | 1 + 6 files changed, 16 insertions(+), 22 deletions(-) diff --git a/src/components/ReportActionItem/MoneyRequestPreview.js b/src/components/ReportActionItem/MoneyRequestPreview.js index 6182583023e9..e13ce8561fee 100644 --- a/src/components/ReportActionItem/MoneyRequestPreview.js +++ b/src/components/ReportActionItem/MoneyRequestPreview.js @@ -258,6 +258,11 @@ function MoneyRequestPreview(props) { )} + {moneyRequestAction.merchant && ( + + {moneyRequestAction.merchant} + + )} {!isCurrentUserManager && props.shouldShowPendingConversionMessage && ( diff --git a/src/components/ReportActionItem/ReportActionItemImages.js b/src/components/ReportActionItem/ReportActionItemImages.js index daf72391c569..6ccc47738568 100644 --- a/src/components/ReportActionItem/ReportActionItemImages.js +++ b/src/components/ReportActionItem/ReportActionItemImages.js @@ -6,6 +6,7 @@ import styles from '../../styles/styles'; import RenderHTML from '../RenderHTML'; import stylePropTypes from '../../styles/stylePropTypes'; import Text from '../Text'; +import Image from '../Image'; const propTypes = { /** array of image and thumbnail URIs */ diff --git a/src/components/ReportActionItem/ReportPreview.js b/src/components/ReportActionItem/ReportPreview.js index af6f3960653b..a5ae3d156c3b 100644 --- a/src/components/ReportActionItem/ReportPreview.js +++ b/src/components/ReportActionItem/ReportPreview.js @@ -98,19 +98,24 @@ const defaultProps = { function ReportPreview(props) { const managerID = props.iouReport.managerID || props.action.actorAccountID || 0; const isCurrentUserManager = managerID === lodashGet(props.session, 'accountID'); - const moneyRequestCount = lodashGet(props.action, 'childMoneyRequestCount', 0); - const moneyRequestComment = lodashGet(props.action, 'childLastMoneyRequestComment', ''); - const showComment = moneyRequestComment || moneyRequestCount > 1; const reportTotal = ReportUtils.getMoneyRequestTotal(props.iouReport); const iouSettled = ReportUtils.isSettled(props.iouReportID); const numberOfRequests = ReportActionUtils.getNumberOfMoneyRequests(props.action); const numberOfScanningReceipts = ReportActionUtils.getNumberOfScanningReceipts(props.iouReport); + const moneyRequestComment = lodashGet(props.action, 'childLastMoneyRequestComment', ''); const transactions = ReportActionUtils.getReportPreviewTransactionsWithReceipts(props.action); const hasReceipts = transactions.length > 0; const isScanning = hasReceipts && !ReportActionUtils.hasReadyMoneyRequests(props.action); const hasOnlyOneReceiptRequest = numberOfRequests === 1 && hasReceipts; + console.log(numberOfScanningReceipts); + const previewSubtitleIfNoComment = hasOnlyOneReceiptRequest + ? transactions[0].merchant + : props.translate('iou.requestCount', { + count: numberOfRequests, + scanningReceipts: numberOfScanningReceipts, + }); let displayAmount; if (reportTotal) { @@ -185,27 +190,11 @@ function ReportPreview(props) { {hasReceipts && !isScanning && ( - { - hasOnlyOneReceiptRequest - ? transactions[0].merchant - : props.translate('iou.requestCount', { - count: numberOfRequests, - scanningReceipts: numberOfScanningReceipts, - }) - } + {moneyRequestComment || previewSubtitleIfNoComment} )} - {showComment && ( - - - - {moneyRequestCount > 1 ? props.translate('iou.requestCount', {count: moneyRequestCount}) : moneyRequestComment} - - - - )} {shouldShowSettlementButton && ( `${formattedAmount} request${comment ? ` for ${comment}` : ''}`, threadSentMoneyReportName: ({formattedAmount, comment}) => `${formattedAmount} sent${comment ? ` for ${comment}` : ''}`, - requestCount: ({count}) => `${count} requests`, error: { invalidSplit: 'Split amounts do not equal total amount', other: 'Unexpected error, please try again later', diff --git a/src/languages/es.js b/src/languages/es.js index f286592e5a69..3d11c20d588b 100644 --- a/src/languages/es.js +++ b/src/languages/es.js @@ -411,7 +411,6 @@ export default { pendingConversionMessage: 'El total se actualizará cuando estés online', threadRequestReportName: ({formattedAmount, comment}) => `Solicitud de ${formattedAmount}${comment ? ` para ${comment}` : ''}`, threadSentMoneyReportName: ({formattedAmount, comment}) => `${formattedAmount} enviado${comment ? ` para ${comment}` : ''}`, - requestCount: ({count}) => `${count} solicitudes`, error: { invalidSplit: 'La suma de las partes no equivale al monto total', other: 'Error inesperado, por favor inténtalo más tarde', diff --git a/src/libs/TransactionUtils.js b/src/libs/TransactionUtils.js index 7a636b8ab8e3..d8870f64af1a 100644 --- a/src/libs/TransactionUtils.js +++ b/src/libs/TransactionUtils.js @@ -30,6 +30,7 @@ Onyx.connect({ * @param {String} [originalTransactionID] * @param {String} [merchant] * @param {Object} [receipt] + * @param {String} [filename] * @returns {Object} */ function buildOptimisticTransaction(amount, currency, reportID, comment = '', source = '', originalTransactionID = '', merchant = CONST.REPORT.TYPE.IOU, receipt = {}, filename = '') { From 5d61c873333bfc8401f3d3073244adb28b6a7270 Mon Sep 17 00:00:00 2001 From: Andrew Li Date: Mon, 14 Aug 2023 15:23:56 -0400 Subject: [PATCH 28/61] Fix linting and dependency cycle --- src/components/MoneyRequestHeader.js | 2 +- src/components/ReportActionItem/MoneyRequestPreview.js | 2 -- src/components/ReportActionItem/ReportPreview.js | 1 - src/languages/en.js | 5 ++--- src/languages/es.js | 5 ++--- .../report/ContextMenu/PopoverReportActionContextMenu.js | 4 ++-- 6 files changed, 7 insertions(+), 12 deletions(-) diff --git a/src/components/MoneyRequestHeader.js b/src/components/MoneyRequestHeader.js index feddd69cd274..f0dfae1c771b 100644 --- a/src/components/MoneyRequestHeader.js +++ b/src/components/MoneyRequestHeader.js @@ -84,7 +84,7 @@ function MoneyRequestHeader(props) { threeDotsMenuItems={[ { icon: Expensicons.Trashcan, - text: translate('reportActionContextMenu.deleteAction', {action: parentReportAction}), + text: translate('reportActionContextMenu.deleteAction', {isMoneyRequest: ReportActionsUtils.isMoneyRequestAction(parentReportAction)}), onSelected: () => setIsDeleteModalVisible(true), }, ]} diff --git a/src/components/ReportActionItem/MoneyRequestPreview.js b/src/components/ReportActionItem/MoneyRequestPreview.js index e13ce8561fee..e488d7ec6cd2 100644 --- a/src/components/ReportActionItem/MoneyRequestPreview.js +++ b/src/components/ReportActionItem/MoneyRequestPreview.js @@ -32,8 +32,6 @@ import * as ReportActionUtils from '../../libs/ReportActionsUtils'; import * as TransactionUtils from '../../libs/TransactionUtils'; import * as ReceiptUtils from '../../libs/ReceiptUtils'; import ReportActionItemImages from './ReportActionItemImages'; -import RenderHTML from '../RenderHTML'; -import Image from '../Image'; const propTypes = { /** The active IOUReport, used for Onyx subscription */ diff --git a/src/components/ReportActionItem/ReportPreview.js b/src/components/ReportActionItem/ReportPreview.js index a5ae3d156c3b..ad8e94105b9a 100644 --- a/src/components/ReportActionItem/ReportPreview.js +++ b/src/components/ReportActionItem/ReportPreview.js @@ -109,7 +109,6 @@ function ReportPreview(props) { const hasReceipts = transactions.length > 0; const isScanning = hasReceipts && !ReportActionUtils.hasReadyMoneyRequests(props.action); const hasOnlyOneReceiptRequest = numberOfRequests === 1 && hasReceipts; - console.log(numberOfScanningReceipts); const previewSubtitleIfNoComment = hasOnlyOneReceiptRequest ? transactions[0].merchant : props.translate('iou.requestCount', { diff --git a/src/languages/en.js b/src/languages/en.js index e18979a36228..88143966b0b8 100755 --- a/src/languages/en.js +++ b/src/languages/en.js @@ -1,6 +1,5 @@ import {CONST as COMMON_CONST} from 'expensify-common/lib/CONST'; import CONST from '../CONST'; -import * as ReportActionsUtils from '../libs/ReportActionsUtils'; /* eslint-disable max-len */ export default { @@ -276,8 +275,8 @@ export default { markAsUnread: 'Mark as unread', markAsRead: 'Mark as read', editComment: 'Edit comment', - deleteAction: ({action}) => `Delete ${ReportActionsUtils.isMoneyRequestAction(action) ? 'request' : 'comment'}`, - deleteConfirmation: ({action}) => `Are you sure you want to delete this ${ReportActionsUtils.isMoneyRequestAction(action) ? 'request' : 'comment'}?`, + deleteAction: ({isMoneyRequest}) => `Delete ${isMoneyRequest ? 'request' : 'comment'}`, + deleteConfirmation: ({isMoneyRequest}) => `Are you sure you want to delete this ${isMoneyRequest ? 'request' : 'comment'}?`, onlyVisible: 'Only visible to', replyInThread: 'Reply in thread', flagAsOffensive: 'Flag as offensive', diff --git a/src/languages/es.js b/src/languages/es.js index 3d11c20d588b..801baca1ec89 100644 --- a/src/languages/es.js +++ b/src/languages/es.js @@ -1,5 +1,4 @@ import CONST from '../CONST'; -import * as ReportActionsUtils from '../libs/ReportActionsUtils'; /* eslint-disable max-len */ export default { @@ -275,8 +274,8 @@ export default { markAsUnread: 'Marcar como no leído', markAsRead: 'Marcar como leído', editComment: 'Editar comentario', - deleteAction: ({action}) => `Eliminar ${ReportActionsUtils.isMoneyRequestAction(action) ? 'pedido' : 'comentario'}`, - deleteConfirmation: ({action}) => `¿Estás seguro de que quieres eliminar este ${ReportActionsUtils.isMoneyRequestAction(action) ? 'pedido' : 'comentario'}`, + deleteAction: ({isMoneyRequest = false}) => `Eliminar ${isMoneyRequest ? 'pedido' : 'comentario'}`, + deleteConfirmation: ({isMoneyRequest = false}) => `¿Estás seguro de que quieres eliminar este ${isMoneyRequest ? 'pedido' : 'comentario'}`, onlyVisible: 'Visible sólo para', replyInThread: 'Responder en el hilo', flagAsOffensive: 'Marcar como ofensivo', diff --git a/src/pages/home/report/ContextMenu/PopoverReportActionContextMenu.js b/src/pages/home/report/ContextMenu/PopoverReportActionContextMenu.js index 81858564b416..9f2ea19f3711 100644 --- a/src/pages/home/report/ContextMenu/PopoverReportActionContextMenu.js +++ b/src/pages/home/report/ContextMenu/PopoverReportActionContextMenu.js @@ -322,7 +322,7 @@ class PopoverReportActionContextMenu extends React.Component { /> Date: Mon, 14 Aug 2023 15:38:34 -0400 Subject: [PATCH 29/61] Run prettier --- src/components/HeaderWithBackButton/index.js | 17 ++++++++- src/components/MoneyRequestHeader.js | 2 +- .../ReportActionItem/MoneyRequestImage.js | 10 +++-- .../ReportActionItem/MoneyRequestPreview.js | 2 +- .../ReportActionItemImages.js | 30 +++++++++------ .../ReportActionItem/ReportPreview.js | 9 ++--- src/components/ThumbnailImage.js | 4 +- src/languages/en.js | 2 +- src/libs/ReceiptUtils.js | 11 ++---- src/libs/ReportActionsUtils.js | 37 +++++++++++-------- src/libs/TransactionUtils.js | 11 +----- src/libs/fileDownload/FileUtils.js | 2 +- src/styles/styles.js | 2 +- 13 files changed, 76 insertions(+), 63 deletions(-) diff --git a/src/components/HeaderWithBackButton/index.js b/src/components/HeaderWithBackButton/index.js index 6577a91b24cd..83c18978587e 100755 --- a/src/components/HeaderWithBackButton/index.js +++ b/src/components/HeaderWithBackButton/index.js @@ -165,8 +165,21 @@ function HeaderWithBackButton({ {shouldShowStatusBar && ( - - + + {statusBarBadgeText} diff --git a/src/components/MoneyRequestHeader.js b/src/components/MoneyRequestHeader.js index f0dfae1c771b..5196fc24f23a 100644 --- a/src/components/MoneyRequestHeader.js +++ b/src/components/MoneyRequestHeader.js @@ -19,7 +19,7 @@ import * as IOU from '../libs/actions/IOU'; import * as ReportActionsUtils from '../libs/ReportActionsUtils'; import ConfirmModal from './ConfirmModal'; import useLocalize from '../hooks/useLocalize'; -import withLocalize, { withLocalizePropTypes } from './withLocalize'; +import withLocalize, {withLocalizePropTypes} from './withLocalize'; const propTypes = { /** The report currently being looked at */ diff --git a/src/components/ReportActionItem/MoneyRequestImage.js b/src/components/ReportActionItem/MoneyRequestImage.js index 3186e0108291..82b927658a29 100644 --- a/src/components/ReportActionItem/MoneyRequestImage.js +++ b/src/components/ReportActionItem/MoneyRequestImage.js @@ -15,18 +15,20 @@ const propTypes = { function MoneyRequestImage(props) { return ( - - `} /> + `} + /> - ) + ); } MoneyRequestImage.propTypes = propTypes; MoneyRequestImage.displayName = 'MoneyRequestImage'; -export default MoneyRequestImage; \ No newline at end of file +export default MoneyRequestImage; diff --git a/src/components/ReportActionItem/MoneyRequestPreview.js b/src/components/ReportActionItem/MoneyRequestPreview.js index e488d7ec6cd2..e5a53be6c46a 100644 --- a/src/components/ReportActionItem/MoneyRequestPreview.js +++ b/src/components/ReportActionItem/MoneyRequestPreview.js @@ -192,7 +192,7 @@ function MoneyRequestPreview(props) { } return CurrencyUtils.convertToDisplayString(requestAmount, requestCurrency); - } + }; const childContainer = ( diff --git a/src/components/ReportActionItem/ReportActionItemImages.js b/src/components/ReportActionItem/ReportActionItemImages.js index 6ccc47738568..34e9c0b14979 100644 --- a/src/components/ReportActionItem/ReportActionItemImages.js +++ b/src/components/ReportActionItem/ReportActionItemImages.js @@ -10,10 +10,12 @@ import Image from '../Image'; const propTypes = { /** array of image and thumbnail URIs */ - images: PropTypes.arrayOf(PropTypes.shape({ - thumbnail: PropTypes.string, - image: PropTypes.string, - })), + images: PropTypes.arrayOf( + PropTypes.shape({ + thumbnail: PropTypes.string, + image: PropTypes.string, + }), + ), /** max number of images to show in the row */ size: PropTypes.number, @@ -44,17 +46,23 @@ function ReportActionItemImages(props) { key={image} style={[styles.reportActionItemImage, props.hoverStyle]} > - {thumbnail - ? - `} /> - : - } + `} + /> + ) : ( + + )} {isLastImage && remaining > 0 && ( +{remaining} @@ -64,11 +72,11 @@ function ReportActionItemImages(props) { ); })} - ) + ); } ReportActionItemImages.propTypes = propTypes; ReportActionItemImages.defaultProps = defaultProps; ReportActionItemImages.displayName = 'ReportActionItemImages'; -export default ReportActionItemImages; \ No newline at end of file +export default ReportActionItemImages; diff --git a/src/components/ReportActionItem/ReportPreview.js b/src/components/ReportActionItem/ReportPreview.js index ad8e94105b9a..e525837fcd83 100644 --- a/src/components/ReportActionItem/ReportPreview.js +++ b/src/components/ReportActionItem/ReportPreview.js @@ -112,9 +112,9 @@ function ReportPreview(props) { const previewSubtitleIfNoComment = hasOnlyOneReceiptRequest ? transactions[0].merchant : props.translate('iou.requestCount', { - count: numberOfRequests, - scanningReceipts: numberOfScanningReceipts, - }); + count: numberOfRequests, + scanningReceipts: numberOfScanningReceipts, + }); let displayAmount; if (reportTotal) { @@ -142,8 +142,7 @@ function ReportPreview(props) { } const bankAccountRoute = ReportUtils.getBankAccountRoute(props.chatReport); - const shouldShowSettlementButton = - !_.isEmpty(props.iouReport) && isCurrentUserManager && !iouSettled && !props.iouReport.isWaitingOnBankAccount && reportTotal !== 0; + const shouldShowSettlementButton = !_.isEmpty(props.iouReport) && isCurrentUserManager && !iouSettled && !props.iouReport.isWaitingOnBankAccount && reportTotal !== 0; return ( diff --git a/src/components/ThumbnailImage.js b/src/components/ThumbnailImage.js index d8cee2734dfc..983f806bb2e2 100644 --- a/src/components/ThumbnailImage.js +++ b/src/components/ThumbnailImage.js @@ -90,9 +90,7 @@ function ThumbnailImage(props) { [windowHeight], ); - const sizeStyles = props.shouldDynamicallyResize - ? [StyleUtils.getWidthAndHeightStyle(imageWidth, imageHeight)] - : [styles.w100, styles.h100]; + const sizeStyles = props.shouldDynamicallyResize ? [StyleUtils.getWidthAndHeightStyle(imageWidth, imageHeight)] : [styles.w100, styles.h100]; return ( diff --git a/src/languages/en.js b/src/languages/en.js index 88143966b0b8..15a277bb5616 100755 --- a/src/languages/en.js +++ b/src/languages/en.js @@ -384,7 +384,7 @@ export default { deleteReceipt: 'Delete receipt', receiptScanning: 'Receipt scan in progress…', receiptStatusTitle: 'Scanning…', - receiptStatusText: 'Only you can see this receipt when it\'s scanning. Check back later or enter the details now.', + receiptStatusText: "Only you can see this receipt when it's scanning. Check back later or enter the details now.", requestCount: ({count, scanningReceipts = 0}) => `${count} requests${scanningReceipts > 0 ? `, ${scanningReceipts} scanning` : ''}`, deleteRequest: 'Delete request', deleteConfirmation: 'Are you sure that you want to delete this request?', diff --git a/src/libs/ReceiptUtils.js b/src/libs/ReceiptUtils.js index e973c85f02d8..635537226815 100644 --- a/src/libs/ReceiptUtils.js +++ b/src/libs/ReceiptUtils.js @@ -28,8 +28,7 @@ function validateReceipt(file) { } return true; -}; - +} /** * Grab the appropriate receipt image and thumbnail URIs based on file type @@ -63,14 +62,10 @@ function getThumbnailAndImageURIs(path, filename) { image = ReceiptSVG; } return {thumbnail: null, image}; -}; +} function isBeingScanned(receipt) { return receipt.state === CONST.IOU.RECEIPT_STATE.SCANREADY || receipt.state === CONST.IOU.RECEIPT_STATE.SCANNING; } -export { - validateReceipt, - getThumbnailAndImageURIs, - isBeingScanned, -}; +export {validateReceipt, getThumbnailAndImageURIs, isBeingScanned}; diff --git a/src/libs/ReportActionsUtils.js b/src/libs/ReportActionsUtils.js index a4cc99040db4..60fdd6d875ff 100644 --- a/src/libs/ReportActionsUtils.js +++ b/src/libs/ReportActionsUtils.js @@ -603,13 +603,17 @@ function isMessageDeleted(reportAction) { */ function getReportPreviewTransactionsWithReceipts(reportPreviewAction) { const transactionIDs = lodashGet(reportPreviewAction, ['childLastReceiptTransactionIDs'], '').split(','); - return _.reduce(transactionIDs, (transactions, transactionID) => { - const transaction = allTransactions[transactionID]; - if (transaction) { - transactions.push(transaction); - } - return transactions; - }, []); + return _.reduce( + transactionIDs, + (transactions, transactionID) => { + const transaction = allTransactions[transactionID]; + if (transaction) { + transactions.push(transaction); + } + return transactions; + }, + [], + ); } /** @@ -621,7 +625,6 @@ function getTransaction(iouReportAction) { return allTransactions[transactionID] || {}; } - /** * Checks if the IOU or expense report has either no smartscanned receipts or at least one is already done scanning * @@ -662,13 +665,17 @@ function getNumberOfMoneyRequests(reportPreviewAction) { */ function getNumberOfScanningReceipts(iouReport) { const reportActions = lodashGet(allReportActions, lodashGet(iouReport, 'reportID'), []); - return _.reduce(reportActions, (count, reportAction) => { - if (!isMoneyRequestAction(reportAction)) { - return count; - } - const transaction = getTransaction(reportAction); - return count + Number(TransactionUtils.hasReceipt(transaction) && ReceiptUtils.isBeingScanned(transaction.receipt)); - }, 0); + return _.reduce( + reportActions, + (count, reportAction) => { + if (!isMoneyRequestAction(reportAction)) { + return count; + } + const transaction = getTransaction(reportAction); + return count + Number(TransactionUtils.hasReceipt(transaction) && ReceiptUtils.isBeingScanned(transaction.receipt)); + }, + 0, + ); } export { diff --git a/src/libs/TransactionUtils.js b/src/libs/TransactionUtils.js index d8870f64af1a..48892a69cc01 100644 --- a/src/libs/TransactionUtils.js +++ b/src/libs/TransactionUtils.js @@ -167,13 +167,4 @@ function getCreated(transaction) { return format(new Date(lodashGet(transaction, 'created', '')), CONST.DATE.FNS_FORMAT_STRING); } -export { - buildOptimisticTransaction, - hasReceipt, - getUpdatedTransaction, - getTransaction, - getDescription, - getAmount, - getCurrency, - getCreated, -}; +export {buildOptimisticTransaction, hasReceipt, getUpdatedTransaction, getTransaction, getDescription, getAmount, getCurrency, getCreated}; diff --git a/src/libs/fileDownload/FileUtils.js b/src/libs/fileDownload/FileUtils.js index 962923241b62..fed41f839688 100644 --- a/src/libs/fileDownload/FileUtils.js +++ b/src/libs/fileDownload/FileUtils.js @@ -152,7 +152,7 @@ const readFileAsync = (path, fileName) => return res.blob(); }) .then((blob) => { - const cleanName = cleanFileName(fileName) + const cleanName = cleanFileName(fileName); const file = new File([blob], cleanName); file.source = path; resolve(file); diff --git a/src/styles/styles.js b/src/styles/styles.js index bf63fa07568f..d1075cce40ca 100644 --- a/src/styles/styles.js +++ b/src/styles/styles.js @@ -3744,7 +3744,7 @@ const styles = { borderColor: themeColors.cardBG, borderRadius: variables.componentBorderRadiusLarge, height: 200, - } + }, }; export default styles; From c039cd3b7d93ff22ea5f194f664678bc01afb5a7 Mon Sep 17 00:00:00 2001 From: Andrew Li Date: Mon, 14 Aug 2023 17:02:24 -0400 Subject: [PATCH 30/61] Update optimistic report preview data --- src/libs/ReportUtils.js | 6 +++++- src/libs/TransactionUtils.js | 2 +- src/libs/actions/IOU.js | 2 +- 3 files changed, 7 insertions(+), 3 deletions(-) diff --git a/src/libs/ReportUtils.js b/src/libs/ReportUtils.js index fba5d12c7046..0249ebfaec7b 100644 --- a/src/libs/ReportUtils.js +++ b/src/libs/ReportUtils.js @@ -1942,10 +1942,12 @@ function buildOptimisticIOUReportAction( * @param {Object} chatReport * @param {Object} iouReport * @param {String} [comment] - User comment for the IOU. + * @param {Object} [transaction] - optimistic first transaction of preview * * @returns {Object} */ -function buildOptimisticReportPreview(chatReport, iouReport, comment = '') { +function buildOptimisticReportPreview(chatReport, iouReport, comment = '', transaction = undefined) { + const hasReceipt = TransactionUtils.hasReceipt(transaction); const message = getReportPreviewMessage(iouReport); return { reportActionID: NumberUtils.rand64(), @@ -1968,6 +1970,8 @@ function buildOptimisticReportPreview(chatReport, iouReport, comment = '') { actorAccountID: iouReport.managerID || 0, childMoneyRequestCount: 1, childLastMoneyRequestComment: comment, + childLastReceiptTransactionIDs: hasReceipt ? transaction.transactionID : '', + whisperedToAccountIDs: iouReport.managerID && hasReceipt ? [iouReport.managerID] : [], }; } diff --git a/src/libs/TransactionUtils.js b/src/libs/TransactionUtils.js index 48892a69cc01..cd7e7922b3fa 100644 --- a/src/libs/TransactionUtils.js +++ b/src/libs/TransactionUtils.js @@ -65,7 +65,7 @@ function buildOptimisticTransaction(amount, currency, reportID, comment = '', so * @returns {Boolean} */ function hasReceipt(transaction) { - return !_.isEmpty(transaction.receipt); + return !_.isEmpty(lodashGet(transaction, 'receipt')); } /** diff --git a/src/libs/actions/IOU.js b/src/libs/actions/IOU.js index a7a199c1477a..3c704c78877d 100644 --- a/src/libs/actions/IOU.js +++ b/src/libs/actions/IOU.js @@ -404,7 +404,7 @@ function requestMoney(report, amount, currency, payeeEmail, payeeAccountID, part if (reportPreviewAction) { reportPreviewAction = ReportUtils.updateReportPreview(iouReport, reportPreviewAction, comment); } else { - reportPreviewAction = ReportUtils.buildOptimisticReportPreview(chatReport, iouReport, comment); + reportPreviewAction = ReportUtils.buildOptimisticReportPreview(chatReport, iouReport, comment, optimisticTransaction); } // STEP 5: Build Onyx Data From b20f2a51c8d8afa2ff18960edb88ef3e67da710d Mon Sep 17 00:00:00 2001 From: Andrew Li Date: Mon, 14 Aug 2023 18:03:36 -0400 Subject: [PATCH 31/61] Add optimistic data for updating the report preview --- src/libs/ReportUtils.js | 9 ++++++++- 1 file changed, 8 insertions(+), 1 deletion(-) diff --git a/src/libs/ReportUtils.js b/src/libs/ReportUtils.js index 0249ebfaec7b..05d266509f9c 100644 --- a/src/libs/ReportUtils.js +++ b/src/libs/ReportUtils.js @@ -2022,10 +2022,16 @@ function buildOptimisticModifiedExpenseReportAction(transactionThread, oldTransa * @param {Object} iouReport * @param {Object} reportPreviewAction * @param {String} [comment] - User comment for the IOU. + * @param {Object} [transaction] - optimistic newest transaction of a report preview * * @returns {Object} */ -function updateReportPreview(iouReport, reportPreviewAction, comment = '') { +function updateReportPreview(iouReport, reportPreviewAction, comment = '', transaction = undefined) { + const hasReceipt = TransactionUtils.hasReceipt(transaction); + const previousIDs = lodashGet(reportPreviewAction, 'childLastReceiptTransactionIDs', '').split(',').slice(0, 2); + const newTransactions = hasReceipt + ? { childLastReceiptTransactionIDs: [transaction.transactionID, ...previousIDs] } + : {}; const message = getReportPreviewMessage(iouReport, reportPreviewAction); return { ...reportPreviewAction, @@ -2040,6 +2046,7 @@ function updateReportPreview(iouReport, reportPreviewAction, comment = '') { ], childLastMoneyRequestComment: comment || reportPreviewAction.childLastMoneyRequestComment, childMoneyRequestCount: reportPreviewAction.childMoneyRequestCount + 1, + ...newTransactions, }; } From 12536f9a5cad7a145c6d11b1299007fe180759ac Mon Sep 17 00:00:00 2001 From: Andrew Li Date: Tue, 15 Aug 2023 00:06:43 -0400 Subject: [PATCH 32/61] Remove MoneyRequestImage and update optimistic data --- .../ReportActionItem/MoneyRequestImage.js | 34 ------------------- .../ReportActionItem/MoneyRequestPreview.js | 2 -- .../ReportActionItem/MoneyRequestView.js | 23 ++++++++++--- .../ReportActionItemImages.js | 16 ++++----- src/libs/ReportUtils.js | 10 +++--- src/libs/actions/IOU.js | 2 +- 6 files changed, 31 insertions(+), 56 deletions(-) delete mode 100644 src/components/ReportActionItem/MoneyRequestImage.js diff --git a/src/components/ReportActionItem/MoneyRequestImage.js b/src/components/ReportActionItem/MoneyRequestImage.js deleted file mode 100644 index 82b927658a29..000000000000 --- a/src/components/ReportActionItem/MoneyRequestImage.js +++ /dev/null @@ -1,34 +0,0 @@ -import React from 'react'; -import PropTypes from 'prop-types'; -import {View} from 'react-native'; -import styles from '../../styles/styles'; -import RenderHTML from '../RenderHTML'; - -const propTypes = { - /** array of image and thumbnail URIs */ - image: PropTypes.shape({ - thumbnail: PropTypes.string, - image: PropTypes.string, - }).isRequired, -}; - -function MoneyRequestImage(props) { - return ( - - - `} - /> - - ); -} - -MoneyRequestImage.propTypes = propTypes; -MoneyRequestImage.displayName = 'MoneyRequestImage'; - -export default MoneyRequestImage; diff --git a/src/components/ReportActionItem/MoneyRequestPreview.js b/src/components/ReportActionItem/MoneyRequestPreview.js index e5a53be6c46a..be2a2495a229 100644 --- a/src/components/ReportActionItem/MoneyRequestPreview.js +++ b/src/components/ReportActionItem/MoneyRequestPreview.js @@ -210,8 +210,6 @@ function MoneyRequestPreview(props) { {hasReceipt && ( )} diff --git a/src/components/ReportActionItem/MoneyRequestView.js b/src/components/ReportActionItem/MoneyRequestView.js index b01226df0d35..f2d050cacf38 100644 --- a/src/components/ReportActionItem/MoneyRequestView.js +++ b/src/components/ReportActionItem/MoneyRequestView.js @@ -1,5 +1,5 @@ import React from 'react'; -import {View, Image} from 'react-native'; +import {View} from 'react-native'; import {withOnyx} from 'react-native-onyx'; import lodashGet from 'lodash/get'; import PropTypes from 'prop-types'; @@ -26,7 +26,8 @@ import * as TransactionUtils from '../../libs/TransactionUtils'; import * as ReceiptUtils from '../../libs/ReceiptUtils'; import withLocalize from '../withLocalize'; import useWindowDimensions from '../../hooks/useWindowDimensions'; -import MoneyRequestImage from './MoneyRequestImage'; +import Image from '../Image'; +import RenderHTML from '../RenderHTML'; const propTypes = { /** The report currently being looked at */ @@ -89,9 +90,9 @@ function MoneyRequestView({report, parentReport, shouldShowHorizontalRule, polic const transaction = ReportActionsUtils.getTransaction(parentReportAction); const hasReceipt = TransactionUtils.hasReceipt(transaction); - let receiptUris; + let receiptImage; if (hasReceipt) { - receiptUris = ReceiptUtils.getThumbnailAndImageURIs(transaction.receipt.source, transaction.filename); + receiptImage = ReceiptUtils.getThumbnailAndImageURIs(transaction.receipt.source, transaction.filename).image; } return ( @@ -103,7 +104,19 @@ function MoneyRequestView({report, parentReport, shouldShowHorizontalRule, polic style={[StyleUtils.getReportWelcomeBackgroundImageStyle(true)]} /> - {hasReceipt && } + {hasReceipt && ( + + + `} + /> + + )} diff --git a/src/libs/ReportUtils.js b/src/libs/ReportUtils.js index 05d266509f9c..8d63b7802a25 100644 --- a/src/libs/ReportUtils.js +++ b/src/libs/ReportUtils.js @@ -1967,11 +1967,11 @@ function buildOptimisticReportPreview(chatReport, iouReport, comment = '', trans ], created: DateUtils.getDBTime(), accountID: iouReport.managerID || 0, - actorAccountID: iouReport.managerID || 0, + actorAccountID: hasReceipt ? currentUserAccountID : iouReport.managerID || 0, childMoneyRequestCount: 1, childLastMoneyRequestComment: comment, childLastReceiptTransactionIDs: hasReceipt ? transaction.transactionID : '', - whisperedToAccountIDs: iouReport.managerID && hasReceipt ? [iouReport.managerID] : [], + whisperedToAccountIDs: hasReceipt ? [currentUserAccountID] : [], }; } @@ -2029,9 +2029,7 @@ function buildOptimisticModifiedExpenseReportAction(transactionThread, oldTransa function updateReportPreview(iouReport, reportPreviewAction, comment = '', transaction = undefined) { const hasReceipt = TransactionUtils.hasReceipt(transaction); const previousIDs = lodashGet(reportPreviewAction, 'childLastReceiptTransactionIDs', '').split(',').slice(0, 2); - const newTransactions = hasReceipt - ? { childLastReceiptTransactionIDs: [transaction.transactionID, ...previousIDs] } - : {}; + const message = getReportPreviewMessage(iouReport, reportPreviewAction); return { ...reportPreviewAction, @@ -2046,7 +2044,7 @@ function updateReportPreview(iouReport, reportPreviewAction, comment = '', trans ], childLastMoneyRequestComment: comment || reportPreviewAction.childLastMoneyRequestComment, childMoneyRequestCount: reportPreviewAction.childMoneyRequestCount + 1, - ...newTransactions, + childLastReceiptTransactionIDs: hasReceipt ? [transaction.transactionID, ...previousIDs].join(',') : '', }; } diff --git a/src/libs/actions/IOU.js b/src/libs/actions/IOU.js index 3c704c78877d..f8fa456bb4ae 100644 --- a/src/libs/actions/IOU.js +++ b/src/libs/actions/IOU.js @@ -402,7 +402,7 @@ function requestMoney(report, amount, currency, payeeEmail, payeeAccountID, part let reportPreviewAction = isNewIOUReport ? null : ReportActionsUtils.getReportPreviewAction(chatReport.reportID, iouReport.reportID); if (reportPreviewAction) { - reportPreviewAction = ReportUtils.updateReportPreview(iouReport, reportPreviewAction, comment); + reportPreviewAction = ReportUtils.updateReportPreview(iouReport, reportPreviewAction, comment, optimisticTransaction); } else { reportPreviewAction = ReportUtils.buildOptimisticReportPreview(chatReport, iouReport, comment, optimisticTransaction); } From 3c2a7b7539e7698a7b24f5b2e4c1bf3fba996118 Mon Sep 17 00:00:00 2001 From: Andrew Li Date: Tue, 15 Aug 2023 01:59:14 -0400 Subject: [PATCH 33/61] Show number of requests before comment, update optimistic data --- .../ReportActionItem/ReportPreview.js | 4 ++-- src/libs/ReportActionsUtils.js | 24 +++++++++++-------- src/libs/ReportUtils.js | 8 +++++-- 3 files changed, 22 insertions(+), 14 deletions(-) diff --git a/src/components/ReportActionItem/ReportPreview.js b/src/components/ReportActionItem/ReportPreview.js index e525837fcd83..2fd804b4dc7d 100644 --- a/src/components/ReportActionItem/ReportPreview.js +++ b/src/components/ReportActionItem/ReportPreview.js @@ -109,7 +109,7 @@ function ReportPreview(props) { const hasReceipts = transactions.length > 0; const isScanning = hasReceipts && !ReportActionUtils.hasReadyMoneyRequests(props.action); const hasOnlyOneReceiptRequest = numberOfRequests === 1 && hasReceipts; - const previewSubtitleIfNoComment = hasOnlyOneReceiptRequest + const previewSubtitle = hasOnlyOneReceiptRequest ? transactions[0].merchant : props.translate('iou.requestCount', { count: numberOfRequests, @@ -188,7 +188,7 @@ function ReportPreview(props) { {hasReceipts && !isScanning && ( - {moneyRequestComment || previewSubtitleIfNoComment} + {previewSubtitle || moneyRequestComment} )} diff --git a/src/libs/ReportActionsUtils.js b/src/libs/ReportActionsUtils.js index 60fdd6d875ff..45413fdd9a58 100644 --- a/src/libs/ReportActionsUtils.js +++ b/src/libs/ReportActionsUtils.js @@ -625,6 +625,16 @@ function getTransaction(iouReportAction) { return allTransactions[transactionID] || {}; } +/** + * Returns the number of money requests associated with a report preview + * + * @param {Object|null} reportPreviewAction + * @returns {Number} + */ +function getNumberOfMoneyRequests(reportPreviewAction) { + return lodashGet(reportPreviewAction, 'childMoneyRequestCount', 0); +} + /** * Checks if the IOU or expense report has either no smartscanned receipts or at least one is already done scanning * @@ -634,6 +644,10 @@ function getTransaction(iouReportAction) { function hasReadyMoneyRequests(reportAction) { if (isReportPreviewAction(reportAction)) { const transactions = getReportPreviewTransactionsWithReceipts(reportAction); + // If we have more requests than requests with receipts, we have some manual requests + if (getNumberOfMoneyRequests(reportAction) > transactions.length) { + return true; + } return _.some(transactions, (transaction) => !ReceiptUtils.isBeingScanned(transaction.receipt)); } @@ -645,16 +659,6 @@ function hasReadyMoneyRequests(reportAction) { return true; } -/** - * Returns the number of money requests associated with a report preview - * - * @param {Object|null} reportPreviewAction - * @returns {Number} - */ -function getNumberOfMoneyRequests(reportPreviewAction) { - return lodashGet(reportPreviewAction, 'childMoneyRequestCount', 0); -} - /** * Returns the number of receipts associated with an IOU report that are still being scanned. * Note that we search the IOU report for this number, since scanning receipts will be whispers diff --git a/src/libs/ReportUtils.js b/src/libs/ReportUtils.js index 8d63b7802a25..02e03e3ae448 100644 --- a/src/libs/ReportUtils.js +++ b/src/libs/ReportUtils.js @@ -2028,7 +2028,8 @@ function buildOptimisticModifiedExpenseReportAction(transactionThread, oldTransa */ function updateReportPreview(iouReport, reportPreviewAction, comment = '', transaction = undefined) { const hasReceipt = TransactionUtils.hasReceipt(transaction); - const previousIDs = lodashGet(reportPreviewAction, 'childLastReceiptTransactionIDs', '').split(',').slice(0, 2); + const lastReceiptTransactionIDs = lodashGet(reportPreviewAction, 'childLastReceiptTransactionIDs', ''); + const previousIDs = lastReceiptTransactionIDs.split(',').slice(0, 2); const message = getReportPreviewMessage(iouReport, reportPreviewAction); return { @@ -2044,7 +2045,10 @@ function updateReportPreview(iouReport, reportPreviewAction, comment = '', trans ], childLastMoneyRequestComment: comment || reportPreviewAction.childLastMoneyRequestComment, childMoneyRequestCount: reportPreviewAction.childMoneyRequestCount + 1, - childLastReceiptTransactionIDs: hasReceipt ? [transaction.transactionID, ...previousIDs].join(',') : '', + childLastReceiptTransactionIDs: hasReceipt ? [transaction.transactionID, ...previousIDs].join(',') : lastReceiptTransactionIDs, + // As soon as we add a transaction without a receipt to the report, it will have ready money requests, + // so we remove the whisper + whisperedToAccountIDs: hasReceipt ? reportPreviewAction.whisperedToAccountIDs : [], }; } From 2fcf0529ec575e232be5ddd0d4054dd6a5076669 Mon Sep 17 00:00:00 2001 From: Andrew Li Date: Tue, 15 Aug 2023 11:22:03 -0400 Subject: [PATCH 34/61] Add file URI when using drag and drop --- src/pages/iou/ReceiptSelector/index.js | 1 + 1 file changed, 1 insertion(+) diff --git a/src/pages/iou/ReceiptSelector/index.js b/src/pages/iou/ReceiptSelector/index.js index b693b299eac3..c7b469e6ec5a 100644 --- a/src/pages/iou/ReceiptSelector/index.js +++ b/src/pages/iou/ReceiptSelector/index.js @@ -153,6 +153,7 @@ function ReceiptSelector(props) { { const file = lodashGet(e, ['dataTransfer', 'files', 0]); + file.uri = URL.createObjectURL(file); setReceiptAndNavigate(file, props.iou, props.report); }} receiptImageTopPosition={receiptImageTopPosition} From 64e5b37332eea876dfc546c751e4c80107fd2795 Mon Sep 17 00:00:00 2001 From: Andrew Li Date: Tue, 15 Aug 2023 14:28:14 -0400 Subject: [PATCH 35/61] Address comments and update comments and docs --- src/components/AttachmentModal.js | 22 +------ .../extractAttachmentsFromReport.js | 4 +- src/components/MoneyRequestHeader.js | 7 +-- .../ReportActionItem/MoneyRequestPreview.js | 3 +- .../ReportActionItem/MoneyRequestView.js | 2 - .../ReportActionItemImages.js | 26 ++++---- .../ReportActionItem/ReportPreview.js | 40 +++++++------ src/libs/ReportActionsUtils.js | 59 ++++--------------- src/libs/ReportUtils.js | 5 +- src/libs/TransactionUtils.js | 23 +++++++- src/libs/fileDownload/FileUtils.js | 3 +- src/pages/iou/ReceiptSelector/index.js | 4 +- 12 files changed, 83 insertions(+), 115 deletions(-) diff --git a/src/components/AttachmentModal.js b/src/components/AttachmentModal.js index 8845634ed48d..310dbe6d22ff 100755 --- a/src/components/AttachmentModal.js +++ b/src/components/AttachmentModal.js @@ -114,13 +114,13 @@ function AttachmentModal(props) { /** * Keeps the attachment source in sync with the attachment displayed currently in the carousel. - * @param {{ source: String, isAuthTokenRequired: Boolean, file: { name: string } }} attachment + * @param {{ source: String, isAuthTokenRequired: Boolean, file: { name: string }, isReceipt: Boolean }} attachment */ const onNavigate = useCallback( (attachment) => { setSource(attachment.source); setFile(attachment.file); - setIsAttachmentReceipt(attachment.receipt); + setIsAttachmentReceipt(attachment.isReceipt); setIsAuthTokenRequired(attachment.isAuthTokenRequired); onCarouselAttachmentChange(attachment); }, @@ -328,24 +328,6 @@ function AttachmentModal(props) { onDownloadButtonPress={() => downloadAttachment(source)} shouldShowCloseButton={!props.isSmallScreenWidth} shouldShowBackButton={props.isSmallScreenWidth} - shouldShowThreeDotsButton={isAttachmentReceipt} - threeDotsMenuItems={[ - { - icon: Expensicons.Camera, - text: props.translate('common.replace'), - onSelected: () => {}, // TODO - }, - { - icon: Expensicons.Download, - text: props.translate('common.download'), - onSelected: () => downloadAttachment(source), - }, - { - icon: Expensicons.Trashcan, - text: props.translate('iou.deleteReceipt'), - onSelected: () => {}, // TODO - }, - ]} onBackButtonPress={closeModal} onCloseButtonPress={closeModal} /> diff --git a/src/components/Attachments/AttachmentCarousel/extractAttachmentsFromReport.js b/src/components/Attachments/AttachmentCarousel/extractAttachmentsFromReport.js index 8cdfb53b132b..467f7260010f 100644 --- a/src/components/Attachments/AttachmentCarousel/extractAttachmentsFromReport.js +++ b/src/components/Attachments/AttachmentCarousel/extractAttachmentsFromReport.js @@ -32,7 +32,7 @@ function extractAttachmentsFromReport(report, reportActions, source) { source: tryResolveUrlFromApiRoot(expensifySource || attribs.src), isAuthTokenRequired: Boolean(expensifySource), file: {name: attribs[CONST.ATTACHMENT_ORIGINAL_FILENAME_ATTRIBUTE]}, - receipt: false, + isReceipt: false, }); }, }); @@ -52,7 +52,7 @@ function extractAttachmentsFromReport(report, reportActions, source) { source: tryResolveUrlFromApiRoot(image), isAuthTokenRequired: true, file: {name: transaction.filename}, - receipt: true, + isReceipt: true, }); return; } diff --git a/src/components/MoneyRequestHeader.js b/src/components/MoneyRequestHeader.js index 5196fc24f23a..256010e07ef3 100644 --- a/src/components/MoneyRequestHeader.js +++ b/src/components/MoneyRequestHeader.js @@ -19,7 +19,6 @@ import * as IOU from '../libs/actions/IOU'; import * as ReportActionsUtils from '../libs/ReportActionsUtils'; import ConfirmModal from './ConfirmModal'; import useLocalize from '../hooks/useLocalize'; -import withLocalize, {withLocalizePropTypes} from './withLocalize'; const propTypes = { /** The report currently being looked at */ @@ -44,7 +43,6 @@ const propTypes = { }), ...windowDimensionsPropTypes, - ...withLocalizePropTypes, }; const defaultProps = { @@ -96,8 +94,8 @@ function MoneyRequestHeader(props) { onBackButtonPress={() => Navigation.goBack(ROUTES.HOME, false, true)} shouldShowBorderBottom shouldShowStatusBar={isScanning} - statusBarBadgeText={props.translate('iou.receiptStatusTitle')} - statusBarDescription={props.translate('iou.receiptStatusText')} + statusBarBadgeText={translate('iou.receiptStatusTitle')} + statusBarDescription={translate('iou.receiptStatusText')} /> `${ONYXKEYS.COLLECTION.REPORT}${report.parentReportID}`, diff --git a/src/components/ReportActionItem/ReportActionItemImages.js b/src/components/ReportActionItem/ReportActionItemImages.js index 96886601846d..e5f9451d3eab 100644 --- a/src/components/ReportActionItem/ReportActionItemImages.js +++ b/src/components/ReportActionItem/ReportActionItemImages.js @@ -17,11 +17,15 @@ const propTypes = { }), ).isRequired, - /** optional: max number of images to show in the row if different than images length */ + // We're not providing default values for size and total and disabling the ESLint rule + // because we want them to default to the length of images, but we can't set default props + // to be computed from another prop + + /** max number of images to show in the row if different than images length */ // eslint-disable-next-line react/require-default-props size: PropTypes.number, - /** optional: total number of images if different than images length */ + /** total number of images if different than images length */ // eslint-disable-next-line react/require-default-props total: PropTypes.number, @@ -32,19 +36,19 @@ const defaultProps = { hoverStyle: {}, }; -function ReportActionItemImages(props) { - const size = props.size || props.images.length; - const images = props.images.slice(0, size); - const remaining = (props.total || props.images.length) - size; +function ReportActionItemImages({images, size, total, hoverStyle}) { + const numberOfShownImages = size || images.length; + const shownImages = images.slice(0, size); + const remaining = (total || images.length) - size; return ( - - {_.map(images, ({thumbnail, image}, index) => { - const isLastImage = index === props.size - 1; + + {_.map(shownImages, ({thumbnail, image}, index) => { + const isLastImage = index === numberOfShownImages - 1; return ( {thumbnail ? ( )} {isLastImage && remaining > 0 && ( - + +{remaining} )} diff --git a/src/components/ReportActionItem/ReportPreview.js b/src/components/ReportActionItem/ReportPreview.js index 2fd804b4dc7d..ea155a09a1c3 100644 --- a/src/components/ReportActionItem/ReportPreview.js +++ b/src/components/ReportActionItem/ReportPreview.js @@ -27,6 +27,7 @@ import themeColors from '../../styles/themes/default'; import reportPropTypes from '../../pages/reportPropTypes'; import * as ReceiptUtils from '../../libs/ReceiptUtils'; import * as ReportActionUtils from '../../libs/ReportActionsUtils'; +import * as TransactionUtils from '../../libs/TransactionUtils'; import ReportActionItemImages from './ReportActionItemImages'; const propTypes = { @@ -105,7 +106,7 @@ function ReportPreview(props) { const numberOfScanningReceipts = ReportActionUtils.getNumberOfScanningReceipts(props.iouReport); const moneyRequestComment = lodashGet(props.action, 'childLastMoneyRequestComment', ''); - const transactions = ReportActionUtils.getReportPreviewTransactionsWithReceipts(props.action); + const transactions = TransactionUtils.getReportPreviewTransactionsWithReceipts(props.action); const hasReceipts = transactions.length > 0; const isScanning = hasReceipts && !ReportActionUtils.hasReadyMoneyRequests(props.action); const hasOnlyOneReceiptRequest = numberOfRequests === 1 && hasReceipts; @@ -116,14 +117,16 @@ function ReportPreview(props) { scanningReceipts: numberOfScanningReceipts, }); - let displayAmount; - if (reportTotal) { - displayAmount = CurrencyUtils.convertToDisplayString(reportTotal, props.iouReport.currency); - } else if (isScanning) { - displayAmount = props.translate('iou.receiptScanning'); - } else { + const getDisplayAmount = () => { + if (reportTotal) { + return CurrencyUtils.convertToDisplayString(reportTotal, props.iouReport.currency); + } + if (isScanning) { + return props.translate('iou.receiptScanning'); + } + // If iouReport is not available, get amount from the action message (Ex: "Domain20821's Workspace owes $33.00" or "paid ₫60" or "paid -₫60 elsewhere") - displayAmount = ''; + let displayAmount = ''; const actionMessage = lodashGet(props.action, ['message', 0, 'text'], ''); const splits = actionMessage.split(' '); for (let i = 0; i < splits.length; i++) { @@ -131,15 +134,16 @@ function ReportPreview(props) { displayAmount = splits[i]; } } - } + return displayAmount; + }; - let previewMessage; - const managerName = ReportUtils.isPolicyExpenseChat(props.chatReport) ? ReportUtils.getPolicyName(props.chatReport) : ReportUtils.getDisplayNameForParticipant(managerID, true); - if (isScanning) { - previewMessage = props.translate('common.receipt'); - } else { - previewMessage = props.translate(iouSettled || props.iouReport.isWaitingOnBankAccount ? 'iou.payerPaid' : 'iou.payerOwes', {payer: managerName}); - } + const getPreviewMessage = () => { + const managerName = ReportUtils.isPolicyExpenseChat(props.chatReport) ? ReportUtils.getPolicyName(props.chatReport) : ReportUtils.getDisplayNameForParticipant(managerID, true); + if (isScanning) { + return props.translate('common.receipt'); + } + return props.translate(iouSettled || props.iouReport.isWaitingOnBankAccount ? 'iou.payerPaid' : 'iou.payerOwes', {payer: managerName}); + }; const bankAccountRoute = ReportUtils.getBankAccountRoute(props.chatReport); const shouldShowSettlementButton = !_.isEmpty(props.iouReport) && isCurrentUserManager && !iouSettled && !props.iouReport.isWaitingOnBankAccount && reportTotal !== 0; @@ -169,12 +173,12 @@ function ReportPreview(props) { - {previewMessage} + {getPreviewMessage()} - {displayAmount} + {getDisplayAmount()} {ReportUtils.isSettled(props.iouReportID) && ( { - if (!key || !actions) { - return; - } - - const transactionID = CollectionUtils.extractCollectionItemID(key); - allTransactions[transactionID] = actions; - }, -}); - let isNetworkOffline = false; Onyx.connect({ key: ONYXKEYS.NETWORK, @@ -595,36 +582,6 @@ function isMessageDeleted(reportAction) { return lodashGet(reportAction, ['message', 0, 'isDeletedParentAction'], false); } -/** - * Get the transactions related to a report preview - * - * @param {Object} reportPreviewAction - * @returns {Object} - */ -function getReportPreviewTransactionsWithReceipts(reportPreviewAction) { - const transactionIDs = lodashGet(reportPreviewAction, ['childLastReceiptTransactionIDs'], '').split(','); - return _.reduce( - transactionIDs, - (transactions, transactionID) => { - const transaction = allTransactions[transactionID]; - if (transaction) { - transactions.push(transaction); - } - return transactions; - }, - [], - ); -} - -/** - * @param {Object} iouReportAction - * @returns {Object} - */ -function getTransaction(iouReportAction) { - const transactionID = lodashGet(iouReportAction, ['originalMessage', 'IOUTransactionID']); - return allTransactions[transactionID] || {}; -} - /** * Returns the number of money requests associated with a report preview * @@ -636,14 +593,18 @@ function getNumberOfMoneyRequests(reportPreviewAction) { } /** - * Checks if the IOU or expense report has either no smartscanned receipts or at least one is already done scanning + * For report previews and money request actions, we display a "Receipt scan in progress" indicator + * instead of the report total only when we have no report total ready to show. As soon as we have + * a non-receipt request, or as soon as one receipt request is done scanning, we have at least one + * "ready" money request, and we remove this indicator to show the partial report total. * * @param {Object|null} reportAction * @returns {Boolean} */ function hasReadyMoneyRequests(reportAction) { + // If a report preview has at least one manual request or at least one scanned receipt if (isReportPreviewAction(reportAction)) { - const transactions = getReportPreviewTransactionsWithReceipts(reportAction); + const transactions = TransactionUtils.getReportPreviewTransactionsWithReceipts(reportAction); // If we have more requests than requests with receipts, we have some manual requests if (getNumberOfMoneyRequests(reportAction) > transactions.length) { return true; @@ -651,8 +612,10 @@ function hasReadyMoneyRequests(reportAction) { return _.some(transactions, (transaction) => !ReceiptUtils.isBeingScanned(transaction.receipt)); } + // If a money request action is not a scanning receipt if (isMoneyRequestAction(reportAction)) { - const transaction = getTransaction(reportAction); + const transactionID = getLinkedTransactionID(reportAction); + const transaction = TransactionUtils.getTransaction(transactionID); return !TransactionUtils.hasReceipt(transaction) || !ReceiptUtils.isBeingScanned(transaction.receipt); } @@ -675,7 +638,7 @@ function getNumberOfScanningReceipts(iouReport) { if (!isMoneyRequestAction(reportAction)) { return count; } - const transaction = getTransaction(reportAction); + const transaction = TransactionUtils.getTransaction(getLinkedTransactionID(reportAction)); return count + Number(TransactionUtils.hasReceipt(transaction) && ReceiptUtils.isBeingScanned(transaction.receipt)); }, 0, @@ -716,9 +679,7 @@ export { isWhisperAction, isPendingRemove, getReportAction, - getReportPreviewTransactionsWithReceipts, hasReadyMoneyRequests, - getTransaction, getNumberOfMoneyRequests, getNumberOfScanningReceipts, }; diff --git a/src/libs/ReportUtils.js b/src/libs/ReportUtils.js index 02e03e3ae448..f2d92757762a 100644 --- a/src/libs/ReportUtils.js +++ b/src/libs/ReportUtils.js @@ -1967,6 +1967,7 @@ function buildOptimisticReportPreview(chatReport, iouReport, comment = '', trans ], created: DateUtils.getDBTime(), accountID: iouReport.managerID || 0, + // The preview is initially whispered if created with a receipt, so the actor is the current user as well actorAccountID: hasReceipt ? currentUserAccountID : iouReport.managerID || 0, childMoneyRequestCount: 1, childLastMoneyRequestComment: comment, @@ -2029,7 +2030,7 @@ function buildOptimisticModifiedExpenseReportAction(transactionThread, oldTransa function updateReportPreview(iouReport, reportPreviewAction, comment = '', transaction = undefined) { const hasReceipt = TransactionUtils.hasReceipt(transaction); const lastReceiptTransactionIDs = lodashGet(reportPreviewAction, 'childLastReceiptTransactionIDs', ''); - const previousIDs = lastReceiptTransactionIDs.split(',').slice(0, 2); + const previousTransactionIDs = lastReceiptTransactionIDs.split(',').slice(0, 2); const message = getReportPreviewMessage(iouReport, reportPreviewAction); return { @@ -2045,7 +2046,7 @@ function updateReportPreview(iouReport, reportPreviewAction, comment = '', trans ], childLastMoneyRequestComment: comment || reportPreviewAction.childLastMoneyRequestComment, childMoneyRequestCount: reportPreviewAction.childMoneyRequestCount + 1, - childLastReceiptTransactionIDs: hasReceipt ? [transaction.transactionID, ...previousIDs].join(',') : lastReceiptTransactionIDs, + childLastReceiptTransactionIDs: hasReceipt ? [transaction.transactionID, ...previousTransactionIDs].join(',') : lastReceiptTransactionIDs, // As soon as we add a transaction without a receipt to the report, it will have ready money requests, // so we remove the whisper whisperedToAccountIDs: hasReceipt ? reportPreviewAction.whisperedToAccountIDs : [], diff --git a/src/libs/TransactionUtils.js b/src/libs/TransactionUtils.js index cd7e7922b3fa..3978585b326a 100644 --- a/src/libs/TransactionUtils.js +++ b/src/libs/TransactionUtils.js @@ -167,4 +167,25 @@ function getCreated(transaction) { return format(new Date(lodashGet(transaction, 'created', '')), CONST.DATE.FNS_FORMAT_STRING); } -export {buildOptimisticTransaction, hasReceipt, getUpdatedTransaction, getTransaction, getDescription, getAmount, getCurrency, getCreated}; +/** + * Get the transactions related to a report preview with receipts + * + * @param {Object} reportPreviewAction + * @returns {Object} + */ +function getReportPreviewTransactionsWithReceipts(reportPreviewAction) { + const transactionIDs = lodashGet(reportPreviewAction, ['childLastReceiptTransactionIDs'], '').split(','); + return _.reduce( + transactionIDs, + (transactions, transactionID) => { + const transaction = getTransaction(transactionID); + if (!_.isEmpty(transaction)) { + transactions.push(transaction); + } + return transactions; + }, + [], + ); +} + +export {buildOptimisticTransaction, hasReceipt, getUpdatedTransaction, getTransaction, getDescription, getAmount, getCurrency, getCreated, getReportPreviewTransactionsWithReceipts}; diff --git a/src/libs/fileDownload/FileUtils.js b/src/libs/fileDownload/FileUtils.js index fed41f839688..4d714fff4f24 100644 --- a/src/libs/fileDownload/FileUtils.js +++ b/src/libs/fileDownload/FileUtils.js @@ -152,8 +152,7 @@ const readFileAsync = (path, fileName) => return res.blob(); }) .then((blob) => { - const cleanName = cleanFileName(fileName); - const file = new File([blob], cleanName); + const file = new File([blob], cleanFileName(fileName)); file.source = path; resolve(file); }) diff --git a/src/pages/iou/ReceiptSelector/index.js b/src/pages/iou/ReceiptSelector/index.js index c7b469e6ec5a..392e96887f8b 100644 --- a/src/pages/iou/ReceiptSelector/index.js +++ b/src/pages/iou/ReceiptSelector/index.js @@ -99,7 +99,8 @@ function ReceiptSelector(props) { return; } - IOU.setMoneyRequestReceipt(file.uri, file.name); + const filePath = URL.createObjectURL(file); + IOU.setMoneyRequestReceipt(filePath, file.name); IOU.navigateToNextPage(iou, iouType, reportID, report); }; @@ -153,7 +154,6 @@ function ReceiptSelector(props) { { const file = lodashGet(e, ['dataTransfer', 'files', 0]); - file.uri = URL.createObjectURL(file); setReceiptAndNavigate(file, props.iou, props.report); }} receiptImageTopPosition={receiptImageTopPosition} From 4aee47ba5fa1e7409a8791020a28edae8d56458b Mon Sep 17 00:00:00 2001 From: Andrew Li Date: Tue, 15 Aug 2023 14:38:35 -0400 Subject: [PATCH 36/61] Separate status bar from header --- src/components/HeaderWithBackButton/index.js | 33 ++------------------ src/components/MoneyRequestHeader.js | 28 +++++++++++++++-- src/styles/styles.js | 2 +- 3 files changed, 29 insertions(+), 34 deletions(-) diff --git a/src/components/HeaderWithBackButton/index.js b/src/components/HeaderWithBackButton/index.js index 83c18978587e..02579172653d 100755 --- a/src/components/HeaderWithBackButton/index.js +++ b/src/components/HeaderWithBackButton/index.js @@ -18,8 +18,6 @@ import headerWithBackButtonPropTypes from './headerWithBackButtonPropTypes'; import useThrottledButtonState from '../../hooks/useThrottledButtonState'; import useLocalize from '../../hooks/useLocalize'; import useKeyboardState from '../../hooks/useKeyboardState'; -import themeColors from '../../styles/themes/default'; -import Text from '../Text'; function HeaderWithBackButton({ iconFill = undefined, @@ -39,10 +37,6 @@ function HeaderWithBackButton({ shouldShowGetAssistanceButton = false, shouldShowPinButton = false, shouldShowThreeDotsButton = false, - shouldShowStatusBar = false, - statusBarBadgeColor = themeColors.border, - statusBarBadgeText = '', - statusBarDescription = '', stepCounter = null, subtitle = '', title = '', @@ -53,13 +47,14 @@ function HeaderWithBackButton({ }, threeDotsMenuItems = [], children = null, + statusBar = null, }) { const [isDownloadButtonActive, temporarilyDisableDownloadButton] = useThrottledButtonState(); const {translate} = useLocalize(); const {isKeyboardShown} = useKeyboardState(); return ( <> - + {shouldShowBackButton && ( @@ -164,29 +159,7 @@ function HeaderWithBackButton({ - {shouldShowStatusBar && ( - - - {statusBarBadgeText} - - - {statusBarDescription} - - - )} + {statusBar} ); } diff --git a/src/components/MoneyRequestHeader.js b/src/components/MoneyRequestHeader.js index 256010e07ef3..0a1bd636688b 100644 --- a/src/components/MoneyRequestHeader.js +++ b/src/components/MoneyRequestHeader.js @@ -72,6 +72,30 @@ function MoneyRequestHeader(props) { const isScanning = !ReportActionsUtils.hasReadyMoneyRequests(parentReportAction); + const getStatusBar = () => ( + + + {translate('iou.receiptStatusTitle')} + + + {translate('iou.receiptStatusText')} + + + ); + return ( <> @@ -92,10 +116,8 @@ function MoneyRequestHeader(props) { personalDetails={props.personalDetails} shouldShowBackButton={props.isSmallScreenWidth} onBackButtonPress={() => Navigation.goBack(ROUTES.HOME, false, true)} + statusBar={isScanning ? getStatusBar() : null} shouldShowBorderBottom - shouldShowStatusBar={isScanning} - statusBarBadgeText={translate('iou.receiptStatusTitle')} - statusBarDescription={translate('iou.receiptStatusText')} /> Date: Tue, 15 Aug 2023 15:14:40 -0400 Subject: [PATCH 37/61] Refactor image to different component and fix linting --- src/components/AttachmentModal.js | 1 - .../extractAttachmentsFromReport.js | 2 +- src/components/MoneyRequestHeader.js | 1 + .../ReportActionItem/MoneyRequestPreview.js | 3 +- .../ReportActionItem/MoneyRequestView.js | 20 +++----- .../ReportActionItem/ReportActionItemImage.js | 51 +++++++++++++++++++ .../ReportActionItemImages.js | 24 ++------- src/libs/ReportActionsUtils.js | 5 +- src/styles/styles.js | 1 + 9 files changed, 70 insertions(+), 38 deletions(-) create mode 100644 src/components/ReportActionItem/ReportActionItemImage.js diff --git a/src/components/AttachmentModal.js b/src/components/AttachmentModal.js index 310dbe6d22ff..cbf2e9f91b99 100755 --- a/src/components/AttachmentModal.js +++ b/src/components/AttachmentModal.js @@ -25,7 +25,6 @@ import HeaderGap from './HeaderGap'; import SafeAreaConsumer from './SafeAreaConsumer'; import addEncryptedAuthTokenToURL from '../libs/addEncryptedAuthTokenToURL'; import reportPropTypes from '../pages/reportPropTypes'; -import * as Expensicons from './Icon/Expensicons'; /** * Modal render prop component that exposes modal launching triggers that can be used diff --git a/src/components/Attachments/AttachmentCarousel/extractAttachmentsFromReport.js b/src/components/Attachments/AttachmentCarousel/extractAttachmentsFromReport.js index 467f7260010f..7b8c97c5c13c 100644 --- a/src/components/Attachments/AttachmentCarousel/extractAttachmentsFromReport.js +++ b/src/components/Attachments/AttachmentCarousel/extractAttachmentsFromReport.js @@ -45,7 +45,7 @@ function extractAttachmentsFromReport(report, reportActions, source) { // We're handling receipts differently here because receipt images are not // part of the report action message, the images are constructed client-side if (ReportActionsUtils.isMoneyRequestAction(action)) { - const transaction = ReportActionsUtils.getTransaction(action); + const transaction = TransactionUtils.getTransaction(action.originalMessage.IOUTransactionID); if (TransactionUtils.hasReceipt(transaction)) { const {image} = ReceiptUtils.getThumbnailAndImageURIs(transaction.receipt.source, transaction.filename); attachments.unshift({ diff --git a/src/components/MoneyRequestHeader.js b/src/components/MoneyRequestHeader.js index 0a1bd636688b..39a2fe316055 100644 --- a/src/components/MoneyRequestHeader.js +++ b/src/components/MoneyRequestHeader.js @@ -19,6 +19,7 @@ import * as IOU from '../libs/actions/IOU'; import * as ReportActionsUtils from '../libs/ReportActionsUtils'; import ConfirmModal from './ConfirmModal'; import useLocalize from '../hooks/useLocalize'; +import Text from './Text'; const propTypes = { /** The report currently being looked at */ diff --git a/src/components/ReportActionItem/MoneyRequestPreview.js b/src/components/ReportActionItem/MoneyRequestPreview.js index 76c688eb612c..f9683a2977d4 100644 --- a/src/components/ReportActionItem/MoneyRequestPreview.js +++ b/src/components/ReportActionItem/MoneyRequestPreview.js @@ -147,8 +147,7 @@ function MoneyRequestPreview(props) { const requestCurrency = moneyRequestAction.currency; const requestComment = moneyRequestAction.comment.trim(); - const transactionID = ReportActionUtils.getLinkedTransactionID(props.action); - const transaction = TransactionUtils.getTransaction(transactionID); + const transaction = TransactionUtils.getTransaction(props.action.originalMessage.IOUTransactionID); const hasReceipt = TransactionUtils.hasReceipt(transaction); const isScanning = !ReportActionUtils.hasReadyMoneyRequests(props.action); diff --git a/src/components/ReportActionItem/MoneyRequestView.js b/src/components/ReportActionItem/MoneyRequestView.js index 9eaca0c5429f..a4fbe973c352 100644 --- a/src/components/ReportActionItem/MoneyRequestView.js +++ b/src/components/ReportActionItem/MoneyRequestView.js @@ -26,7 +26,7 @@ import * as TransactionUtils from '../../libs/TransactionUtils'; import * as ReceiptUtils from '../../libs/ReceiptUtils'; import useWindowDimensions from '../../hooks/useWindowDimensions'; import Image from '../Image'; -import RenderHTML from '../RenderHTML'; +import ReportActionItemImage from './ReportActionItemImage'; const propTypes = { /** The report currently being looked at */ @@ -87,11 +87,11 @@ function MoneyRequestView({report, parentReport, shouldShowHorizontalRule, polic description += ` • ${translate('iou.pending')}`; } - const transaction = ReportActionsUtils.getTransaction(parentReportAction); + const transaction = TransactionUtils.getTransaction(parentReportAction.originalMessage.IOUTransactionID); const hasReceipt = TransactionUtils.hasReceipt(transaction); - let receiptImage; + let receiptURIs; if (hasReceipt) { - receiptImage = ReceiptUtils.getThumbnailAndImageURIs(transaction.receipt.source, transaction.filename).image; + receiptURIs = ReceiptUtils.getThumbnailAndImageURIs(transaction.receipt.source, transaction.filename); } return ( @@ -105,14 +105,10 @@ function MoneyRequestView({report, parentReport, shouldShowHorizontalRule, polic {hasReceipt && ( - - `} + )} diff --git a/src/components/ReportActionItem/ReportActionItemImage.js b/src/components/ReportActionItem/ReportActionItemImage.js new file mode 100644 index 000000000000..f88b84b76a17 --- /dev/null +++ b/src/components/ReportActionItem/ReportActionItemImage.js @@ -0,0 +1,51 @@ +import React from 'react'; +import PropTypes from 'prop-types'; +import styles from '../../styles/styles'; +import RenderHTML from '../RenderHTML'; +import Image from '../Image'; + +const propTypes = { + /** thumbnail URI for the image */ + thumbnail: PropTypes.string, + + /** URI for the image */ + image: PropTypes.string.isRequired, + + /** whether or not to enable the image preview modal */ + enablePreviewModal: PropTypes.bool, +}; + +const defaultProps = { + thumbnail: null, + enablePreviewModal: false, +}; + +function ReportActionItemImage({thumbnail, image, enablePreviewModal}) { + if (thumbnail) { + return ( + + `} + /> + ); + } + + return ( + + ); +} + +ReportActionItemImage.propTypes = propTypes; +ReportActionItemImage.defaultProps = defaultProps; +ReportActionItemImage.displayName = 'ReportActionItemImage'; + +export default ReportActionItemImage; diff --git a/src/components/ReportActionItem/ReportActionItemImages.js b/src/components/ReportActionItem/ReportActionItemImages.js index e5f9451d3eab..57ded78c3fb1 100644 --- a/src/components/ReportActionItem/ReportActionItemImages.js +++ b/src/components/ReportActionItem/ReportActionItemImages.js @@ -3,10 +3,9 @@ import {View} from 'react-native'; import PropTypes from 'prop-types'; import _ from 'underscore'; import styles from '../../styles/styles'; -import RenderHTML from '../RenderHTML'; import stylePropTypes from '../../styles/stylePropTypes'; import Text from '../Text'; -import Image from '../Image'; +import ReportActionItemImage from './ReportActionItemImage'; const propTypes = { /** array of image and thumbnail URIs */ @@ -50,23 +49,10 @@ function ReportActionItemImages({images, size, total, hoverStyle}) { key={image} style={[styles.reportActionItemImage, hoverStyle]} > - {thumbnail ? ( - - `} - /> - ) : ( - - )} + {isLastImage && remaining > 0 && ( +{remaining} diff --git a/src/libs/ReportActionsUtils.js b/src/libs/ReportActionsUtils.js index 7fb1d2438165..9940940f765c 100644 --- a/src/libs/ReportActionsUtils.js +++ b/src/libs/ReportActionsUtils.js @@ -614,8 +614,7 @@ function hasReadyMoneyRequests(reportAction) { // If a money request action is not a scanning receipt if (isMoneyRequestAction(reportAction)) { - const transactionID = getLinkedTransactionID(reportAction); - const transaction = TransactionUtils.getTransaction(transactionID); + const transaction = TransactionUtils.getTransaction(reportAction.originalMessage.IOUTransactionID); return !TransactionUtils.hasReceipt(transaction) || !ReceiptUtils.isBeingScanned(transaction.receipt); } @@ -638,7 +637,7 @@ function getNumberOfScanningReceipts(iouReport) { if (!isMoneyRequestAction(reportAction)) { return count; } - const transaction = TransactionUtils.getTransaction(getLinkedTransactionID(reportAction)); + const transaction = TransactionUtils.getTransaction(reportAction.originalMessage.IOUTransactionID); return count + Number(TransactionUtils.hasReceipt(transaction) && ReceiptUtils.isBeingScanned(transaction.receipt)); }, 0, diff --git a/src/styles/styles.js b/src/styles/styles.js index 447a1d9e0b77..7992653ae832 100644 --- a/src/styles/styles.js +++ b/src/styles/styles.js @@ -3722,6 +3722,7 @@ const styles = { padding: 8, borderRadius: variables.componentBorderRadiusMedium, marginRight: 16, + backgroundColor: themeColors.border, }, staticHeaderImage: { From 143c780b45edcdb7727900ce18b0515701149615 Mon Sep 17 00:00:00 2001 From: Andrew Li Date: Wed, 16 Aug 2023 09:47:53 -0400 Subject: [PATCH 38/61] Rename to areAllRequestsBeingSmartScanned --- src/components/ReportActionItem/MoneyRequestPreview.js | 2 +- src/libs/ReportActionsUtils.js | 9 +++++---- src/libs/ReportUtils.js | 2 +- 3 files changed, 7 insertions(+), 6 deletions(-) diff --git a/src/components/ReportActionItem/MoneyRequestPreview.js b/src/components/ReportActionItem/MoneyRequestPreview.js index f9683a2977d4..7d6717f39e71 100644 --- a/src/components/ReportActionItem/MoneyRequestPreview.js +++ b/src/components/ReportActionItem/MoneyRequestPreview.js @@ -149,7 +149,7 @@ function MoneyRequestPreview(props) { const transaction = TransactionUtils.getTransaction(props.action.originalMessage.IOUTransactionID); const hasReceipt = TransactionUtils.hasReceipt(transaction); - const isScanning = !ReportActionUtils.hasReadyMoneyRequests(props.action); + const isScanning = !ReportActionUtils.areAllRequestsBeingSmartscanned(props.action); const getSettledMessage = () => { switch (lodashGet(props.action, 'originalMessage.paymentType', '')) { diff --git a/src/libs/ReportActionsUtils.js b/src/libs/ReportActionsUtils.js index 9940940f765c..d6330f4da076 100644 --- a/src/libs/ReportActionsUtils.js +++ b/src/libs/ReportActionsUtils.js @@ -594,14 +594,15 @@ function getNumberOfMoneyRequests(reportPreviewAction) { /** * For report previews and money request actions, we display a "Receipt scan in progress" indicator - * instead of the report total only when we have no report total ready to show. As soon as we have - * a non-receipt request, or as soon as one receipt request is done scanning, we have at least one + * instead of the report total only when we have no report total ready to show. This is the case when + * all requests are receipts that are being SmartScanned. As soon as we have a non-receipt request, + * or as soon as one receipt request is done scanning, we have at least one * "ready" money request, and we remove this indicator to show the partial report total. * * @param {Object|null} reportAction * @returns {Boolean} */ -function hasReadyMoneyRequests(reportAction) { +function areAllRequestsBeingSmartScanned(reportAction) { // If a report preview has at least one manual request or at least one scanned receipt if (isReportPreviewAction(reportAction)) { const transactions = TransactionUtils.getReportPreviewTransactionsWithReceipts(reportAction); @@ -678,7 +679,7 @@ export { isWhisperAction, isPendingRemove, getReportAction, - hasReadyMoneyRequests, + areAllRequestsBeingSmartscanned, getNumberOfMoneyRequests, getNumberOfScanningReceipts, }; diff --git a/src/libs/ReportUtils.js b/src/libs/ReportUtils.js index f2d92757762a..1d091e4a0b5a 100644 --- a/src/libs/ReportUtils.js +++ b/src/libs/ReportUtils.js @@ -1261,7 +1261,7 @@ function getTransactionReportName(reportAction) { return Localize.translateLocal('parentReportAction.deletedRequest'); } - if (!ReportActionsUtils.hasReadyMoneyRequests(reportAction)) { + if (!ReportActionsUtils.areAllRequestsBeingSmartscanned(reportAction)) { return Localize.translateLocal('iou.receiptScanning'); } From 4f2726a406e4631847edb78d967f1c773bdffb1a Mon Sep 17 00:00:00 2001 From: Andrew Li Date: Wed, 16 Aug 2023 10:37:15 -0400 Subject: [PATCH 39/61] Fix settlement button padding and typos --- src/components/MoneyRequestHeader.js | 2 +- .../ReportActionItem/MoneyRequestPreview.js | 2 +- .../ReportActionItem/ReportPreview.js | 28 +++++++++---------- src/libs/ReportActionsUtils.js | 2 +- src/libs/ReportUtils.js | 2 +- src/styles/styles.js | 2 +- 6 files changed, 19 insertions(+), 19 deletions(-) diff --git a/src/components/MoneyRequestHeader.js b/src/components/MoneyRequestHeader.js index 39a2fe316055..925c36411811 100644 --- a/src/components/MoneyRequestHeader.js +++ b/src/components/MoneyRequestHeader.js @@ -71,7 +71,7 @@ function MoneyRequestHeader(props) { setIsDeleteModalVisible(false); }, [parentReportAction, setIsDeleteModalVisible]); - const isScanning = !ReportActionsUtils.hasReadyMoneyRequests(parentReportAction); + const isScanning = !ReportActionsUtils.areAllRequestsBeingSmartScanned(parentReportAction); const getStatusBar = () => ( { switch (lodashGet(props.action, 'originalMessage.paymentType', '')) { diff --git a/src/components/ReportActionItem/ReportPreview.js b/src/components/ReportActionItem/ReportPreview.js index ea155a09a1c3..4f9574f86fcb 100644 --- a/src/components/ReportActionItem/ReportPreview.js +++ b/src/components/ReportActionItem/ReportPreview.js @@ -108,7 +108,7 @@ function ReportPreview(props) { const transactions = TransactionUtils.getReportPreviewTransactionsWithReceipts(props.action); const hasReceipts = transactions.length > 0; - const isScanning = hasReceipts && !ReportActionUtils.hasReadyMoneyRequests(props.action); + const isScanning = hasReceipts && !ReportActionUtils.areAllRequestsBeingSmartScanned(props.action); const hasOnlyOneReceiptRequest = numberOfRequests === 1 && hasReceipts; const previewSubtitle = hasOnlyOneReceiptRequest ? transactions[0].merchant @@ -170,7 +170,7 @@ function ReportPreview(props) { hoverStyle={props.isHovered || isScanning ? styles.reportPreviewBoxHoverBorder : undefined} /> )} - + {getPreviewMessage()} @@ -196,19 +196,19 @@ function ReportPreview(props) { )} + {shouldShowSettlementButton && ( + IOU.payMoneyRequest(paymentType, props.chatReport, props.iouReport)} + enablePaymentsRoute={ROUTES.BANK_ACCOUNT_NEW} + addBankAccountRoute={bankAccountRoute} + style={[styles.requestPreviewBox]} + /> + )} - {shouldShowSettlementButton && ( - IOU.payMoneyRequest(paymentType, props.chatReport, props.iouReport)} - enablePaymentsRoute={ROUTES.BANK_ACCOUNT_NEW} - addBankAccountRoute={bankAccountRoute} - style={[styles.requestPreviewBox]} - /> - )} diff --git a/src/libs/ReportActionsUtils.js b/src/libs/ReportActionsUtils.js index d6330f4da076..21f606fa621c 100644 --- a/src/libs/ReportActionsUtils.js +++ b/src/libs/ReportActionsUtils.js @@ -679,7 +679,7 @@ export { isWhisperAction, isPendingRemove, getReportAction, - areAllRequestsBeingSmartscanned, + areAllRequestsBeingSmartScanned, getNumberOfMoneyRequests, getNumberOfScanningReceipts, }; diff --git a/src/libs/ReportUtils.js b/src/libs/ReportUtils.js index 1d091e4a0b5a..ff435f4a479b 100644 --- a/src/libs/ReportUtils.js +++ b/src/libs/ReportUtils.js @@ -1261,7 +1261,7 @@ function getTransactionReportName(reportAction) { return Localize.translateLocal('parentReportAction.deletedRequest'); } - if (!ReportActionsUtils.areAllRequestsBeingSmartscanned(reportAction)) { + if (!ReportActionsUtils.areAllRequestsBeingSmartScanned(reportAction)) { return Localize.translateLocal('iou.receiptScanning'); } diff --git a/src/styles/styles.js b/src/styles/styles.js index 7992653ae832..47f18df7a54f 100644 --- a/src/styles/styles.js +++ b/src/styles/styles.js @@ -3682,7 +3682,7 @@ const styles = { backgroundColor: themeColors.border, }, - reportPreviewBoxText: { + reportPreviewBoxBody: { padding: 16, }, From 6d026dd37640fb0096a7122c94d7a85afc9230cf Mon Sep 17 00:00:00 2001 From: Andrew Li Date: Wed, 16 Aug 2023 13:56:04 -0400 Subject: [PATCH 40/61] Add maxWidth to receipt preview in money request view --- src/styles/styles.js | 1 + 1 file changed, 1 insertion(+) diff --git a/src/styles/styles.js b/src/styles/styles.js index 971b36387129..78ff4e85e766 100644 --- a/src/styles/styles.js +++ b/src/styles/styles.js @@ -3744,6 +3744,7 @@ const styles = { borderColor: themeColors.cardBG, borderRadius: variables.componentBorderRadiusLarge, height: 200, + maxWidth: 400, }, }; From eb96c58ea97c4d73489771be406fb627dc7fd300 Mon Sep 17 00:00:00 2001 From: Andrew Li Date: Wed, 16 Aug 2023 14:34:29 -0400 Subject: [PATCH 41/61] Add arrow right on report preview/iou preview --- src/components/ReportActionItem/MoneyRequestPreview.js | 5 ++++- src/components/ReportActionItem/MoneyRequestView.js | 2 +- src/components/ReportActionItem/ReportPreview.js | 1 + 3 files changed, 6 insertions(+), 2 deletions(-) diff --git a/src/components/ReportActionItem/MoneyRequestPreview.js b/src/components/ReportActionItem/MoneyRequestPreview.js index ef4194f78d21..1cbe57efe13a 100644 --- a/src/components/ReportActionItem/MoneyRequestPreview.js +++ b/src/components/ReportActionItem/MoneyRequestPreview.js @@ -152,6 +152,8 @@ function MoneyRequestPreview(props) { const isScanning = !ReportActionUtils.areAllRequestsBeingSmartScanned(props.action); const getSettledMessage = () => { + return 'test'; + switch (lodashGet(props.action, 'originalMessage.paymentType', '')) { case CONST.IOU.PAYMENT_TYPE.PAYPAL_ME: return props.translate('iou.settledPaypalMe'); @@ -225,10 +227,11 @@ function MoneyRequestPreview(props) { height={4} additionalStyles={[styles.mr1, styles.ml1]} /> - {getSettledMessage()} + {getSettledMessage()} )} + diff --git a/src/components/ReportActionItem/MoneyRequestView.js b/src/components/ReportActionItem/MoneyRequestView.js index c65302483342..76f53965633b 100644 --- a/src/components/ReportActionItem/MoneyRequestView.js +++ b/src/components/ReportActionItem/MoneyRequestView.js @@ -92,7 +92,7 @@ function MoneyRequestView({report, parentReport, shouldShowHorizontalRule, polic if (ReportActionsUtils.isDeletedAction(parentReportAction)) { return null; } - + const transaction = TransactionUtils.getTransaction(parentReportAction.originalMessage.IOUTransactionID); const hasReceipt = TransactionUtils.hasReceipt(transaction); let receiptURIs; diff --git a/src/components/ReportActionItem/ReportPreview.js b/src/components/ReportActionItem/ReportPreview.js index 4f9574f86fcb..23e5767f8991 100644 --- a/src/components/ReportActionItem/ReportPreview.js +++ b/src/components/ReportActionItem/ReportPreview.js @@ -175,6 +175,7 @@ function ReportPreview(props) { {getPreviewMessage()} + From 2fe3b2b588561a6257ade6d5e5ca8ab2cb8d3b61 Mon Sep 17 00:00:00 2001 From: Andrew Li Date: Wed, 16 Aug 2023 14:37:46 -0400 Subject: [PATCH 42/61] Remove test code --- src/components/ReportActionItem/MoneyRequestPreview.js | 2 -- 1 file changed, 2 deletions(-) diff --git a/src/components/ReportActionItem/MoneyRequestPreview.js b/src/components/ReportActionItem/MoneyRequestPreview.js index 1cbe57efe13a..4b7740ae089f 100644 --- a/src/components/ReportActionItem/MoneyRequestPreview.js +++ b/src/components/ReportActionItem/MoneyRequestPreview.js @@ -152,8 +152,6 @@ function MoneyRequestPreview(props) { const isScanning = !ReportActionUtils.areAllRequestsBeingSmartScanned(props.action); const getSettledMessage = () => { - return 'test'; - switch (lodashGet(props.action, 'originalMessage.paymentType', '')) { case CONST.IOU.PAYMENT_TYPE.PAYPAL_ME: return props.translate('iou.settledPaypalMe'); From 9ea46f91b72d3a3ae441704a3ebde90ce0ee866b Mon Sep 17 00:00:00 2001 From: Andrew Li Date: Wed, 16 Aug 2023 17:09:54 -0400 Subject: [PATCH 43/61] Attach URI to file for iOS uploads, change optimistic update for whisper --- src/libs/ReceiptUtils.js | 3 ++- src/libs/ReportUtils.js | 3 ++- src/libs/fileDownload/FileUtils.js | 1 + src/styles/styles.js | 2 +- 4 files changed, 6 insertions(+), 3 deletions(-) diff --git a/src/libs/ReceiptUtils.js b/src/libs/ReceiptUtils.js index 635537226815..b4451d338cd4 100644 --- a/src/libs/ReceiptUtils.js +++ b/src/libs/ReceiptUtils.js @@ -38,7 +38,8 @@ function validateReceipt(file) { * @returns {Object} */ function getThumbnailAndImageURIs(path, filename) { - if (path.startsWith('blob:')) { + // For local files, we won't have a thumbnail yet + if (path.startsWith('blob:') || path.startsWith('file:')) { return {thumbnail: null, image: path}; } diff --git a/src/libs/ReportUtils.js b/src/libs/ReportUtils.js index 13b730c5dfbc..9af39c7f5847 100644 --- a/src/libs/ReportUtils.js +++ b/src/libs/ReportUtils.js @@ -1924,6 +1924,7 @@ function buildOptimisticIOUReportAction( created: DateUtils.getDBTime(), pendingAction: CONST.RED_BRICK_ROAD_PENDING_ACTION.ADD, receipt, + whisperedToAccountIDs: !_.isEmpty(receipt) ? [currentUserAccountID] : [], }; } @@ -1959,7 +1960,7 @@ function buildOptimisticReportPreview(chatReport, iouReport, comment = '', trans created: DateUtils.getDBTime(), accountID: iouReport.managerID || 0, // The preview is initially whispered if created with a receipt, so the actor is the current user as well - actorAccountID: hasReceipt ? currentUserAccountID : iouReport.managerID || 0, + actorAccountID: hasReceipt ? currentUserAccountID : (iouReport.managerID || 0), childMoneyRequestCount: 1, childLastMoneyRequestComment: comment, childLastReceiptTransactionIDs: hasReceipt ? transaction.transactionID : '', diff --git a/src/libs/fileDownload/FileUtils.js b/src/libs/fileDownload/FileUtils.js index 4d714fff4f24..0b33bc406ece 100644 --- a/src/libs/fileDownload/FileUtils.js +++ b/src/libs/fileDownload/FileUtils.js @@ -154,6 +154,7 @@ const readFileAsync = (path, fileName) => .then((blob) => { const file = new File([blob], cleanFileName(fileName)); file.source = path; + file.uri = path; resolve(file); }) .catch((e) => { diff --git a/src/styles/styles.js b/src/styles/styles.js index 78ff4e85e766..0b672350e274 100644 --- a/src/styles/styles.js +++ b/src/styles/styles.js @@ -3708,7 +3708,7 @@ const styles = { reportActionItemImagesMore: { position: 'absolute', - borderRadius: '50%', + borderRadius: 18, backgroundColor: themeColors.cardBG, width: 36, height: 36, From 739c3159eea84240a747aa698e2cbeef49a3b821 Mon Sep 17 00:00:00 2001 From: Andrew Li Date: Wed, 16 Aug 2023 17:48:51 -0400 Subject: [PATCH 44/61] Fix linting and prettier --- .../ReportActionItem/MoneyRequestPreview.js | 11 +++++++---- .../ReportActionItem/MoneyRequestView.js | 1 - src/libs/ReportUtils.js | 2 +- src/libs/TransactionUtils.js | 15 +++++++++++++-- 4 files changed, 21 insertions(+), 8 deletions(-) diff --git a/src/components/ReportActionItem/MoneyRequestPreview.js b/src/components/ReportActionItem/MoneyRequestPreview.js index a8d2373542bf..1f3cf771ad93 100644 --- a/src/components/ReportActionItem/MoneyRequestPreview.js +++ b/src/components/ReportActionItem/MoneyRequestPreview.js @@ -142,7 +142,7 @@ function MoneyRequestPreview(props) { const isCurrentUserManager = managerID === sessionAccountID; const transaction = TransactionUtils.getLinkedTransaction(props.action); - const {amount: requestAmount, currency: requestCurrency, comment: requestComment} = ReportUtils.getTransactionDetails(transaction); + const {amount: requestAmount, currency: requestCurrency, comment: requestComment, merchant: requestMerchant} = ReportUtils.getTransactionDetails(transaction); const hasReceipt = TransactionUtils.hasReceipt(transaction); const isScanning = !ReportActionUtils.areAllRequestsBeingSmartScanned(props.action); @@ -250,9 +250,9 @@ function MoneyRequestPreview(props) { )} - {moneyRequestAction.merchant && ( + {requestMerchant && ( - {moneyRequestAction.merchant} + {requestMerchant} )} @@ -265,7 +265,10 @@ function MoneyRequestPreview(props) { {props.isBillSplit && !_.isEmpty(participantAccountIDs) && ( {props.translate('iou.amountEach', { - amount: CurrencyUtils.convertToDisplayString(IOUUtils.calculateAmount(participantAccountIDs.length - 1, requestAmount, requestCurrency), requestCurrency), + amount: CurrencyUtils.convertToDisplayString( + IOUUtils.calculateAmount(participantAccountIDs.length - 1, requestAmount, requestCurrency), + requestCurrency, + ), })} )} diff --git a/src/components/ReportActionItem/MoneyRequestView.js b/src/components/ReportActionItem/MoneyRequestView.js index 3d78c3761248..f78bd870106e 100644 --- a/src/components/ReportActionItem/MoneyRequestView.js +++ b/src/components/ReportActionItem/MoneyRequestView.js @@ -22,7 +22,6 @@ import iouReportPropTypes from '../../pages/iouReportPropTypes'; import * as CurrencyUtils from '../../libs/CurrencyUtils'; import EmptyStateBackgroundImage from '../../../assets/images/empty-state_background-fade.png'; import useLocalize from '../../hooks/useLocalize'; -import * as TransactionUtils from '../../libs/TransactionUtils'; import * as ReceiptUtils from '../../libs/ReceiptUtils'; import useWindowDimensions from '../../hooks/useWindowDimensions'; import Image from '../Image'; diff --git a/src/libs/ReportUtils.js b/src/libs/ReportUtils.js index d8f9764401f0..8b821ac0696b 100644 --- a/src/libs/ReportUtils.js +++ b/src/libs/ReportUtils.js @@ -1961,7 +1961,7 @@ function buildOptimisticReportPreview(chatReport, iouReport, comment = '', trans created: DateUtils.getDBTime(), accountID: iouReport.managerID || 0, // The preview is initially whispered if created with a receipt, so the actor is the current user as well - actorAccountID: hasReceipt ? currentUserAccountID : (iouReport.managerID || 0), + actorAccountID: hasReceipt ? currentUserAccountID : iouReport.managerID || 0, childMoneyRequestCount: 1, childLastMoneyRequestComment: comment, childLastReceiptTransactionIDs: hasReceipt ? transaction.transactionID : '', diff --git a/src/libs/TransactionUtils.js b/src/libs/TransactionUtils.js index ac2baf0539ff..432a3192d83f 100644 --- a/src/libs/TransactionUtils.js +++ b/src/libs/TransactionUtils.js @@ -203,5 +203,16 @@ function getAllReportTransactions(reportID) { return _.filter(allTransactions, (transaction) => transaction.reportID === reportID); } -export {buildOptimisticTransaction, getUpdatedTransaction, getTransaction, getDescription, getAmount, getCurrency, getCreated, getLinkedTransaction, getAllReportTransactions, hasReceipt, getReportPreviewTransactionsWithReceipts}; - +export { + buildOptimisticTransaction, + getUpdatedTransaction, + getTransaction, + getDescription, + getAmount, + getCurrency, + getCreated, + getLinkedTransaction, + getAllReportTransactions, + hasReceipt, + getReportPreviewTransactionsWithReceipts, +}; From ecd553b9035597027e31c18d14abcf0900494350 Mon Sep 17 00:00:00 2001 From: Andrew Li Date: Wed, 16 Aug 2023 17:52:18 -0400 Subject: [PATCH 45/61] Remove identifier --- src/components/ReportActionItem/MoneyRequestView.js | 1 - 1 file changed, 1 deletion(-) diff --git a/src/components/ReportActionItem/MoneyRequestView.js b/src/components/ReportActionItem/MoneyRequestView.js index f78bd870106e..bd101f28207f 100644 --- a/src/components/ReportActionItem/MoneyRequestView.js +++ b/src/components/ReportActionItem/MoneyRequestView.js @@ -91,7 +91,6 @@ function MoneyRequestView({report, parentReport, shouldShowHorizontalRule, polic return null; } - const transaction = TransactionUtils.getTransaction(parentReportAction.originalMessage.IOUTransactionID); const hasReceipt = TransactionUtils.hasReceipt(transaction); let receiptURIs; if (hasReceipt) { From a5b6dffb5864761d6b9f2de7dcdc8892409e2d28 Mon Sep 17 00:00:00 2001 From: Andrew Li Date: Thu, 17 Aug 2023 11:55:02 -0400 Subject: [PATCH 46/61] Address comments --- src/components/HeaderWithBackButton/index.js | 2 +- src/components/MoneyRequestHeader.js | 29 +-------------- src/components/MoneyRequestHeaderStatusBar.js | 37 +++++++++++++++++++ .../ReportActionItem/MoneyRequestPreview.js | 4 +- .../ReportActionItemImages.js | 9 +++-- .../ReportActionItem/ReportPreview.js | 2 +- src/libs/ReportActionsUtils.js | 8 ++-- src/libs/ReportUtils.js | 2 +- src/libs/fileDownload/FileUtils.js | 2 + src/styles/styles.js | 5 --- 10 files changed, 55 insertions(+), 45 deletions(-) create mode 100644 src/components/MoneyRequestHeaderStatusBar.js diff --git a/src/components/HeaderWithBackButton/index.js b/src/components/HeaderWithBackButton/index.js index 02579172653d..c178f6e500cb 100755 --- a/src/components/HeaderWithBackButton/index.js +++ b/src/components/HeaderWithBackButton/index.js @@ -54,7 +54,7 @@ function HeaderWithBackButton({ const {isKeyboardShown} = useKeyboardState(); return ( <> - + {shouldShowBackButton && ( diff --git a/src/components/MoneyRequestHeader.js b/src/components/MoneyRequestHeader.js index 925c36411811..92c3ca41c94d 100644 --- a/src/components/MoneyRequestHeader.js +++ b/src/components/MoneyRequestHeader.js @@ -19,7 +19,7 @@ import * as IOU from '../libs/actions/IOU'; import * as ReportActionsUtils from '../libs/ReportActionsUtils'; import ConfirmModal from './ConfirmModal'; import useLocalize from '../hooks/useLocalize'; -import Text from './Text'; +import MoneyRequestHeaderStatusBar from './MoneyRequestHeaderStatusBar'; const propTypes = { /** The report currently being looked at */ @@ -73,30 +73,6 @@ function MoneyRequestHeader(props) { const isScanning = !ReportActionsUtils.areAllRequestsBeingSmartScanned(parentReportAction); - const getStatusBar = () => ( - - - {translate('iou.receiptStatusTitle')} - - - {translate('iou.receiptStatusText')} - - - ); - return ( <> @@ -117,8 +93,7 @@ function MoneyRequestHeader(props) { personalDetails={props.personalDetails} shouldShowBackButton={props.isSmallScreenWidth} onBackButtonPress={() => Navigation.goBack(ROUTES.HOME, false, true)} - statusBar={isScanning ? getStatusBar() : null} - shouldShowBorderBottom + statusBar={isScanning ? : null} /> + + {translate('iou.receiptStatusTitle')} + + + {translate('iou.receiptStatusText')} + + + ); +} + +MoneyRequestHeaderStatusBar.displayName = 'MoneyRequestHeaderStatusBar'; + +export default MoneyRequestHeaderStatusBar; \ No newline at end of file diff --git a/src/components/ReportActionItem/MoneyRequestPreview.js b/src/components/ReportActionItem/MoneyRequestPreview.js index 1f3cf771ad93..cda2cd5faad0 100644 --- a/src/components/ReportActionItem/MoneyRequestPreview.js +++ b/src/components/ReportActionItem/MoneyRequestPreview.js @@ -201,11 +201,11 @@ function MoneyRequestPreview(props) { errorRowStyles={[styles.mbn1]} needsOffscreenAlphaCompositing > - + {hasReceipt && ( )} diff --git a/src/components/ReportActionItem/ReportActionItemImages.js b/src/components/ReportActionItem/ReportActionItemImages.js index 57ded78c3fb1..57c03947c496 100644 --- a/src/components/ReportActionItem/ReportActionItemImages.js +++ b/src/components/ReportActionItem/ReportActionItemImages.js @@ -3,7 +3,6 @@ import {View} from 'react-native'; import PropTypes from 'prop-types'; import _ from 'underscore'; import styles from '../../styles/styles'; -import stylePropTypes from '../../styles/stylePropTypes'; import Text from '../Text'; import ReportActionItemImage from './ReportActionItemImage'; @@ -28,18 +27,20 @@ const propTypes = { // eslint-disable-next-line react/require-default-props total: PropTypes.number, - hoverStyle: stylePropTypes, + /** if the corresponding report action item is hovered */ + isHovered: PropTypes.boolean, }; const defaultProps = { - hoverStyle: {}, + isHovered: false, }; -function ReportActionItemImages({images, size, total, hoverStyle}) { +function ReportActionItemImages({images, size, total, isHovered}) { const numberOfShownImages = size || images.length; const shownImages = images.slice(0, size); const remaining = (total || images.length) - size; + const hoverStyle = isHovered ? styles.reportPreviewBoxHoverBorder : undefined; return ( {_.map(shownImages, ({thumbnail, image}, index) => { diff --git a/src/components/ReportActionItem/ReportPreview.js b/src/components/ReportActionItem/ReportPreview.js index 23e5767f8991..c5cf4a4d041b 100644 --- a/src/components/ReportActionItem/ReportPreview.js +++ b/src/components/ReportActionItem/ReportPreview.js @@ -167,7 +167,7 @@ function ReportPreview(props) { images={_.map(transactions, ({receipt, filename}) => ReceiptUtils.getThumbnailAndImageURIs(receipt.source, filename))} size={3} total={ReportActionUtils.getNumberOfMoneyRequests(props.action)} - hoverStyle={props.isHovered || isScanning ? styles.reportPreviewBoxHoverBorder : undefined} + isHovered={props.isHovered || isScanning} /> )} diff --git a/src/libs/ReportActionsUtils.js b/src/libs/ReportActionsUtils.js index 12b92239c1ed..e20b660b9789 100644 --- a/src/libs/ReportActionsUtils.js +++ b/src/libs/ReportActionsUtils.js @@ -594,18 +594,18 @@ function areAllRequestsBeingSmartScanned(reportAction) { const transactions = TransactionUtils.getReportPreviewTransactionsWithReceipts(reportAction); // If we have more requests than requests with receipts, we have some manual requests if (getNumberOfMoneyRequests(reportAction) > transactions.length) { - return true; + return false; } - return _.some(transactions, (transaction) => !ReceiptUtils.isBeingScanned(transaction.receipt)); + return _.all(transactions, (transaction) => !ReceiptUtils.isBeingScanned(transaction.receipt)); } // If a money request action is not a scanning receipt if (isMoneyRequestAction(reportAction)) { const transaction = TransactionUtils.getTransaction(reportAction.originalMessage.IOUTransactionID); - return !TransactionUtils.hasReceipt(transaction) || !ReceiptUtils.isBeingScanned(transaction.receipt); + return TransactionUtils.hasReceipt(transaction) && !ReceiptUtils.isBeingScanned(transaction.receipt); } - return true; + return false; } /** diff --git a/src/libs/ReportUtils.js b/src/libs/ReportUtils.js index 8b821ac0696b..a403a7118e65 100644 --- a/src/libs/ReportUtils.js +++ b/src/libs/ReportUtils.js @@ -1240,7 +1240,7 @@ function getTransactionReportName(reportAction) { return Localize.translateLocal('parentReportAction.deletedRequest'); } - if (!ReportActionsUtils.areAllRequestsBeingSmartScanned(reportAction)) { + if (ReportActionsUtils.areAllRequestsBeingSmartScanned(reportAction)) { return Localize.translateLocal('iou.receiptScanning'); } diff --git a/src/libs/fileDownload/FileUtils.js b/src/libs/fileDownload/FileUtils.js index 0b33bc406ece..01c3f7fcb834 100644 --- a/src/libs/fileDownload/FileUtils.js +++ b/src/libs/fileDownload/FileUtils.js @@ -154,6 +154,8 @@ const readFileAsync = (path, fileName) => .then((blob) => { const file = new File([blob], cleanFileName(fileName)); file.source = path; + // For some reason, the File object on iOS does not have a uri property + // so images aren't uploaded correctly to the backend file.uri = path; resolve(file); }) diff --git a/src/styles/styles.js b/src/styles/styles.js index 1815614c594d..6a2354a559e7 100644 --- a/src/styles/styles.js +++ b/src/styles/styles.js @@ -2653,11 +2653,6 @@ const styles = { padding: 16, }, - moneyRequestPreviewBoxHover: { - backgroundColor: themeColors.border, - borderColor: themeColors.border, - }, - moneyRequestPreviewBoxLoading: { // When a new IOU request arrives it is very briefly in a loading state, so set the minimum height of the container to 94 to match the rendered height after loading. // Otherwise, the IOU request pay button will not be fully visible and the user will have to scroll up to reveal the entire IOU request container. From b28d952f536db5f377ab926139a95da342db7967 Mon Sep 17 00:00:00 2001 From: Andrew Li Date: Thu, 17 Aug 2023 13:13:09 -0400 Subject: [PATCH 47/61] Move status bar component and fix conditions --- src/components/HeaderWithBackButton/index.js | 180 +++++++++--------- src/components/MoneyRequestHeader.js | 4 +- .../ReportActionItem/MoneyRequestAction.js | 2 +- .../ReportActionItem/MoneyRequestPreview.js | 4 +- .../ReportActionItem/ReportPreview.js | 2 +- src/libs/ReportActionsUtils.js | 4 +- src/libs/TransactionUtils.js | 2 +- 7 files changed, 97 insertions(+), 101 deletions(-) diff --git a/src/components/HeaderWithBackButton/index.js b/src/components/HeaderWithBackButton/index.js index c178f6e500cb..6e6164f4c4fc 100755 --- a/src/components/HeaderWithBackButton/index.js +++ b/src/components/HeaderWithBackButton/index.js @@ -47,120 +47,116 @@ function HeaderWithBackButton({ }, threeDotsMenuItems = [], children = null, - statusBar = null, }) { const [isDownloadButtonActive, temporarilyDisableDownloadButton] = useThrottledButtonState(); const {translate} = useLocalize(); const {isKeyboardShown} = useKeyboardState(); return ( - <> - - - {shouldShowBackButton && ( - + + + {shouldShowBackButton && ( + + { + if (isKeyboardShown) { + Keyboard.dismiss(); + } + onBackButtonPress(); + }} + style={[styles.touchableButtonImage]} + accessibilityRole="button" + accessibilityLabel={translate('common.back')} + > + + + + )} + {shouldShowAvatarWithDisplay && ( + + )} + {!shouldShowAvatarWithDisplay && ( +
+ )} + + {children} + {shouldShowDownloadButton && ( + { - if (isKeyboardShown) { - Keyboard.dismiss(); + onPress={(e) => { + // Blur the pressable in case this button triggers a Growl notification + // We do not want to overlap Growl with the Tooltip (#15271) + e.currentTarget.blur(); + + if (!isDownloadButtonActive) { + return; } - onBackButtonPress(); + + onDownloadButtonPress(); + temporarilyDisableDownloadButton(true); }} style={[styles.touchableButtonImage]} accessibilityRole="button" - accessibilityLabel={translate('common.back')} + accessibilityLabel={translate('common.download')} + > + + + + )} + {shouldShowGetAssistanceButton && ( + + Navigation.navigate(ROUTES.getGetAssistanceRoute(guidesCallTaskID))} + style={[styles.touchableButtonImage]} + accessibilityRole="button" + accessibilityLabel={translate('getAssistancePage.questionMarkButtonTooltip')} > )} - {shouldShowAvatarWithDisplay && ( - } + {shouldShowThreeDotsButton && ( + )} - {!shouldShowAvatarWithDisplay && ( -
+ {shouldShowCloseButton && ( + + + + + )} - - {children} - {shouldShowDownloadButton && ( - - { - // Blur the pressable in case this button triggers a Growl notification - // We do not want to overlap Growl with the Tooltip (#15271) - e.currentTarget.blur(); - - if (!isDownloadButtonActive) { - return; - } - - onDownloadButtonPress(); - temporarilyDisableDownloadButton(true); - }} - style={[styles.touchableButtonImage]} - accessibilityRole="button" - accessibilityLabel={translate('common.download')} - > - - - - )} - {shouldShowGetAssistanceButton && ( - - Navigation.navigate(ROUTES.getGetAssistanceRoute(guidesCallTaskID))} - style={[styles.touchableButtonImage]} - accessibilityRole="button" - accessibilityLabel={translate('getAssistancePage.questionMarkButtonTooltip')} - > - - - - )} - {shouldShowPinButton && } - {shouldShowThreeDotsButton && ( - - )} - {shouldShowCloseButton && ( - - - - - - )} - - {statusBar} - + ); } diff --git a/src/components/MoneyRequestHeader.js b/src/components/MoneyRequestHeader.js index 92c3ca41c94d..54ea27ea7e6d 100644 --- a/src/components/MoneyRequestHeader.js +++ b/src/components/MoneyRequestHeader.js @@ -71,7 +71,7 @@ function MoneyRequestHeader(props) { setIsDeleteModalVisible(false); }, [parentReportAction, setIsDeleteModalVisible]); - const isScanning = !ReportActionsUtils.areAllRequestsBeingSmartScanned(parentReportAction); + const isScanning = ReportActionsUtils.areAllRequestsBeingSmartScanned(parentReportAction); return ( <> @@ -93,8 +93,8 @@ function MoneyRequestHeader(props) { personalDetails={props.personalDetails} shouldShowBackButton={props.isSmallScreenWidth} onBackButtonPress={() => Navigation.goBack(ROUTES.HOME, false, true)} - statusBar={isScanning ? : null} /> + {isScanning && } ); diff --git a/src/components/ReportActionItem/MoneyRequestPreview.js b/src/components/ReportActionItem/MoneyRequestPreview.js index cda2cd5faad0..cbf60dc8a00c 100644 --- a/src/components/ReportActionItem/MoneyRequestPreview.js +++ b/src/components/ReportActionItem/MoneyRequestPreview.js @@ -144,7 +144,7 @@ function MoneyRequestPreview(props) { const transaction = TransactionUtils.getLinkedTransaction(props.action); const {amount: requestAmount, currency: requestCurrency, comment: requestComment, merchant: requestMerchant} = ReportUtils.getTransactionDetails(transaction); const hasReceipt = TransactionUtils.hasReceipt(transaction); - const isScanning = !ReportActionUtils.areAllRequestsBeingSmartScanned(props.action); + const isScanning = ReportActionUtils.areAllRequestsBeingSmartScanned(props.action); const getSettledMessage = () => { switch (lodashGet(props.action, 'originalMessage.paymentType', '')) { @@ -201,7 +201,7 @@ function MoneyRequestPreview(props) { errorRowStyles={[styles.mbn1]} needsOffscreenAlphaCompositing > - + {hasReceipt && ( 0; - const isScanning = hasReceipts && !ReportActionUtils.areAllRequestsBeingSmartScanned(props.action); + const isScanning = hasReceipts && ReportActionUtils.areAllRequestsBeingSmartScanned(props.action); const hasOnlyOneReceiptRequest = numberOfRequests === 1 && hasReceipts; const previewSubtitle = hasOnlyOneReceiptRequest ? transactions[0].merchant diff --git a/src/libs/ReportActionsUtils.js b/src/libs/ReportActionsUtils.js index e20b660b9789..63cc48e5db66 100644 --- a/src/libs/ReportActionsUtils.js +++ b/src/libs/ReportActionsUtils.js @@ -596,13 +596,13 @@ function areAllRequestsBeingSmartScanned(reportAction) { if (getNumberOfMoneyRequests(reportAction) > transactions.length) { return false; } - return _.all(transactions, (transaction) => !ReceiptUtils.isBeingScanned(transaction.receipt)); + return _.all(transactions, (transaction) => ReceiptUtils.isBeingScanned(transaction.receipt)); } // If a money request action is not a scanning receipt if (isMoneyRequestAction(reportAction)) { const transaction = TransactionUtils.getTransaction(reportAction.originalMessage.IOUTransactionID); - return TransactionUtils.hasReceipt(transaction) && !ReceiptUtils.isBeingScanned(transaction.receipt); + return TransactionUtils.hasReceipt(transaction) && ReceiptUtils.isBeingScanned(transaction.receipt); } return false; diff --git a/src/libs/TransactionUtils.js b/src/libs/TransactionUtils.js index 432a3192d83f..a9d6f49fd8f2 100644 --- a/src/libs/TransactionUtils.js +++ b/src/libs/TransactionUtils.js @@ -179,7 +179,7 @@ function getReportPreviewTransactionsWithReceipts(reportPreviewAction) { transactionIDs, (transactions, transactionID) => { const transaction = getTransaction(transactionID); - if (!_.isEmpty(transaction)) { + if (hasReceipt(transaction)) { transactions.push(transaction); } return transactions; From 6c8be7125daa2a6687c01029d356f03872908372 Mon Sep 17 00:00:00 2001 From: Andrew Li Date: Thu, 17 Aug 2023 13:13:43 -0400 Subject: [PATCH 48/61] Fix prettier --- src/components/MoneyRequestHeaderStatusBar.js | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/components/MoneyRequestHeaderStatusBar.js b/src/components/MoneyRequestHeaderStatusBar.js index acbed940aaf5..488c122ce5bc 100644 --- a/src/components/MoneyRequestHeaderStatusBar.js +++ b/src/components/MoneyRequestHeaderStatusBar.js @@ -34,4 +34,4 @@ function MoneyRequestHeaderStatusBar() { MoneyRequestHeaderStatusBar.displayName = 'MoneyRequestHeaderStatusBar'; -export default MoneyRequestHeaderStatusBar; \ No newline at end of file +export default MoneyRequestHeaderStatusBar; From 25c91a09415f46f61f893ad9a0c5d0de63a7c941 Mon Sep 17 00:00:00 2001 From: Andrew Li Date: Thu, 17 Aug 2023 13:44:56 -0400 Subject: [PATCH 49/61] Remove dependency cycle by moving getNumberOfScanningReceipts and areAllReceiptsBeingSmartScanned to ReportUtils --- src/components/MoneyRequestHeader.js | 4 +- .../ReportActionItem/MoneyRequestPreview.js | 3 +- .../ReportActionItem/ReportPreview.js | 4 +- src/languages/en.js | 5 +- src/languages/es.js | 5 +- src/libs/ReceiptUtils.js | 6 +- src/libs/ReportActionsUtils.js | 64 +++---------------- src/libs/ReportUtils.js | 58 ++++++++++++++++- src/libs/TransactionUtils.js | 5 ++ .../PopoverReportActionContextMenu.js | 4 +- 10 files changed, 84 insertions(+), 74 deletions(-) diff --git a/src/components/MoneyRequestHeader.js b/src/components/MoneyRequestHeader.js index 54ea27ea7e6d..1efafd6eda5c 100644 --- a/src/components/MoneyRequestHeader.js +++ b/src/components/MoneyRequestHeader.js @@ -71,7 +71,7 @@ function MoneyRequestHeader(props) { setIsDeleteModalVisible(false); }, [parentReportAction, setIsDeleteModalVisible]); - const isScanning = ReportActionsUtils.areAllRequestsBeingSmartScanned(parentReportAction); + const isScanning = ReportUtils.areAllRequestsBeingSmartScanned(parentReportAction); return ( <> @@ -83,7 +83,7 @@ function MoneyRequestHeader(props) { threeDotsMenuItems={[ { icon: Expensicons.Trashcan, - text: translate('reportActionContextMenu.deleteAction', {isMoneyRequest: ReportActionsUtils.isMoneyRequestAction(parentReportAction)}), + text: translate('reportActionContextMenu.deleteAction', {action: parentReportAction}), onSelected: () => setIsDeleteModalVisible(true), }, ]} diff --git a/src/components/ReportActionItem/MoneyRequestPreview.js b/src/components/ReportActionItem/MoneyRequestPreview.js index cbf60dc8a00c..48a4fba9d6a8 100644 --- a/src/components/ReportActionItem/MoneyRequestPreview.js +++ b/src/components/ReportActionItem/MoneyRequestPreview.js @@ -29,7 +29,6 @@ import * as ReportUtils from '../../libs/ReportUtils'; import * as TransactionUtils from '../../libs/TransactionUtils'; import refPropTypes from '../refPropTypes'; import PressableWithFeedback from '../Pressable/PressableWithoutFeedback'; -import * as ReportActionUtils from '../../libs/ReportActionsUtils'; import * as ReceiptUtils from '../../libs/ReceiptUtils'; import ReportActionItemImages from './ReportActionItemImages'; @@ -144,7 +143,7 @@ function MoneyRequestPreview(props) { const transaction = TransactionUtils.getLinkedTransaction(props.action); const {amount: requestAmount, currency: requestCurrency, comment: requestComment, merchant: requestMerchant} = ReportUtils.getTransactionDetails(transaction); const hasReceipt = TransactionUtils.hasReceipt(transaction); - const isScanning = ReportActionUtils.areAllRequestsBeingSmartScanned(props.action); + const isScanning = ReportUtils.areAllRequestsBeingSmartScanned(props.action); const getSettledMessage = () => { switch (lodashGet(props.action, 'originalMessage.paymentType', '')) { diff --git a/src/components/ReportActionItem/ReportPreview.js b/src/components/ReportActionItem/ReportPreview.js index c10fdd233f7a..ccd0cf466d7f 100644 --- a/src/components/ReportActionItem/ReportPreview.js +++ b/src/components/ReportActionItem/ReportPreview.js @@ -103,12 +103,12 @@ function ReportPreview(props) { const iouSettled = ReportUtils.isSettled(props.iouReportID); const numberOfRequests = ReportActionUtils.getNumberOfMoneyRequests(props.action); - const numberOfScanningReceipts = ReportActionUtils.getNumberOfScanningReceipts(props.iouReport); + const numberOfScanningReceipts = ReportUtils.getNumberOfScanningReceipts(props.iouReport); const moneyRequestComment = lodashGet(props.action, 'childLastMoneyRequestComment', ''); const transactions = TransactionUtils.getReportPreviewTransactionsWithReceipts(props.action); const hasReceipts = transactions.length > 0; - const isScanning = hasReceipts && ReportActionUtils.areAllRequestsBeingSmartScanned(props.action); + const isScanning = hasReceipts && ReportUtils.areAllRequestsBeingSmartScanned(props.action); const hasOnlyOneReceiptRequest = numberOfRequests === 1 && hasReceipts; const previewSubtitle = hasOnlyOneReceiptRequest ? transactions[0].merchant diff --git a/src/languages/en.js b/src/languages/en.js index ae69e0441537..1cd97988e552 100755 --- a/src/languages/en.js +++ b/src/languages/en.js @@ -1,5 +1,6 @@ import {CONST as COMMON_CONST} from 'expensify-common/lib/CONST'; import CONST from '../CONST'; +import * as ReportActionsUtils from '../libs/ReportActionsUtils'; /* eslint-disable max-len */ export default { @@ -275,8 +276,8 @@ export default { markAsUnread: 'Mark as unread', markAsRead: 'Mark as read', editComment: 'Edit comment', - deleteAction: ({isMoneyRequest}) => `Delete ${isMoneyRequest ? 'request' : 'comment'}`, - deleteConfirmation: ({isMoneyRequest}) => `Are you sure you want to delete this ${isMoneyRequest ? 'request' : 'comment'}?`, + deleteAction: ({action}) => `Delete ${ReportActionsUtils.isMoneyRequest(action) ? 'request' : 'comment'}`, + deleteConfirmation: ({action}) => `Are you sure you want to delete this ${ReportActionsUtils.isMoneyRequest(action) ? 'request' : 'comment'}?`, onlyVisible: 'Only visible to', replyInThread: 'Reply in thread', flagAsOffensive: 'Flag as offensive', diff --git a/src/languages/es.js b/src/languages/es.js index ef0616b41d92..e1a84486759f 100644 --- a/src/languages/es.js +++ b/src/languages/es.js @@ -1,4 +1,5 @@ import CONST from '../CONST'; +import * as ReportActionsUtils from '../libs/ReportActionsUtils'; /* eslint-disable max-len */ export default { @@ -274,8 +275,8 @@ export default { markAsUnread: 'Marcar como no leído', markAsRead: 'Marcar como leído', editComment: 'Editar comentario', - deleteAction: ({isMoneyRequest = false}) => `Eliminar ${isMoneyRequest ? 'pedido' : 'comentario'}`, - deleteConfirmation: ({isMoneyRequest = false}) => `¿Estás seguro de que quieres eliminar este ${isMoneyRequest ? 'pedido' : 'comentario'}`, + deleteAction: ({action}) => `Eliminar ${ReportActionsUtils.isMoneyRequest(action) ? 'pedido' : 'comentario'}`, + deleteConfirmation: ({action}) => `¿Estás seguro de que quieres eliminar este ${ReportActionsUtils.isMoneyRequest(action) ? 'pedido' : 'comentario'}`, onlyVisible: 'Visible sólo para', replyInThread: 'Responder en el hilo', flagAsOffensive: 'Marcar como ofensivo', diff --git a/src/libs/ReceiptUtils.js b/src/libs/ReceiptUtils.js index b4451d338cd4..c1c028073690 100644 --- a/src/libs/ReceiptUtils.js +++ b/src/libs/ReceiptUtils.js @@ -65,8 +65,4 @@ function getThumbnailAndImageURIs(path, filename) { return {thumbnail: null, image}; } -function isBeingScanned(receipt) { - return receipt.state === CONST.IOU.RECEIPT_STATE.SCANREADY || receipt.state === CONST.IOU.RECEIPT_STATE.SCANNING; -} - -export {validateReceipt, getThumbnailAndImageURIs, isBeingScanned}; +export {validateReceipt, getThumbnailAndImageURIs}; diff --git a/src/libs/ReportActionsUtils.js b/src/libs/ReportActionsUtils.js index 63cc48e5db66..ca78dcc62a21 100644 --- a/src/libs/ReportActionsUtils.js +++ b/src/libs/ReportActionsUtils.js @@ -9,8 +9,6 @@ import CONST from '../CONST'; import ONYXKEYS from '../ONYXKEYS'; import Log from './Log'; import isReportMessageAttachment from './isReportMessageAttachment'; -import * as TransactionUtils from './TransactionUtils'; -import * as ReceiptUtils from './ReceiptUtils'; const allReports = {}; Onyx.connect({ @@ -579,64 +577,19 @@ function getNumberOfMoneyRequests(reportPreviewAction) { } /** - * For report previews and money request actions, we display a "Receipt scan in progress" indicator - * instead of the report total only when we have no report total ready to show. This is the case when - * all requests are receipts that are being SmartScanned. As soon as we have a non-receipt request, - * or as soon as one receipt request is done scanning, we have at least one - * "ready" money request, and we remove this indicator to show the partial report total. - * - * @param {Object|null} reportAction + * @param {*} reportAction * @returns {Boolean} */ -function areAllRequestsBeingSmartScanned(reportAction) { - // If a report preview has at least one manual request or at least one scanned receipt - if (isReportPreviewAction(reportAction)) { - const transactions = TransactionUtils.getReportPreviewTransactionsWithReceipts(reportAction); - // If we have more requests than requests with receipts, we have some manual requests - if (getNumberOfMoneyRequests(reportAction) > transactions.length) { - return false; - } - return _.all(transactions, (transaction) => ReceiptUtils.isBeingScanned(transaction.receipt)); - } - - // If a money request action is not a scanning receipt - if (isMoneyRequestAction(reportAction)) { - const transaction = TransactionUtils.getTransaction(reportAction.originalMessage.IOUTransactionID); - return TransactionUtils.hasReceipt(transaction) && ReceiptUtils.isBeingScanned(transaction.receipt); - } - - return false; -} - -/** - * Returns the number of receipts associated with an IOU report that are still being scanned. - * Note that we search the IOU report for this number, since scanning receipts will be whispers - * in the IOU report for only the submitter and we can get an accurate count. - * - * @param {Object|null} iouReport - * @returns {Number} - */ -function getNumberOfScanningReceipts(iouReport) { - const reportActions = lodashGet(allReportActions, lodashGet(iouReport, 'reportID'), []); - return _.reduce( - reportActions, - (count, reportAction) => { - if (!isMoneyRequestAction(reportAction)) { - return count; - } - const transaction = TransactionUtils.getTransaction(reportAction.originalMessage.IOUTransactionID); - return count + Number(TransactionUtils.hasReceipt(transaction) && ReceiptUtils.isBeingScanned(transaction.receipt)); - }, - 0, - ); +function isSplitBillAction(reportAction) { + return lodashGet(reportAction, 'originalMessage.type', '') === CONST.IOU.REPORT_ACTION_TYPE.SPLIT; } /** - * @param {*} reportAction - * @returns {Boolean} + * @param {*} reportID + * @returns {[Object]} */ -function isSplitBillAction(reportAction) { - return lodashGet(reportAction, 'originalMessage.type', '') === CONST.IOU.REPORT_ACTION_TYPE.SPLIT; +function getAllReportActions(reportID) { + return lodashGet(allReportActions, reportID, []); } export { @@ -672,8 +625,7 @@ export { isWhisperAction, isPendingRemove, getReportAction, - areAllRequestsBeingSmartScanned, getNumberOfMoneyRequests, - getNumberOfScanningReceipts, isSplitBillAction, + getAllReportActions, }; diff --git a/src/libs/ReportUtils.js b/src/libs/ReportUtils.js index a403a7118e65..f79e2c0933dc 100644 --- a/src/libs/ReportUtils.js +++ b/src/libs/ReportUtils.js @@ -1229,6 +1229,36 @@ function getTransactionDetails(transaction) { }; } +/** + * For report previews and money request actions, we display a "Receipt scan in progress" indicator + * instead of the report total only when we have no report total ready to show. This is the case when + * all requests are receipts that are being SmartScanned. As soon as we have a non-receipt request, + * or as soon as one receipt request is done scanning, we have at least one + * "ready" money request, and we remove this indicator to show the partial report total. + * + * @param {Object|null} reportAction + * @returns {Boolean} + */ +function areAllRequestsBeingSmartScanned(reportAction) { + // If a report preview has at least one manual request or at least one scanned receipt + if (ReportActionsUtils.isReportPreviewAction(reportAction)) { + const transactions = TransactionUtils.getReportPreviewTransactionsWithReceipts(reportAction); + // If we have more requests than requests with receipts, we have some manual requests + if (ReportActionsUtils.getNumberOfMoneyRequests(reportAction) > transactions.length) { + return false; + } + return _.all(transactions, (transaction) => TransactionUtils.isReceiptBeingScanned(transaction.receipt)); + } + + // If a money request action is not a scanning receipt + if (ReportActionsUtils.isMoneyRequestAction(reportAction)) { + const transaction = TransactionUtils.getTransaction(reportAction.originalMessage.IOUTransactionID); + return TransactionUtils.hasReceipt(transaction) && TransactionUtils.isReceiptBeingScanned(transaction.receipt); + } + + return false; +} + /** * Given a parent IOU report action get report name for the LHN. * @@ -1240,7 +1270,7 @@ function getTransactionReportName(reportAction) { return Localize.translateLocal('parentReportAction.deletedRequest'); } - if (ReportActionsUtils.areAllRequestsBeingSmartScanned(reportAction)) { + if (areAllRequestsBeingSmartScanned(reportAction)) { return Localize.translateLocal('iou.receiptScanning'); } @@ -3154,6 +3184,30 @@ function getTaskAssigneeChatOnyxData(accountID, assigneeEmail, assigneeAccountID }; } +/** + * Returns the number of receipts associated with an IOU report that are still being scanned. + * Note that we search the IOU report for this number, since scanning receipts will be whispers + * in the IOU report for only the submitter and we can get an accurate count. + * + * @param {Object|null} iouReport + * @returns {Number} + */ +function getNumberOfScanningReceipts(iouReport) { + const reportID = lodashGet(iouReport, 'reportID'); + const reportActions = ReportActionsUtils.getAllReportActions(reportID); + return _.reduce( + reportActions, + (count, reportAction) => { + if (!ReportActionsUtils.isMoneyRequestAction(reportAction)) { + return count; + } + const transaction = TransactionUtils.getTransaction(reportAction.originalMessage.IOUTransactionID); + return count + Number(TransactionUtils.hasReceipt(transaction) && TransactionUtils.isReceiptBeingScanned(transaction.receipt)); + }, + 0, + ); +} + export { getReportParticipantsTitle, isReportMessageAttachment, @@ -3279,4 +3333,6 @@ export { getTransactionReportName, getTransactionDetails, getTaskAssigneeChatOnyxData, + areAllRequestsBeingSmartScanned, + getNumberOfScanningReceipts, }; diff --git a/src/libs/TransactionUtils.js b/src/libs/TransactionUtils.js index a9d6f49fd8f2..1cd2729b1ad6 100644 --- a/src/libs/TransactionUtils.js +++ b/src/libs/TransactionUtils.js @@ -203,6 +203,10 @@ function getAllReportTransactions(reportID) { return _.filter(allTransactions, (transaction) => transaction.reportID === reportID); } +function isReceiptBeingScanned(transaction) { + return transaction.receipt.state === CONST.IOU.RECEIPT_STATE.SCANREADY || transaction.receipt.state === CONST.IOU.RECEIPT_STATE.SCANNING; +} + export { buildOptimisticTransaction, getUpdatedTransaction, @@ -214,5 +218,6 @@ export { getLinkedTransaction, getAllReportTransactions, hasReceipt, + isReceiptBeingScanned, getReportPreviewTransactionsWithReceipts, }; diff --git a/src/pages/home/report/ContextMenu/PopoverReportActionContextMenu.js b/src/pages/home/report/ContextMenu/PopoverReportActionContextMenu.js index 9f2ea19f3711..81858564b416 100644 --- a/src/pages/home/report/ContextMenu/PopoverReportActionContextMenu.js +++ b/src/pages/home/report/ContextMenu/PopoverReportActionContextMenu.js @@ -322,7 +322,7 @@ class PopoverReportActionContextMenu extends React.Component { /> Date: Thu, 17 Aug 2023 13:57:25 -0400 Subject: [PATCH 50/61] Update Spanish translations --- src/languages/es.js | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/src/languages/es.js b/src/languages/es.js index e1a84486759f..a7d3f249a824 100644 --- a/src/languages/es.js +++ b/src/languages/es.js @@ -383,9 +383,9 @@ export default { pending: 'Pendiente', deleteReceipt: 'Eliminar recibo', receiptScanning: 'Escaneo de recibo en curso…', - receiptStatusTitle: 'Escaneado…', - receiptStatusText: 'Solo tú puedes ver este recibo cuando se está escaneando. Vuelve más tarde o ingresa los detalles ahora.', - requestCount: ({count, scanningReceipts = 0}) => `${count} solicitudes${scanningReceipts > 0 ? `, ${scanningReceipts} escaneo` : ''}`, + receiptStatusTitle: 'Escaneando…', + receiptStatusText: 'Solo tú puedes ver este recibo cuando se está escaneando. Vuelve más tarde o introduce los detalles ahora.', + requestCount: ({count, scanningReceipts = 0}) => `${count} solicitudes${scanningReceipts > 0 ? `, ${scanningReceipts} escaneando` : ''}`, deleteRequest: 'Eliminar pedido', deleteConfirmation: '¿Estás seguro de que quieres eliminar este pedido?', settledExpensify: 'Pagado', From 69b1e8fb94b2d7961ede507456171fdfcf86495a Mon Sep 17 00:00:00 2001 From: Andrew Li Date: Thu, 17 Aug 2023 13:58:39 -0400 Subject: [PATCH 51/61] Fix typo --- src/languages/en.js | 4 ++-- src/languages/es.js | 4 ++-- src/libs/ReportUtils.js | 6 +++--- 3 files changed, 7 insertions(+), 7 deletions(-) diff --git a/src/languages/en.js b/src/languages/en.js index 1cd97988e552..d284e997c801 100755 --- a/src/languages/en.js +++ b/src/languages/en.js @@ -276,8 +276,8 @@ export default { markAsUnread: 'Mark as unread', markAsRead: 'Mark as read', editComment: 'Edit comment', - deleteAction: ({action}) => `Delete ${ReportActionsUtils.isMoneyRequest(action) ? 'request' : 'comment'}`, - deleteConfirmation: ({action}) => `Are you sure you want to delete this ${ReportActionsUtils.isMoneyRequest(action) ? 'request' : 'comment'}?`, + deleteAction: ({action}) => `Delete ${ReportActionsUtils.isMoneyRequestAction(action) ? 'request' : 'comment'}`, + deleteConfirmation: ({action}) => `Are you sure you want to delete this ${ReportActionsUtils.isMoneyRequestAction(action) ? 'request' : 'comment'}?`, onlyVisible: 'Only visible to', replyInThread: 'Reply in thread', flagAsOffensive: 'Flag as offensive', diff --git a/src/languages/es.js b/src/languages/es.js index a7d3f249a824..49f92937ec2c 100644 --- a/src/languages/es.js +++ b/src/languages/es.js @@ -275,8 +275,8 @@ export default { markAsUnread: 'Marcar como no leído', markAsRead: 'Marcar como leído', editComment: 'Editar comentario', - deleteAction: ({action}) => `Eliminar ${ReportActionsUtils.isMoneyRequest(action) ? 'pedido' : 'comentario'}`, - deleteConfirmation: ({action}) => `¿Estás seguro de que quieres eliminar este ${ReportActionsUtils.isMoneyRequest(action) ? 'pedido' : 'comentario'}`, + deleteAction: ({action}) => `Eliminar ${ReportActionsUtils.isMoneyRequestAction(action) ? 'pedido' : 'comentario'}`, + deleteConfirmation: ({action}) => `¿Estás seguro de que quieres eliminar este ${ReportActionsUtils.isMoneyRequestAction(action) ? 'pedido' : 'comentario'}`, onlyVisible: 'Visible sólo para', replyInThread: 'Responder en el hilo', flagAsOffensive: 'Marcar como ofensivo', diff --git a/src/libs/ReportUtils.js b/src/libs/ReportUtils.js index f79e2c0933dc..0999f6063aa0 100644 --- a/src/libs/ReportUtils.js +++ b/src/libs/ReportUtils.js @@ -1247,13 +1247,13 @@ function areAllRequestsBeingSmartScanned(reportAction) { if (ReportActionsUtils.getNumberOfMoneyRequests(reportAction) > transactions.length) { return false; } - return _.all(transactions, (transaction) => TransactionUtils.isReceiptBeingScanned(transaction.receipt)); + return _.all(transactions, (transaction) => TransactionUtils.isReceiptBeingScanned(transaction)); } // If a money request action is not a scanning receipt if (ReportActionsUtils.isMoneyRequestAction(reportAction)) { const transaction = TransactionUtils.getTransaction(reportAction.originalMessage.IOUTransactionID); - return TransactionUtils.hasReceipt(transaction) && TransactionUtils.isReceiptBeingScanned(transaction.receipt); + return TransactionUtils.hasReceipt(transaction) && TransactionUtils.isReceiptBeingScanned(transaction); } return false; @@ -3202,7 +3202,7 @@ function getNumberOfScanningReceipts(iouReport) { return count; } const transaction = TransactionUtils.getTransaction(reportAction.originalMessage.IOUTransactionID); - return count + Number(TransactionUtils.hasReceipt(transaction) && TransactionUtils.isReceiptBeingScanned(transaction.receipt)); + return count + Number(TransactionUtils.hasReceipt(transaction) && TransactionUtils.isReceiptBeingScanned(transaction)); }, 0, ); From 51558571b96f19c4d176c7a26741c5e5ff87548b Mon Sep 17 00:00:00 2001 From: Andrew Li Date: Thu, 17 Aug 2023 14:27:24 -0400 Subject: [PATCH 52/61] Show number of additional scanning receipts in overlay --- src/components/ReportActionItem/ReportPreview.js | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/components/ReportActionItem/ReportPreview.js b/src/components/ReportActionItem/ReportPreview.js index ccd0cf466d7f..5b96f4c7d078 100644 --- a/src/components/ReportActionItem/ReportPreview.js +++ b/src/components/ReportActionItem/ReportPreview.js @@ -166,7 +166,7 @@ function ReportPreview(props) { ReceiptUtils.getThumbnailAndImageURIs(receipt.source, filename))} size={3} - total={ReportActionUtils.getNumberOfMoneyRequests(props.action)} + total={ReportUtils.getNumberOfScanningReceipts(props.iouReport)} isHovered={props.isHovered || isScanning} /> )} From cc483cce04b76ae4748c50dce3b3a6b45a3839c1 Mon Sep 17 00:00:00 2001 From: Andrew Li Date: Thu, 17 Aug 2023 14:57:56 -0400 Subject: [PATCH 53/61] Separate last 3 receipts to display from total number of receipts in an IOU report --- src/components/MoneyRequestHeader.js | 4 +- .../ReportActionItem/MoneyRequestPreview.js | 2 +- .../ReportActionItemImages.js | 2 +- .../ReportActionItem/ReportPreview.js | 13 ++-- src/libs/ReportUtils.js | 65 +++++++++++++------ src/libs/TransactionUtils.js | 22 ------- 6 files changed, 58 insertions(+), 50 deletions(-) diff --git a/src/components/MoneyRequestHeader.js b/src/components/MoneyRequestHeader.js index 1efafd6eda5c..60d8a121d4bb 100644 --- a/src/components/MoneyRequestHeader.js +++ b/src/components/MoneyRequestHeader.js @@ -20,6 +20,7 @@ import * as ReportActionsUtils from '../libs/ReportActionsUtils'; import ConfirmModal from './ConfirmModal'; import useLocalize from '../hooks/useLocalize'; import MoneyRequestHeaderStatusBar from './MoneyRequestHeaderStatusBar'; +import * as TransactionUtils from '../libs/TransactionUtils'; const propTypes = { /** The report currently being looked at */ @@ -71,7 +72,8 @@ function MoneyRequestHeader(props) { setIsDeleteModalVisible(false); }, [parentReportAction, setIsDeleteModalVisible]); - const isScanning = ReportUtils.areAllRequestsBeingSmartScanned(parentReportAction); + const transaction = TransactionUtils.getLinkedTransaction(parentReportAction); + const isScanning = TransactionUtils.hasReceipt(transaction) && TransactionUtils.isReceiptBeingScanned(transaction); return ( <> diff --git a/src/components/ReportActionItem/MoneyRequestPreview.js b/src/components/ReportActionItem/MoneyRequestPreview.js index 48a4fba9d6a8..53fe2e052b8a 100644 --- a/src/components/ReportActionItem/MoneyRequestPreview.js +++ b/src/components/ReportActionItem/MoneyRequestPreview.js @@ -143,7 +143,7 @@ function MoneyRequestPreview(props) { const transaction = TransactionUtils.getLinkedTransaction(props.action); const {amount: requestAmount, currency: requestCurrency, comment: requestComment, merchant: requestMerchant} = ReportUtils.getTransactionDetails(transaction); const hasReceipt = TransactionUtils.hasReceipt(transaction); - const isScanning = ReportUtils.areAllRequestsBeingSmartScanned(props.action); + const isScanning = hasReceipt && TransactionUtils.isReceiptBeingScanned(transaction); const getSettledMessage = () => { switch (lodashGet(props.action, 'originalMessage.paymentType', '')) { diff --git a/src/components/ReportActionItem/ReportActionItemImages.js b/src/components/ReportActionItem/ReportActionItemImages.js index 57c03947c496..a5dcc0ccaea2 100644 --- a/src/components/ReportActionItem/ReportActionItemImages.js +++ b/src/components/ReportActionItem/ReportActionItemImages.js @@ -28,7 +28,7 @@ const propTypes = { total: PropTypes.number, /** if the corresponding report action item is hovered */ - isHovered: PropTypes.boolean, + isHovered: PropTypes.bool, }; const defaultProps = { diff --git a/src/components/ReportActionItem/ReportPreview.js b/src/components/ReportActionItem/ReportPreview.js index 5b96f4c7d078..6ccafca75818 100644 --- a/src/components/ReportActionItem/ReportPreview.js +++ b/src/components/ReportActionItem/ReportPreview.js @@ -27,7 +27,6 @@ import themeColors from '../../styles/themes/default'; import reportPropTypes from '../../pages/reportPropTypes'; import * as ReceiptUtils from '../../libs/ReceiptUtils'; import * as ReportActionUtils from '../../libs/ReportActionsUtils'; -import * as TransactionUtils from '../../libs/TransactionUtils'; import ReportActionItemImages from './ReportActionItemImages'; const propTypes = { @@ -106,12 +105,14 @@ function ReportPreview(props) { const numberOfScanningReceipts = ReportUtils.getNumberOfScanningReceipts(props.iouReport); const moneyRequestComment = lodashGet(props.action, 'childLastMoneyRequestComment', ''); - const transactions = TransactionUtils.getReportPreviewTransactionsWithReceipts(props.action); - const hasReceipts = transactions.length > 0; - const isScanning = hasReceipts && ReportUtils.areAllRequestsBeingSmartScanned(props.action); + const transactionsWithReceipts = ReportUtils.getTransactionsWithReceipts(props.iouReport); + const hasReceipts = transactionsWithReceipts.length > 0; + const isScanning = hasReceipts && ReportUtils.areAllRequestsBeingSmartScanned(props.iouReport, props.action); + const lastThreeTransactionsWithReceipts = ReportUtils.getReportPreviewDisplayTransactions(props.action); + const hasOnlyOneReceiptRequest = numberOfRequests === 1 && hasReceipts; const previewSubtitle = hasOnlyOneReceiptRequest - ? transactions[0].merchant + ? transactionsWithReceipts[0].merchant : props.translate('iou.requestCount', { count: numberOfRequests, scanningReceipts: numberOfScanningReceipts, @@ -164,7 +165,7 @@ function ReportPreview(props) { {hasReceipts && ( ReceiptUtils.getThumbnailAndImageURIs(receipt.source, filename))} + images={_.map(lastThreeTransactionsWithReceipts, ({receipt, filename}) => ReceiptUtils.getThumbnailAndImageURIs(receipt.source, filename))} size={3} total={ReportUtils.getNumberOfScanningReceipts(props.iouReport)} isHovered={props.isHovered || isScanning} diff --git a/src/libs/ReportUtils.js b/src/libs/ReportUtils.js index 0999f6063aa0..51c023e132bb 100644 --- a/src/libs/ReportUtils.js +++ b/src/libs/ReportUtils.js @@ -1230,33 +1230,47 @@ function getTransactionDetails(transaction) { } /** - * For report previews and money request actions, we display a "Receipt scan in progress" indicator + * Gets all transactions on an IOU report with a receipt + * + * @param {Object|null} iouReport + * @returns {[Object]} + */ +function getTransactionsWithReceipts(iouReport) { + const reportID = lodashGet(iouReport, 'reportID'); + const reportActions = ReportActionsUtils.getAllReportActions(reportID); + return _.reduce( + reportActions, + (transactions, action) => { + if (ReportActionsUtils.isMoneyRequestAction(action)) { + const transaction = TransactionUtils.getLinkedTransaction(action); + if (TransactionUtils.hasReceipt(transaction)) { + transactions.push(transaction); + } + } + return transactions; + }, + [], + ); +} + +/** + * For report previews, we display a "Receipt scan in progress" indicator * instead of the report total only when we have no report total ready to show. This is the case when * all requests are receipts that are being SmartScanned. As soon as we have a non-receipt request, * or as soon as one receipt request is done scanning, we have at least one * "ready" money request, and we remove this indicator to show the partial report total. * - * @param {Object|null} reportAction + * @param {Object|null} iouReport + * @param {Object|null} reportPreviewAction the preview action associated with the IOU report * @returns {Boolean} */ -function areAllRequestsBeingSmartScanned(reportAction) { - // If a report preview has at least one manual request or at least one scanned receipt - if (ReportActionsUtils.isReportPreviewAction(reportAction)) { - const transactions = TransactionUtils.getReportPreviewTransactionsWithReceipts(reportAction); - // If we have more requests than requests with receipts, we have some manual requests - if (ReportActionsUtils.getNumberOfMoneyRequests(reportAction) > transactions.length) { - return false; - } - return _.all(transactions, (transaction) => TransactionUtils.isReceiptBeingScanned(transaction)); - } - - // If a money request action is not a scanning receipt - if (ReportActionsUtils.isMoneyRequestAction(reportAction)) { - const transaction = TransactionUtils.getTransaction(reportAction.originalMessage.IOUTransactionID); - return TransactionUtils.hasReceipt(transaction) && TransactionUtils.isReceiptBeingScanned(transaction); +function areAllRequestsBeingSmartScanned(iouReport, reportPreviewAction) { + const transactions = getTransactionsWithReceipts(iouReport); + // If we have more requests than requests with receipts, we have some manual requests + if (ReportActionsUtils.getNumberOfMoneyRequests(reportPreviewAction) > transactions.length) { + return false; } - - return false; + return _.all(transactions, (transaction) => TransactionUtils.isReceiptBeingScanned(transaction)); } /** @@ -3208,6 +3222,17 @@ function getNumberOfScanningReceipts(iouReport) { ); } +/** + * Get the last 3 transactions with receipts of an IOU report that will be displayed on the report preview + * + * @param {Object} reportPreviewAction + * @returns {Object} + */ +function getReportPreviewDisplayTransactions(reportPreviewAction) { + const transactionIDs = lodashGet(reportPreviewAction, ['childLastReceiptTransactionIDs'], '').split(','); + return _.map(transactionIDs, (transactionID) => TransactionUtils.getTransaction(transactionID)); +} + export { getReportParticipantsTitle, isReportMessageAttachment, @@ -3335,4 +3360,6 @@ export { getTaskAssigneeChatOnyxData, areAllRequestsBeingSmartScanned, getNumberOfScanningReceipts, + getReportPreviewDisplayTransactions, + getTransactionsWithReceipts, }; diff --git a/src/libs/TransactionUtils.js b/src/libs/TransactionUtils.js index 1cd2729b1ad6..de6a497693f7 100644 --- a/src/libs/TransactionUtils.js +++ b/src/libs/TransactionUtils.js @@ -167,27 +167,6 @@ function getCreated(transaction) { return ''; } -/** - * Get the transactions related to a report preview with receipts - * - * @param {Object} reportPreviewAction - * @returns {Object} - */ -function getReportPreviewTransactionsWithReceipts(reportPreviewAction) { - const transactionIDs = lodashGet(reportPreviewAction, ['childLastReceiptTransactionIDs'], '').split(','); - return _.reduce( - transactionIDs, - (transactions, transactionID) => { - const transaction = getTransaction(transactionID); - if (hasReceipt(transaction)) { - transactions.push(transaction); - } - return transactions; - }, - [], - ); -} - /** * Get the details linked to the IOU reportAction * @@ -219,5 +198,4 @@ export { getAllReportTransactions, hasReceipt, isReceiptBeingScanned, - getReportPreviewTransactionsWithReceipts, }; From 7687d7f7bd3950dfa6ee455c50f429833faad72b Mon Sep 17 00:00:00 2001 From: Andrew Li Date: Thu, 17 Aug 2023 16:32:05 -0400 Subject: [PATCH 54/61] Remove getNumberOfScanningReceipts --- .../ReportActionItem/ReportPreview.js | 5 +-- src/libs/ReportUtils.js | 33 ++++--------------- 2 files changed, 10 insertions(+), 28 deletions(-) diff --git a/src/components/ReportActionItem/ReportPreview.js b/src/components/ReportActionItem/ReportPreview.js index 6ccafca75818..21d45c10c8a2 100644 --- a/src/components/ReportActionItem/ReportPreview.js +++ b/src/components/ReportActionItem/ReportPreview.js @@ -27,6 +27,7 @@ import themeColors from '../../styles/themes/default'; import reportPropTypes from '../../pages/reportPropTypes'; import * as ReceiptUtils from '../../libs/ReceiptUtils'; import * as ReportActionUtils from '../../libs/ReportActionsUtils'; +import * as TransactionUtils from '../../libs/TransactionUtils'; import ReportActionItemImages from './ReportActionItemImages'; const propTypes = { @@ -102,10 +103,10 @@ function ReportPreview(props) { const iouSettled = ReportUtils.isSettled(props.iouReportID); const numberOfRequests = ReportActionUtils.getNumberOfMoneyRequests(props.action); - const numberOfScanningReceipts = ReportUtils.getNumberOfScanningReceipts(props.iouReport); const moneyRequestComment = lodashGet(props.action, 'childLastMoneyRequestComment', ''); const transactionsWithReceipts = ReportUtils.getTransactionsWithReceipts(props.iouReport); + const numberOfScanningReceipts = _.filter(transactionsWithReceipts, (transaction) => TransactionUtils.isReceiptBeingScanned(transaction)).length; const hasReceipts = transactionsWithReceipts.length > 0; const isScanning = hasReceipts && ReportUtils.areAllRequestsBeingSmartScanned(props.iouReport, props.action); const lastThreeTransactionsWithReceipts = ReportUtils.getReportPreviewDisplayTransactions(props.action); @@ -167,7 +168,7 @@ function ReportPreview(props) { ReceiptUtils.getThumbnailAndImageURIs(receipt.source, filename))} size={3} - total={ReportUtils.getNumberOfScanningReceipts(props.iouReport)} + total={transactionsWithReceipts.length} isHovered={props.isHovered || isScanning} /> )} diff --git a/src/libs/ReportUtils.js b/src/libs/ReportUtils.js index 51c023e132bb..34308140c905 100644 --- a/src/libs/ReportUtils.js +++ b/src/libs/ReportUtils.js @@ -3198,30 +3198,6 @@ function getTaskAssigneeChatOnyxData(accountID, assigneeEmail, assigneeAccountID }; } -/** - * Returns the number of receipts associated with an IOU report that are still being scanned. - * Note that we search the IOU report for this number, since scanning receipts will be whispers - * in the IOU report for only the submitter and we can get an accurate count. - * - * @param {Object|null} iouReport - * @returns {Number} - */ -function getNumberOfScanningReceipts(iouReport) { - const reportID = lodashGet(iouReport, 'reportID'); - const reportActions = ReportActionsUtils.getAllReportActions(reportID); - return _.reduce( - reportActions, - (count, reportAction) => { - if (!ReportActionsUtils.isMoneyRequestAction(reportAction)) { - return count; - } - const transaction = TransactionUtils.getTransaction(reportAction.originalMessage.IOUTransactionID); - return count + Number(TransactionUtils.hasReceipt(transaction) && TransactionUtils.isReceiptBeingScanned(transaction)); - }, - 0, - ); -} - /** * Get the last 3 transactions with receipts of an IOU report that will be displayed on the report preview * @@ -3230,7 +3206,13 @@ function getNumberOfScanningReceipts(iouReport) { */ function getReportPreviewDisplayTransactions(reportPreviewAction) { const transactionIDs = lodashGet(reportPreviewAction, ['childLastReceiptTransactionIDs'], '').split(','); - return _.map(transactionIDs, (transactionID) => TransactionUtils.getTransaction(transactionID)); + return _.reduce(transactionIDs, (transactions, transactionID) => { + const transaction = TransactionUtils.getTransaction(transactionID); + if (TransactionUtils.hasReceipt(transaction)) { + transactions.push(transaction); + } + return transactions; + }, []); } export { @@ -3359,7 +3341,6 @@ export { getTransactionDetails, getTaskAssigneeChatOnyxData, areAllRequestsBeingSmartScanned, - getNumberOfScanningReceipts, getReportPreviewDisplayTransactions, getTransactionsWithReceipts, }; From 47883244817e7bd48b69d3c769453baf5a5e847b Mon Sep 17 00:00:00 2001 From: Andrew Li Date: Thu, 17 Aug 2023 16:53:19 -0400 Subject: [PATCH 55/61] Fix header name --- src/libs/ReportUtils.js | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/src/libs/ReportUtils.js b/src/libs/ReportUtils.js index 34308140c905..42cfef8542a5 100644 --- a/src/libs/ReportUtils.js +++ b/src/libs/ReportUtils.js @@ -1284,11 +1284,11 @@ function getTransactionReportName(reportAction) { return Localize.translateLocal('parentReportAction.deletedRequest'); } - if (areAllRequestsBeingSmartScanned(reportAction)) { + const transaction = TransactionUtils.getLinkedTransaction(reportAction); + if (TransactionUtils.hasReceipt(transaction) && TransactionUtils.isReceiptBeingScanned(transaction)) { return Localize.translateLocal('iou.receiptScanning'); } - const transaction = TransactionUtils.getLinkedTransaction(reportAction); const {amount, currency, comment} = getTransactionDetails(transaction); return Localize.translateLocal(ReportActionsUtils.isSentMoneyReportAction(reportAction) ? 'iou.threadSentMoneyReportName' : 'iou.threadRequestReportName', { From dec55b4ce54b3a4f7041038b915835eb37a01ff2 Mon Sep 17 00:00:00 2001 From: Andrew Li Date: Thu, 17 Aug 2023 17:05:51 -0400 Subject: [PATCH 56/61] Run prettier --- src/libs/ReportUtils.js | 18 +++++++++++------- 1 file changed, 11 insertions(+), 7 deletions(-) diff --git a/src/libs/ReportUtils.js b/src/libs/ReportUtils.js index 42cfef8542a5..a272e258221b 100644 --- a/src/libs/ReportUtils.js +++ b/src/libs/ReportUtils.js @@ -3206,13 +3206,17 @@ function getTaskAssigneeChatOnyxData(accountID, assigneeEmail, assigneeAccountID */ function getReportPreviewDisplayTransactions(reportPreviewAction) { const transactionIDs = lodashGet(reportPreviewAction, ['childLastReceiptTransactionIDs'], '').split(','); - return _.reduce(transactionIDs, (transactions, transactionID) => { - const transaction = TransactionUtils.getTransaction(transactionID); - if (TransactionUtils.hasReceipt(transaction)) { - transactions.push(transaction); - } - return transactions; - }, []); + return _.reduce( + transactionIDs, + (transactions, transactionID) => { + const transaction = TransactionUtils.getTransaction(transactionID); + if (TransactionUtils.hasReceipt(transaction)) { + transactions.push(transaction); + } + return transactions; + }, + [], + ); } export { From 55a66a3e5d6b788dea7787019ef446020e91ee60 Mon Sep 17 00:00:00 2001 From: Andrew Li Date: Fri, 18 Aug 2023 19:25:50 -0400 Subject: [PATCH 57/61] Fix weird Android image upload issue --- src/libs/fileDownload/FileUtils.js | 7 +++++-- 1 file changed, 5 insertions(+), 2 deletions(-) diff --git a/src/libs/fileDownload/FileUtils.js b/src/libs/fileDownload/FileUtils.js index 01c3f7fcb834..a98a33b2934f 100644 --- a/src/libs/fileDownload/FileUtils.js +++ b/src/libs/fileDownload/FileUtils.js @@ -1,4 +1,4 @@ -import {Alert, Linking} from 'react-native'; +import {Alert, Linking, Platform} from 'react-native'; import CONST from '../../CONST'; import * as Localize from '../Localize'; import DateUtils from '../DateUtils'; @@ -146,7 +146,10 @@ const readFileAsync = (path, fileName) => return fetch(path) .then((res) => { - if (!res.ok) { + // For some reason, fetch is "Unable to read uploaded file" + // on Android even though the blob is returned, so we'll ignore + // in that case + if (!res.ok && Platform.OS !== 'android') { throw Error(res.statusText); } return res.blob(); From e057aca69952a3e241150a2740d906cee0e87c06 Mon Sep 17 00:00:00 2001 From: Andrew Li Date: Sat, 19 Aug 2023 00:21:16 -0400 Subject: [PATCH 58/61] Use optimistic transaction in updating report preview --- src/libs/actions/IOU.js | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/src/libs/actions/IOU.js b/src/libs/actions/IOU.js index 0e83546ebdb7..c2c8c1c78b56 100644 --- a/src/libs/actions/IOU.js +++ b/src/libs/actions/IOU.js @@ -428,9 +428,9 @@ function getMoneyRequestInformation(report, participant, comment, amount, curren let reportPreviewAction = isNewIOUReport ? null : ReportActionsUtils.getReportPreviewAction(chatReport.reportID, iouReport.reportID); if (reportPreviewAction) { - reportPreviewAction = ReportUtils.updateReportPreview(iouReport, reportPreviewAction, comment, existingTransaction); + reportPreviewAction = ReportUtils.updateReportPreview(iouReport, reportPreviewAction, comment, optimisticTransaction); } else { - reportPreviewAction = ReportUtils.buildOptimisticReportPreview(chatReport, iouReport, comment, existingTransaction); + reportPreviewAction = ReportUtils.buildOptimisticReportPreview(chatReport, iouReport, comment, optimisticTransaction); } // Add optimistic personal details for participant From b12644c6e6e64581141136023aef9118f9403330 Mon Sep 17 00:00:00 2001 From: Andrew Li Date: Mon, 21 Aug 2023 12:08:21 -0400 Subject: [PATCH 59/61] Access transactionID safely in case of pay IOU actions --- .../AttachmentCarousel/extractAttachmentsFromReport.js | 8 +++++++- 1 file changed, 7 insertions(+), 1 deletion(-) diff --git a/src/components/Attachments/AttachmentCarousel/extractAttachmentsFromReport.js b/src/components/Attachments/AttachmentCarousel/extractAttachmentsFromReport.js index 61df1024a534..511c47119e23 100644 --- a/src/components/Attachments/AttachmentCarousel/extractAttachmentsFromReport.js +++ b/src/components/Attachments/AttachmentCarousel/extractAttachmentsFromReport.js @@ -5,6 +5,7 @@ import * as TransactionUtils from '../../../libs/TransactionUtils'; import * as ReceiptUtils from '../../../libs/ReceiptUtils'; import CONST from '../../../CONST'; import tryResolveUrlFromApiRoot from '../../../libs/tryResolveUrlFromApiRoot'; +import lodashGet from 'lodash/get'; /** * Constructs the initial component state from report actions @@ -43,7 +44,12 @@ function extractAttachmentsFromReport(report, reportActions) { // We're handling receipts differently here because receipt images are not // part of the report action message, the images are constructed client-side if (ReportActionsUtils.isMoneyRequestAction(action)) { - const transaction = TransactionUtils.getTransaction(action.originalMessage.IOUTransactionID); + const transactionID = lodashGet(action, ['originalMessage', 'IOUTransactionID']); + if (!transactionID) { + return; + } + + const transaction = TransactionUtils.getTransaction(transactionID); if (TransactionUtils.hasReceipt(transaction)) { const {image} = ReceiptUtils.getThumbnailAndImageURIs(transaction.receipt.source, transaction.filename); attachments.unshift({ From fec32bfa9d34b1b1993cbd21129ad1c4d855c42b Mon Sep 17 00:00:00 2001 From: Andrew Li Date: Mon, 21 Aug 2023 12:19:39 -0400 Subject: [PATCH 60/61] Fix lint --- .../AttachmentCarousel/extractAttachmentsFromReport.js | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/components/Attachments/AttachmentCarousel/extractAttachmentsFromReport.js b/src/components/Attachments/AttachmentCarousel/extractAttachmentsFromReport.js index 511c47119e23..b967d5ab0066 100644 --- a/src/components/Attachments/AttachmentCarousel/extractAttachmentsFromReport.js +++ b/src/components/Attachments/AttachmentCarousel/extractAttachmentsFromReport.js @@ -1,11 +1,11 @@ import {Parser as HtmlParser} from 'htmlparser2'; import _ from 'underscore'; +import lodashGet from 'lodash/get'; import * as ReportActionsUtils from '../../../libs/ReportActionsUtils'; import * as TransactionUtils from '../../../libs/TransactionUtils'; import * as ReceiptUtils from '../../../libs/ReceiptUtils'; import CONST from '../../../CONST'; import tryResolveUrlFromApiRoot from '../../../libs/tryResolveUrlFromApiRoot'; -import lodashGet from 'lodash/get'; /** * Constructs the initial component state from report actions From de09a89a988c8cd7ba4bd8d6453a3d60c85cb82b Mon Sep 17 00:00:00 2001 From: Andrew Li Date: Mon, 21 Aug 2023 12:52:28 -0400 Subject: [PATCH 61/61] Fix lint --- src/libs/TransactionUtils.js | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/libs/TransactionUtils.js b/src/libs/TransactionUtils.js index 0ebc3b219712..a995769eebe0 100644 --- a/src/libs/TransactionUtils.js +++ b/src/libs/TransactionUtils.js @@ -243,4 +243,4 @@ export { getAllReportTransactions, hasReceipt, isReceiptBeingScanned, -}; \ No newline at end of file +};