Skip to content

Commit

Permalink
Merge pull request #29474 from Expensify/georgia-cardTransactions-sho…
Browse files Browse the repository at this point in the history
…wEReceipts

[Wave 8 - ECard Transactions] Show EReceipts in ReportPreviews and Attachment View
  • Loading branch information
luacmartins authored Oct 17, 2023
2 parents 7fcb4c1 + d7ab4c4 commit e92c44f
Show file tree
Hide file tree
Showing 18 changed files with 180 additions and 67 deletions.
5 changes: 5 additions & 0 deletions src/ROUTES.ts
Original file line number Diff line number Diff line change
Expand Up @@ -292,6 +292,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: {
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -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,
Expand Down
16 changes: 15 additions & 1 deletion src/components/Attachments/AttachmentView/index.js
Original file line number Diff line number Diff line change
@@ -1,5 +1,5 @@
import React, {memo, useState} from 'react';
import {View, ActivityIndicator} from 'react-native';
import {View, ScrollView, ActivityIndicator} from 'react-native';
import _ from 'underscore';
import PropTypes from 'prop-types';
import Str from 'expensify-common/lib/str';
Expand All @@ -22,6 +22,7 @@ import * as TransactionUtils from '../../../libs/TransactionUtils';
import DistanceEReceipt from '../../DistanceEReceipt';
import useNetwork from '../../../hooks/useNetwork';
import ONYXKEYS from '../../../ONYXKEYS';
import EReceipt from '../../EReceipt';

const propTypes = {
...attachmentViewPropTypes,
Expand Down Expand Up @@ -101,6 +102,19 @@ function AttachmentView({
);
}

if (TransactionUtils.hasEReceipt(transaction)) {
return (
<View style={[styles.flex1, styles.alignItemsCenter]}>
<ScrollView
style={styles.w100}
contentContainerStyle={[styles.flexGrow1, styles.justifyContentCenter, styles.alignItemsCenter]}
>
<EReceipt transactionID={transaction.transactionID} />
</ScrollView>
</View>
);
}

// 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')))) {
Expand Down
2 changes: 1 addition & 1 deletion src/components/EReceipt.js
Original file line number Diff line number Diff line change
Expand Up @@ -59,7 +59,7 @@ function EReceipt({transaction, transactionID}) {
</View>
<View style={[styles.flexColumn, styles.justifyContentBetween, styles.alignItemsCenter, styles.ph9, styles.flex1]}>
<View style={[styles.alignItemsCenter, styles.alignSelfCenter, styles.flexColumn, styles.gap2, styles.mb8]}>
<View style={[styles.flexRow, styles.justifyContentCenter]}>
<View style={[styles.flexRow, styles.justifyContentCenter, StyleUtils.getWidthStyle(variables.eReceiptTextContainerWidth)]}>
<View style={[styles.flexColumn, styles.pt1]}>
<Text style={[styles.eReceiptCurrency, secondaryTextColorStyle]}>{currency}</Text>
</View>
Expand Down
3 changes: 1 addition & 2 deletions src/components/MoneyRequestConfirmationList.js
Original file line number Diff line number Diff line change
Expand Up @@ -536,8 +536,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} = props.receiptPath && props.receiptFilename ? ReceiptUtils.getThumbnailAndImageURIs(transaction) : {};

return (
<OptionsSelector
Expand Down
2 changes: 1 addition & 1 deletion src/components/ReportActionItem/MoneyRequestPreview.js
Original file line number Diff line number Diff line change
Expand Up @@ -172,7 +172,7 @@ function MoneyRequestPreview(props) {
!_.isEmpty(requestMerchant) && !props.isBillSplit && requestMerchant !== CONST.TRANSACTION.PARTIAL_TRANSACTION_MERCHANT && requestMerchant !== CONST.TRANSACTION.DEFAULT_MERCHANT;
const shouldShowDescription = !_.isEmpty(description) && !shouldShowMerchant;

const receiptImages = hasReceipt ? [ReceiptUtils.getThumbnailAndImageURIs(props.transaction.receipt.source, props.transaction.filename || '')] : [];
const receiptImages = hasReceipt ? [ReceiptUtils.getThumbnailAndImageURIs(props.transaction)] : [];

const getSettledMessage = () => {
if (isExpensifyCardTransaction) {
Expand Down
3 changes: 2 additions & 1 deletion src/components/ReportActionItem/MoneyRequestView.js
Original file line number Diff line number Diff line change
Expand Up @@ -151,7 +151,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);
}

Expand All @@ -170,6 +170,7 @@ function MoneyRequestView({report, betas, parentReport, policyCategories, should
<ReportActionItemImage
thumbnail={receiptURIs.thumbnail}
image={receiptURIs.image}
transaction={transaction}
enablePreviewModal
/>
</View>
Expand Down
50 changes: 36 additions & 14 deletions src/components/ReportActionItem/ReportActionItemImage.js
Original file line number Diff line number Diff line change
@@ -1,5 +1,7 @@
import React from 'react';
import PropTypes from 'prop-types';
import {View} from 'react-native';
import _ from 'underscore';
import styles from '../../styles/styles';
import Image from '../Image';
import ThumbnailImage from '../ThumbnailImage';
Expand All @@ -10,6 +12,9 @@ import {ShowContextMenuContext} from '../ShowContextMenuContext';
import Navigation from '../../libs/Navigation/Navigation';
import PressableWithoutFocus from '../Pressable/PressableWithoutFocus';
import useLocalize from '../../hooks/useLocalize';
import EReceiptThumbnail from '../EReceiptThumbnail';
import transactionPropTypes from '../transactionPropTypes';
import * as TransactionUtils from '../../libs/TransactionUtils';

const propTypes = {
/** thumbnail URI for the image */
Expand All @@ -20,10 +25,14 @@ const propTypes = {

/** whether or not to enable the image preview modal */
enablePreviewModal: PropTypes.bool,

/* The transaction associated with this image, if any. Passed for handling eReceipts. */
transaction: transactionPropTypes,
};

const defaultProps = {
thumbnail: null,
transaction: {},
enablePreviewModal: false,
};

Expand All @@ -33,24 +42,37 @@ const defaultProps = {
* and optional preview modal as well.
*/

function ReportActionItemImage({thumbnail, image, enablePreviewModal}) {
function ReportActionItemImage({thumbnail, image, enablePreviewModal, transaction}) {
const {translate} = useLocalize();
const imageSource = tryResolveUrlFromApiRoot(image || '');
const thumbnailSource = tryResolveUrlFromApiRoot(thumbnail || '');
const isEReceipt = !_.isEmpty(transaction) && TransactionUtils.hasEReceipt(transaction);

let receiptImageComponent;

const receiptImageComponent = thumbnail ? (
<ThumbnailImage
previewSourceURL={thumbnailSource}
style={[styles.w100, styles.h100]}
isAuthTokenRequired
shouldDynamicallyResize={false}
/>
) : (
<Image
source={{uri: image}}
style={[styles.w100, styles.h100]}
/>
);
if (isEReceipt) {
receiptImageComponent = (
<View style={[styles.w100, styles.h100]}>
<EReceiptThumbnail transactionID={transaction.transactionID} />
</View>
);
} else if (thumbnail) {
receiptImageComponent = (
<ThumbnailImage
previewSourceURL={thumbnailSource}
style={[styles.w100, styles.h100]}
isAuthTokenRequired
shouldDynamicallyResize={false}
/>
);
} else {
receiptImageComponent = (
<Image
source={{uri: image}}
style={[styles.w100, styles.h100]}
/>
);
}

if (enablePreviewModal) {
return (
Expand Down
5 changes: 4 additions & 1 deletion src/components/ReportActionItem/ReportActionItemImages.js
Original file line number Diff line number Diff line change
Expand Up @@ -7,13 +7,15 @@ import Text from '../Text';
import ReportActionItemImage from './ReportActionItemImage';
import * as StyleUtils from '../../styles/StyleUtils';
import variables from '../../styles/variables';
import transactionPropTypes from '../transactionPropTypes';

const propTypes = {
/** array of image and thumbnail URIs */
images: PropTypes.arrayOf(
PropTypes.shape({
thumbnail: PropTypes.string,
image: PropTypes.oneOfType([PropTypes.string, PropTypes.number]),
transaction: transactionPropTypes,
}),
).isRequired,

Expand Down Expand Up @@ -68,7 +70,7 @@ function ReportActionItemImages({images, size, total, isHovered}) {

return (
<View style={[styles.reportActionItemImages, hoverStyle, heightStyle]}>
{_.map(shownImages, ({thumbnail, image}, index) => {
{_.map(shownImages, ({thumbnail, image, transaction}, index) => {
const isLastImage = index === numberOfShownImages - 1;

// Show a border to separate multiple images. Shown to the right for each except the last.
Expand All @@ -82,6 +84,7 @@ function ReportActionItemImages({images, size, total, isHovered}) {
<ReportActionItemImage
thumbnail={thumbnail}
image={image}
transaction={transaction}
/>
{isLastImage && remaining > 0 && (
<View style={[styles.reportActionItemImagesMoreContainer]}>
Expand Down
4 changes: 2 additions & 2 deletions src/components/ReportActionItem/ReportPreview.js
Original file line number Diff line number Diff line change
Expand Up @@ -125,8 +125,8 @@ function ReportPreview(props) {
const hasReceipts = transactionsWithReceipts.length > 0;
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 lastThreeTransactionsWithReceipts = transactionsWithReceipts.slice(-3);
const lastThreeReceipts = _.map(lastThreeTransactionsWithReceipts, (transaction) => ReceiptUtils.getThumbnailAndImageURIs(transaction));
const hasNonReimbursableTransactions = ReportUtils.hasNonReimbursableTransactions(props.iouReportID);
const hasOnlyOneReceiptRequest = numberOfRequests === 1 && hasReceipts;
const previewSubtitle = hasOnlyOneReceiptRequest
Expand Down
2 changes: 1 addition & 1 deletion src/libs/CardUtils.ts
Original file line number Diff line number Diff line change
Expand Up @@ -46,7 +46,7 @@ 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}`;
return cardDescriptor ? `${card.bank} - ${cardDescriptor}` : `${card.bank}`;
}

/**
Expand Down
18 changes: 15 additions & 3 deletions src/libs/ReceiptUtils.ts
Original file line number Diff line number Diff line change
Expand Up @@ -6,10 +6,13 @@ 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;
thumbnail: string | null;
transaction?: Transaction;
};

type FileNameAndExtension = {
Expand All @@ -20,12 +23,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 ?? '';
// 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), transaction};
}

// For local files, we won't have a thumbnail yet
if (isReceiptImage && (path.startsWith('blob:') || path.startsWith('file:'))) {
return {thumbnail: null, image: path};
Expand Down
24 changes: 0 additions & 24 deletions src/libs/ReportUtils.js
Original file line number Diff line number Diff line change
Expand Up @@ -3839,29 +3839,6 @@ function getParticipantsIDs(report) {
return participants;
}

/**
* 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, ['childRecentReceiptTransactionIDs']);
return _.reduce(
_.keys(transactionIDs),
(transactions, transactionID) => {
if (transactionIDs[transactionID] !== null) {
const transaction = TransactionUtils.getTransaction(transactionID);
if (TransactionUtils.hasReceipt(transaction)) {
transactions.push(transaction);
}
}
return transactions;
},
[],
);
}

/**
* Return iou report action display message
*
Expand Down Expand Up @@ -4058,7 +4035,6 @@ export {
canEditMoneyRequest,
buildTransactionThread,
areAllRequestsBeingSmartScanned,
getReportPreviewDisplayTransactions,
getTransactionsWithReceipts,
hasNonReimbursableTransactions,
hasMissingSmartscanFields,
Expand Down
18 changes: 9 additions & 9 deletions src/libs/TransactionUtils.ts
Original file line number Diff line number Diff line change
Expand Up @@ -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 isMerchantMissing(transaction: Transaction) {
Expand Down Expand Up @@ -365,13 +372,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
Expand Down Expand Up @@ -472,7 +472,7 @@ export {
getLinkedTransaction,
getAllReportTransactions,
hasReceipt,
hasEreceipt,
hasEReceipt,
hasRoute,
isReceiptBeingScanned,
getValidWaypoints,
Expand Down
Loading

0 comments on commit e92c44f

Please sign in to comment.