From aa07b9c77dd0e2a317327dd0502a51abc1cc2faf Mon Sep 17 00:00:00 2001 From: Georgia Monahan Date: Thu, 12 Oct 2023 19:31:22 +0100 Subject: [PATCH] Add support for displaying ECard Transaction in ReportPreviews --- src/CONST.ts | 1 + src/ROUTES.ts | 5 + .../extractAttachmentsFromReport.js | 2 +- .../Attachments/AttachmentView/index.js | 11 ++ .../MoneyRequestConfirmationList.js | 3 +- .../ReportActionItem/MoneyRequestPreview.js | 2 +- .../ReportActionItem/MoneyRequestView.js | 2 +- .../ReportActionItem/ReportActionItemImage.js | 15 +- .../ReportActionItem/ReportPreview.js | 2 +- src/libs/CardUtils.ts | 3 +- src/libs/ReceiptUtils.ts | 17 +- src/libs/TransactionUtils.ts | 16 +- src/stories/ReportActionItemImages.stories.js | 149 ++++++++++++++++++ src/styles/styles.js | 1 - 14 files changed, 209 insertions(+), 20 deletions(-) create mode 100644 src/stories/ReportActionItemImages.stories.js diff --git a/src/CONST.ts b/src/CONST.ts index 58fdea7654cb..a92244ba001a 100755 --- a/src/CONST.ts +++ b/src/CONST.ts @@ -1476,6 +1476,7 @@ const CONST = { MAKE_REQUEST_WITH_SIDE_EFFECTS: 'makeRequestWithSideEffects', }, + ERECEIPT_PATH: 'eReceipt/', ERECEIPT_COLORS: { YELLOW: 'Yellow', ICE: 'Ice', diff --git a/src/ROUTES.ts b/src/ROUTES.ts index 7127c1483c26..06f8de303e2c 100644 --- a/src/ROUTES.ts +++ b/src/ROUTES.ts @@ -286,6 +286,11 @@ export default { I_AM_A_TEACHER: 'teachersunite/i-am-a-teacher', INTRO_SCHOOL_PRINCIPAL: 'teachersunite/intro-school-principal', + ERECEIPT: { + route: 'eReceipt/:transactionID', + getRoute: (transactionID: string) => `eReceipt/${transactionID}`, + }, + WORKSPACE_NEW: 'workspace/new', WORKSPACE_NEW_ROOM: 'workspace/new-room', WORKSPACE_INITIAL: { diff --git a/src/components/Attachments/AttachmentCarousel/extractAttachmentsFromReport.js b/src/components/Attachments/AttachmentCarousel/extractAttachmentsFromReport.js index 8a623a44709f..dae0191b2158 100644 --- a/src/components/Attachments/AttachmentCarousel/extractAttachmentsFromReport.js +++ b/src/components/Attachments/AttachmentCarousel/extractAttachmentsFromReport.js @@ -53,7 +53,7 @@ function extractAttachmentsFromReport(report, reportActions) { const transaction = TransactionUtils.getTransaction(transactionID); if (TransactionUtils.hasReceipt(transaction)) { - const {image} = ReceiptUtils.getThumbnailAndImageURIs(transaction.receipt.source, transaction.filename); + const {image} = ReceiptUtils.getThumbnailAndImageURIs(transaction); attachments.unshift({ source: tryResolveUrlFromApiRoot(image), isAuthTokenRequired: true, diff --git a/src/components/Attachments/AttachmentView/index.js b/src/components/Attachments/AttachmentView/index.js index f4d3036ff802..5de495cbb2c3 100755 --- a/src/components/Attachments/AttachmentView/index.js +++ b/src/components/Attachments/AttachmentView/index.js @@ -18,6 +18,8 @@ import addEncryptedAuthTokenToURL from '../../../libs/addEncryptedAuthTokenToURL import * as StyleUtils from '../../../styles/StyleUtils'; import {attachmentViewPropTypes, attachmentViewDefaultProps} from './propTypes'; import useNetwork from '../../../hooks/useNetwork'; +import CONST from '../../../CONST'; +import EReceipt from '../../EReceipt'; const propTypes = { ...attachmentViewPropTypes, @@ -92,6 +94,15 @@ function AttachmentView({ ); } + if((_.isString(source)) && source.startsWith(CONST.ERECEIPT_PATH)) { + const transactionIDFromURL = source.split(CONST.ERECEIPT_PATH)[1]; + return ( + + + + ); + } + // Check both source and file.name since PDFs dragged into the text field // will appear with a source that is a blob if ((_.isString(source) && Str.isPDF(source)) || (file && Str.isPDF(file.name || translate('attachmentView.unknownFilename')))) { diff --git a/src/components/MoneyRequestConfirmationList.js b/src/components/MoneyRequestConfirmationList.js index 98ec01de16f8..86cc43ea043d 100755 --- a/src/components/MoneyRequestConfirmationList.js +++ b/src/components/MoneyRequestConfirmationList.js @@ -519,8 +519,7 @@ function MoneyRequestConfirmationList(props) { ); }, [confirm, props.bankAccountRoute, props.iouCurrencyCode, props.iouType, props.isReadOnly, props.policyID, selectedParticipants, splitOrRequestOptions, translate, formError]); - const {image: receiptImage, thumbnail: receiptThumbnail} = - props.receiptPath && props.receiptFilename ? ReceiptUtils.getThumbnailAndImageURIs(props.receiptPath, props.receiptFilename) : {}; + const {image: receiptImage, thumbnail: receiptThumbnail} = transaction ? ReceiptUtils.getThumbnailAndImageURIs(transaction) : {}; return ( { if (isExpensifyCardTransaction || isDistanceRequest) { diff --git a/src/components/ReportActionItem/MoneyRequestView.js b/src/components/ReportActionItem/MoneyRequestView.js index 079bc64d96bf..89372270833e 100644 --- a/src/components/ReportActionItem/MoneyRequestView.js +++ b/src/components/ReportActionItem/MoneyRequestView.js @@ -126,7 +126,7 @@ function MoneyRequestView({report, betas, parentReport, policyCategories, should let receiptURIs; let hasErrors = false; if (hasReceipt) { - receiptURIs = ReceiptUtils.getThumbnailAndImageURIs(transaction.receipt.source, transaction.filename); + receiptURIs = ReceiptUtils.getThumbnailAndImageURIs(transaction); hasErrors = canEdit && TransactionUtils.hasMissingSmartscanFields(transaction); } diff --git a/src/components/ReportActionItem/ReportActionItemImage.js b/src/components/ReportActionItem/ReportActionItemImage.js index 98bdede0fe26..2b27ac50777c 100644 --- a/src/components/ReportActionItem/ReportActionItemImage.js +++ b/src/components/ReportActionItem/ReportActionItemImage.js @@ -1,5 +1,6 @@ import React from 'react'; import PropTypes from 'prop-types'; +import {View} from 'react-native'; import styles from '../../styles/styles'; import Image from '../Image'; import ThumbnailImage from '../ThumbnailImage'; @@ -10,6 +11,7 @@ import {ShowContextMenuContext} from '../ShowContextMenuContext'; import Navigation from '../../libs/Navigation/Navigation'; import PressableWithoutFocus from '../Pressable/PressableWithoutFocus'; import useLocalize from '../../hooks/useLocalize'; +import EReceiptThumbnail from '../EReceiptThumbnail'; const propTypes = { /** thumbnail URI for the image */ @@ -38,7 +40,11 @@ function ReportActionItemImage({thumbnail, image, enablePreviewModal}) { const imageSource = tryResolveUrlFromApiRoot(image || ''); const thumbnailSource = tryResolveUrlFromApiRoot(thumbnail || ''); - const receiptImageComponent = thumbnail ? ( + + const isEReceipt = imageSource.startsWith(CONST.ERECEIPT_PATH); + + + let receiptImageComponent = thumbnail ? ( ); + if(isEReceipt) { + const transactionIDFromURL = imageSource.split(CONST.ERECEIPT_PATH)[1]; + receiptImageComponent = ( + + ); + } + if (enablePreviewModal) { return ( diff --git a/src/components/ReportActionItem/ReportPreview.js b/src/components/ReportActionItem/ReportPreview.js index f9001ed51258..b9bec38878d3 100644 --- a/src/components/ReportActionItem/ReportPreview.js +++ b/src/components/ReportActionItem/ReportPreview.js @@ -126,7 +126,7 @@ function ReportPreview(props) { const isScanning = hasReceipts && ReportUtils.areAllRequestsBeingSmartScanned(props.iouReportID, props.action); const hasErrors = hasReceipts && ReportUtils.hasMissingSmartscanFields(props.iouReportID); const lastThreeTransactionsWithReceipts = ReportUtils.getReportPreviewDisplayTransactions(props.action); - const lastThreeReceipts = _.map(lastThreeTransactionsWithReceipts, ({receipt, filename}) => ReceiptUtils.getThumbnailAndImageURIs(receipt.source, filename || '')); + const lastThreeReceipts = _.map(lastThreeTransactionsWithReceipts, (transaction) => ReceiptUtils.getThumbnailAndImageURIs(transaction)); const hasOnlyOneReceiptRequest = numberOfRequests === 1 && hasReceipts; const previewSubtitle = hasOnlyOneReceiptRequest diff --git a/src/libs/CardUtils.ts b/src/libs/CardUtils.ts index e6c7480974ca..03ff9248552b 100644 --- a/src/libs/CardUtils.ts +++ b/src/libs/CardUtils.ts @@ -47,7 +47,8 @@ function getCardDescription(cardID: number) { return ''; } const cardDescriptor = card.state === CONST.EXPENSIFY_CARD.STATE.NOT_ACTIVATED ? Localize.translateLocal('cardTransactions.notActivated') : card.lastFourPAN; - return `${card.bank} - ${cardDescriptor}`; + const result = cardDescriptor ? `${card.bank} - ${cardDescriptor}` : `${card.bank}`; + return result; } /** diff --git a/src/libs/ReceiptUtils.ts b/src/libs/ReceiptUtils.ts index cdc45cb119d5..1a7fccb3fa9f 100644 --- a/src/libs/ReceiptUtils.ts +++ b/src/libs/ReceiptUtils.ts @@ -6,6 +6,8 @@ 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'; +import { Transaction } from '../types/onyx'; +import ROUTES from '../ROUTES'; type ThumbnailAndImageURI = { image: ImageSourcePropType | string; @@ -20,12 +22,21 @@ type FileNameAndExtension = { /** * Grab the appropriate receipt image and thumbnail URIs based on file type * - * @param path URI to image, i.e. blob:new.expensify.com/9ef3a018-4067-47c6-b29f-5f1bd35f213d or expensify.com/receipts/w_e616108497ef940b7210ec6beb5a462d01a878f4.jpg - * @param filename of uploaded image or last part of remote URI + * @param transaction */ -function getThumbnailAndImageURIs(path: string, filename: string): ThumbnailAndImageURI { +function getThumbnailAndImageURIs(transaction: Transaction): ThumbnailAndImageURI { + // URI to image, i.e. blob:new.expensify.com/9ef3a018-4067-47c6-b29f-5f1bd35f213d or expensify.com/receipts/w_e616108497ef940b7210ec6beb5a462d01a878f4.jpg + const path = transaction?.receipt?.source ?? 'https://hips.hearstapps.com/hmg-prod/images/dog-puppy-on-garden-royalty-free-image-1586966191.jpg?crop=0.752xw:1.00xh;0.175xw,0&resize=1200:*'; + // filename of uploaded image or last part of remote URI + const filename = transaction?.filename ?? ''; const isReceiptImage = Str.isImage(filename); + const hasEReceipt = transaction?.hasEReceipt; + + if(hasEReceipt){ + return {thumbnail: null, image: ROUTES.ERECEIPT.getRoute(transaction.transactionID)}; + } + // For local files, we won't have a thumbnail yet if (isReceiptImage && (path.startsWith('blob:') || path.startsWith('file:'))) { return {thumbnail: null, image: path}; diff --git a/src/libs/TransactionUtils.ts b/src/libs/TransactionUtils.ts index 43b1c2f39902..aa3ddbbb302c 100644 --- a/src/libs/TransactionUtils.ts +++ b/src/libs/TransactionUtils.ts @@ -76,8 +76,15 @@ function buildOptimisticTransaction( }; } +/** + * Check if the transaction has an Ereceipt + */ +function hasEreceipt(transaction: Transaction | undefined | null): boolean { + return !!transaction?.hasEReceipt; +} + function hasReceipt(transaction: Transaction | undefined | null): boolean { - return !!transaction?.receipt?.state; + return !!transaction?.receipt?.state || hasEreceipt(transaction); } function areRequiredFieldsEmpty(transaction: Transaction): boolean { @@ -332,13 +339,6 @@ function hasRoute(transaction: Transaction): boolean { return !!transaction?.routes?.route0?.geometry?.coordinates; } -/** - * Check if the transaction has an Ereceipt - */ -function hasEreceipt(transaction: Transaction): boolean { - return !!transaction?.hasEReceipt; -} - /** * Get the transactions related to a report preview with receipts * Get the details linked to the IOU reportAction diff --git a/src/stories/ReportActionItemImages.stories.js b/src/stories/ReportActionItemImages.stories.js new file mode 100644 index 000000000000..f7a3dd038c2e --- /dev/null +++ b/src/stories/ReportActionItemImages.stories.js @@ -0,0 +1,149 @@ +import React from 'react'; +import ReportActionItemImages from '../components/ReportActionItem/ReportActionItemImages'; +import PressableWithoutFeedback from '../components/Pressable/PressableWithoutFeedback'; + +/** + * We use the Component Story Format for writing stories. Follow the docs here: + * + * https://storybook.js.org/docs/react/writing-stories/introduction#component-story-format + */ +const story = { + title: 'Components/ReportActionItemImages', + component: ReportActionItemImages, +}; + +function Template(args) { + return ( + + {({hovered}) => ( + + )} + + ); +} + +// Arguments can be passed to the component by binding +// See: https://storybook.js.org/docs/react/writing-stories/introduction#using-args +const Default = Template.bind({}); +Default.args = { + images: [{image: 'https://c02.purpledshub.com/uploads/sites/41/2021/05/sleeping-cat-27126ee.jpg', thumbnail: ''}], + size: 1, + total: 1, +}; + +const DisplayJSX = Template.bind({}); +DisplayJSX.args = { + images: [{image: 'eReceipt/FAKE_3', thumbnail: ''}], + size: 1, + total: 1, +}; + +const TwoImages = Template.bind({}); +TwoImages.args = { + images: [ + { + image: 'https://c02.purpledshub.com/uploads/sites/41/2021/05/sleeping-cat-27126ee.jpg', + thumbnail: '', + }, + { + image: 'https://i.guim.co.uk/img/media/7d04c4cb7510a4bd9a8bec449f53425aeccee895/298_266_1150_690/master/1150.jpg?width=1200&quality=85&auto=format&fit=max&s=4ae508ecb99c15ec04610b617efb3fa7', + thumbnail: '', + }, + ], + size: 2, + total: 2, +}; + +const ThreeImages = Template.bind({}); +ThreeImages.args = { + images: [ + { + image: 'https://c02.purpledshub.com/uploads/sites/41/2021/05/sleeping-cat-27126ee.jpg', + thumbnail: '', + }, + { + image: 'https://i.guim.co.uk/img/media/7d04c4cb7510a4bd9a8bec449f53425aeccee895/298_266_1150_690/master/1150.jpg?width=1200&quality=85&auto=format&fit=max&s=4ae508ecb99c15ec04610b617efb3fa7', + thumbnail: '', + }, + { + image: 'https://cdn.theatlantic.com/thumbor/d8lh_KAZuOgBYslMOP4T0iu9Fks=/0x62:2000x1187/1600x900/media/img/mt/2018/03/AP_325360162607/original.jpg', + thumbnail: '', + }, + ], + size: 3, + total: 3, +}; + +const FourImages = Template.bind({}); +FourImages.args = { + images: [ + { + image: 'https://c02.purpledshub.com/uploads/sites/41/2021/05/sleeping-cat-27126ee.jpg', + thumbnail: '', + }, + { + image: 'https://i.guim.co.uk/img/media/7d04c4cb7510a4bd9a8bec449f53425aeccee895/298_266_1150_690/master/1150.jpg?width=1200&quality=85&auto=format&fit=max&s=4ae508ecb99c15ec04610b617efb3fa7', + thumbnail: '', + }, + { + image: 'https://cdn.theatlantic.com/thumbor/d8lh_KAZuOgBYslMOP4T0iu9Fks=/0x62:2000x1187/1600x900/media/img/mt/2018/03/AP_325360162607/original.jpg', + thumbnail: '', + }, + { + image: 'https://www.alleycat.org/wp-content/uploads/2019/03/FELV-cat.jpg', + thumbnail: '', + }, + ], + size: 4, + total: 4, +}; + +const ThreePlusTwoImages = Template.bind({}); +ThreePlusTwoImages.args = { + images: [ + { + image: 'https://c02.purpledshub.com/uploads/sites/41/2021/05/sleeping-cat-27126ee.jpg', + thumbnail: '', + }, + { + image: 'https://i.guim.co.uk/img/media/7d04c4cb7510a4bd9a8bec449f53425aeccee895/298_266_1150_690/master/1150.jpg?width=1200&quality=85&auto=format&fit=max&s=4ae508ecb99c15ec04610b617efb3fa7', + thumbnail: '', + }, + { + image: 'https://cdn.theatlantic.com/thumbor/d8lh_KAZuOgBYslMOP4T0iu9Fks=/0x62:2000x1187/1600x900/media/img/mt/2018/03/AP_325360162607/original.jpg', + thumbnail: '', + }, + ], + size: 3, + total: 5, +}; + +const ThreePlusTenImages = Template.bind({}); +ThreePlusTenImages.args = { + images: [ + { + image: 'https://c02.purpledshub.com/uploads/sites/41/2021/05/sleeping-cat-27126ee.jpg', + thumbnail: '', + }, + { + image: 'https://i.guim.co.uk/img/media/7d04c4cb7510a4bd9a8bec449f53425aeccee895/298_266_1150_690/master/1150.jpg?width=1200&quality=85&auto=format&fit=max&s=4ae508ecb99c15ec04610b617efb3fa7', + thumbnail: '', + }, + { + image: 'https://cdn.theatlantic.com/thumbor/d8lh_KAZuOgBYslMOP4T0iu9Fks=/0x62:2000x1187/1600x900/media/img/mt/2018/03/AP_325360162607/original.jpg', + thumbnail: '', + }, + ], + size: 3, + total: 13, +}; + +export default story; +export {Default, TwoImages, ThreeImages, FourImages, ThreePlusTwoImages, ThreePlusTenImages, DisplayJSX}; diff --git a/src/styles/styles.js b/src/styles/styles.js index 8fa81cd98b21..f004aeada4fe 100644 --- a/src/styles/styles.js +++ b/src/styles/styles.js @@ -3303,7 +3303,6 @@ const styles = (theme) => ({ }, eReceiptContainer: { - flex: 1, width: 335, minHeight: 540, borderRadius: 20,