diff --git a/assets/images/receipt-placeholder-plus.svg b/assets/images/receipt-placeholder-plus.svg
new file mode 100644
index 000000000000..3ebc08b40b06
--- /dev/null
+++ b/assets/images/receipt-placeholder-plus.svg
@@ -0,0 +1,17 @@
+
+
\ No newline at end of file
diff --git a/src/components/Icon/Expensicons.ts b/src/components/Icon/Expensicons.ts
index bd4bb64da050..09f9a3b896ad 100644
--- a/src/components/Icon/Expensicons.ts
+++ b/src/components/Icon/Expensicons.ts
@@ -154,6 +154,7 @@ import Printer from '@assets/images/printer.svg';
import Profile from '@assets/images/profile.svg';
import QrCode from '@assets/images/qrcode.svg';
import QuestionMark from '@assets/images/question-mark-circle.svg';
+import ReceiptPlaceholderPlus from '@assets/images/receipt-placeholder-plus.svg';
import ReceiptPlus from '@assets/images/receipt-plus.svg';
import ReceiptScan from '@assets/images/receipt-scan.svg';
import ReceiptSearch from '@assets/images/receipt-search.svg';
@@ -343,6 +344,7 @@ export {
QrCode,
QuestionMark,
Receipt,
+ ReceiptPlaceholderPlus,
ReceiptPlus,
ReceiptScan,
ReceiptSlash,
diff --git a/src/components/ReceiptEmptyState.tsx b/src/components/ReceiptEmptyState.tsx
index 71d64c7483f1..046026190a5b 100644
--- a/src/components/ReceiptEmptyState.tsx
+++ b/src/components/ReceiptEmptyState.tsx
@@ -1,5 +1,7 @@
import React from 'react';
+import {View} from 'react-native';
import useLocalize from '@hooks/useLocalize';
+import useTheme from '@hooks/useTheme';
import useThemeStyles from '@hooks/useThemeStyles';
import variables from '@styles/variables';
import Icon from './Icon';
@@ -14,12 +16,15 @@ type ReceiptEmptyStateProps = {
onPress?: () => void;
disabled?: boolean;
+
+ isThumbnail?: boolean;
};
// Returns an SVG icon indicating that the user should attach a receipt
-function ReceiptEmptyState({hasError = false, onPress = () => {}, disabled = false}: ReceiptEmptyStateProps) {
+function ReceiptEmptyState({hasError = false, onPress = () => {}, disabled = false, isThumbnail = false}: ReceiptEmptyStateProps) {
const styles = useThemeStyles();
const {translate} = useLocalize();
+ const theme = useTheme();
return (
{}, disabled = fal
onPress={onPress}
disabled={disabled}
disabledStyle={styles.cursorDefault}
- style={[styles.alignItemsCenter, styles.justifyContentCenter, styles.moneyRequestViewImage, styles.moneyRequestAttachReceipt, hasError && styles.borderColorDanger]}
+ style={[
+ styles.alignItemsCenter,
+ styles.justifyContentCenter,
+ styles.moneyRequestViewImage,
+ isThumbnail ? styles.moneyRequestAttachReceiptThumbnail : styles.moneyRequestAttachReceipt,
+ hasError && styles.borderColorDanger,
+ ]}
>
-
+
+
+ {!isThumbnail && (
+
+ )}
+
);
}
diff --git a/src/components/ReceiptImage.tsx b/src/components/ReceiptImage.tsx
index 8c980838b841..bca1ef8b74e6 100644
--- a/src/components/ReceiptImage.tsx
+++ b/src/components/ReceiptImage.tsx
@@ -7,6 +7,7 @@ import EReceiptThumbnail from './EReceiptThumbnail';
import type {IconSize} from './EReceiptThumbnail';
import Image from './Image';
import PDFThumbnail from './PDFThumbnail';
+import ReceiptEmptyState from './ReceiptEmptyState';
import ThumbnailImage from './ThumbnailImage';
type Style = {height: number; borderRadius: number; margin: number};
@@ -79,6 +80,11 @@ type ReceiptImageProps = (
/** The background color of fallback icon */
fallbackIconBackground?: string;
+
+ isEmptyReceipt?: boolean;
+
+ /** Callback to be called on pressing the image */
+ onPress?: () => void;
};
function ReceiptImage({
@@ -97,9 +103,21 @@ function ReceiptImage({
shouldUseInitialObjectPosition = false,
fallbackIconColor,
fallbackIconBackground,
+ isEmptyReceipt = false,
+ onPress,
}: ReceiptImageProps) {
const styles = useThemeStyles();
+ if (isEmptyReceipt) {
+ return (
+
+ );
+ }
+
if (isPDFThumbnail) {
return (
{
if (isCardTransaction) {
@@ -326,6 +326,8 @@ function MoneyRequestPreviewContent({
}
};
+ const shouldDisableOnPress = isBillSplit && isEmptyObject(transaction);
+
const childContainer = (
- {hasReceipt && (
-
- )}
+
{isEmptyObject(transaction) && !ReportActionsUtils.isMessageDeleted(action) && action.pendingAction !== CONST.RED_BRICK_ROAD_PENDING_ACTION.DELETE ? (
) : (
-
+
@@ -484,8 +485,6 @@ function MoneyRequestPreviewContent({
return childContainer;
}
- const shouldDisableOnPress = isBillSplit && isEmptyObject(transaction);
-
return (
void;
};
/**
@@ -73,12 +78,14 @@ function ReportActionItemImage({
enablePreviewModal = false,
transaction,
isLocalFile = false,
+ isEmptyReceipt = false,
fileExtension,
filename,
isSingleImage = true,
readonly = false,
shouldMapHaveBorderRadius,
isFromReviewDuplicates = false,
+ onPress,
}: ReportActionItemImageProps) {
const styles = useThemeStyles();
const {translate} = useLocalize();
@@ -128,6 +135,8 @@ function ReportActionItemImage({
isAuthTokenRequired: false,
source: thumbnail ?? image ?? '',
shouldUseInitialObjectPosition: isDistanceRequest,
+ isEmptyReceipt,
+ onPress,
};
}
diff --git a/src/components/ReportActionItem/ReportActionItemImages.tsx b/src/components/ReportActionItem/ReportActionItemImages.tsx
index f27596556c53..9995b7f77860 100644
--- a/src/components/ReportActionItem/ReportActionItemImages.tsx
+++ b/src/components/ReportActionItem/ReportActionItemImages.tsx
@@ -27,6 +27,9 @@ type ReportActionItemImagesProps = {
/** if the corresponding report action item is hovered */
isHovered?: boolean;
+
+ /** Callback to be called on onPress */
+ onPress?: () => void;
};
/**
@@ -38,7 +41,7 @@ type ReportActionItemImagesProps = {
* additional number when subtracted from size.
*/
-function ReportActionItemImages({images, size, total, isHovered = false}: ReportActionItemImagesProps) {
+function ReportActionItemImages({images, size, total, isHovered = false, onPress}: ReportActionItemImagesProps) {
const theme = useTheme();
const styles = useThemeStyles();
const StyleUtils = useStyleUtils();
@@ -67,7 +70,7 @@ function ReportActionItemImages({images, size, total, isHovered = false}: Report
- {shownImages.map(({thumbnail, isThumbnail, image, transaction, isLocalFile, fileExtension, filename}, index) => {
+ {shownImages.map(({thumbnail, isThumbnail, image, isEmptyReceipt, transaction, isLocalFile, fileExtension, filename}, index) => {
// Show a border to separate multiple images. Shown to the right for each except the last.
const shouldShowBorder = shownImages.length > 1 && index < shownImages.length - 1;
const borderStyle = shouldShowBorder ? styles.reportActionItemImageBorder : {};
@@ -81,11 +84,13 @@ function ReportActionItemImages({images, size, total, isHovered = false}: Report
fileExtension={fileExtension}
image={image}
isLocalFile={isLocalFile}
+ isEmptyReceipt={isEmptyReceipt}
filename={filename}
transaction={transaction}
isThumbnail={isThumbnail}
isSingleImage={numberOfShownImages === 1}
shouldMapHaveBorderRadius={false}
+ onPress={onPress}
/>
);
diff --git a/src/components/ReportActionItem/ReportPreview.tsx b/src/components/ReportActionItem/ReportPreview.tsx
index 63edd29ef4c3..19ab01a27c57 100644
--- a/src/components/ReportActionItem/ReportPreview.tsx
+++ b/src/components/ReportActionItem/ReportPreview.tsx
@@ -173,8 +173,8 @@ function ReportPreview({
ReportUtils.hasWarningTypeViolations(iouReportID, transactionViolations, true) ||
(ReportUtils.isReportOwner(iouReport) && ReportUtils.hasReportViolations(iouReportID)) ||
ReportUtils.hasActionsWithErrors(iouReportID);
- const lastThreeTransactionsWithReceipts = transactionsWithReceipts.slice(-3);
- const lastThreeReceipts = lastThreeTransactionsWithReceipts.map((transaction) => ({...ReceiptUtils.getThumbnailAndImageURIs(transaction), transaction}));
+ const lastThreeTransactions = allTransactions.slice(-3);
+ const lastThreeReceipts = lastThreeTransactions.map((transaction) => ({...ReceiptUtils.getThumbnailAndImageURIs(transaction), transaction}));
const showRTERViolationMessage =
numberOfRequests === 1 &&
TransactionUtils.hasPendingUI(allTransactions.at(0), TransactionUtils.getTransactionViolations(allTransactions.at(0)?.transactionID ?? '-1', transactionViolations));
@@ -448,6 +448,12 @@ function ReportPreview({
checkMarkScale.set(isPaidAnimationRunning ? withDelay(CONST.ANIMATION_PAID_CHECKMARK_DELAY, withSpring(1, {duration: CONST.ANIMATION_PAID_DURATION})) : 1);
}, [isPaidAnimationRunning, iouSettled, checkMarkScale]);
+ const openReportFromPreview = useCallback(() => {
+ Performance.markStart(CONST.TIMING.OPEN_REPORT_FROM_PREVIEW);
+ Timing.start(CONST.TIMING.OPEN_REPORT_FROM_PREVIEW);
+ Navigation.navigate(ROUTES.REPORT_WITH_ID.getRoute(iouReportID));
+ }, [iouReportID]);
+
return (
{
- Performance.markStart(CONST.TIMING.OPEN_REPORT_FROM_PREVIEW);
- Timing.start(CONST.TIMING.OPEN_REPORT_FROM_PREVIEW);
- Navigation.navigate(ROUTES.REPORT_WITH_ID.getRoute(iouReportID));
- }}
+ onPress={openReportFromPreview}
onPressIn={() => DeviceCapabilities.canUseTouchScreen() && ControlSelection.block()}
onPressOut={() => ControlSelection.unblock()}
onLongPress={(event) => showContextMenuForReport(event, contextMenuAnchor, chatReportID, action, checkIfContextMenuActive)}
@@ -470,13 +472,12 @@ function ReportPreview({
accessibilityLabel={translate('iou.viewDetails')}
>
- {hasReceipts && (
-
- )}
+
diff --git a/src/libs/ReceiptUtils.ts b/src/libs/ReceiptUtils.ts
index ed7def2ed1ec..5085f0e2384e 100644
--- a/src/libs/ReceiptUtils.ts
+++ b/src/libs/ReceiptUtils.ts
@@ -16,6 +16,7 @@ type ThumbnailAndImageURI = {
isThumbnail?: boolean;
filename?: string;
fileExtension?: string;
+ isEmptyReceipt?: boolean;
};
/**
@@ -26,6 +27,9 @@ type ThumbnailAndImageURI = {
* @param receiptFileName
*/
function getThumbnailAndImageURIs(transaction: OnyxEntry, receiptPath: ReceiptSource | null = null, receiptFileName: string | null = null): ThumbnailAndImageURI {
+ if (!TransactionUtils.hasReceipt(transaction) && !receiptPath && !receiptFileName) {
+ return {isEmptyReceipt: true};
+ }
if (TransactionUtils.isFetchingWaypointsFromServer(transaction)) {
return {isThumbnail: true, isLocalFile: true};
}
diff --git a/src/styles/index.ts b/src/styles/index.ts
index 6dbdb715a427..2514d4832b5b 100644
--- a/src/styles/index.ts
+++ b/src/styles/index.ts
@@ -4647,6 +4647,21 @@ const styles = (theme: ThemeColors) =>
borderWidth: 1,
},
+ moneyRequestAttachReceiptThumbnail: {
+ backgroundColor: theme.hoverComponentBG,
+ width: '100%',
+ borderWidth: 0,
+ },
+
+ moneyRequestAttachReceiptThumbnailIcon: {
+ position: 'absolute',
+ bottom: -4,
+ right: -4,
+ borderColor: theme.highlightBG,
+ borderWidth: 2,
+ borderRadius: '50%',
+ },
+
mapViewContainer: {
...flex.flex1,
minHeight: 300,
diff --git a/src/styles/variables.ts b/src/styles/variables.ts
index 8b330ce1dd8d..c8a6f7025912 100644
--- a/src/styles/variables.ts
+++ b/src/styles/variables.ts
@@ -179,7 +179,7 @@ export default {
eReceiptThumbnailCenterReceiptBreakpoint: 200,
eReceiptIconHeight: 100,
eReceiptIconWidth: 72,
- eReceiptEmptyIconWidth: 76,
+ eReceiptEmptyIconWidth: 64,
eReceiptMCCHeightWidth: 40,
eReceiptIconHeightSmall: 65,
eReceiptIconWidthSmall: 46,