From 6aed543c0ed730258b79dbc9b7af5bddcd402616 Mon Sep 17 00:00:00 2001 From: borys3kk Date: Mon, 30 Jun 2025 14:23:35 +0200 Subject: [PATCH 01/21] add custom component for displaying enlraged receipt image --- .../DataCells/ReceiptCell.tsx | 10 ++- .../ReceiptPreview/index.tsx | 75 +++++++++++++++++++ 2 files changed, 84 insertions(+), 1 deletion(-) create mode 100644 src/components/TransactionItemRow/ReceiptPreview/index.tsx diff --git a/src/components/TransactionItemRow/DataCells/ReceiptCell.tsx b/src/components/TransactionItemRow/DataCells/ReceiptCell.tsx index 91840db16dc1..b7ca9d17313f 100644 --- a/src/components/TransactionItemRow/DataCells/ReceiptCell.tsx +++ b/src/components/TransactionItemRow/DataCells/ReceiptCell.tsx @@ -3,6 +3,8 @@ import React from 'react'; import {View} from 'react-native'; import {Receipt} from '@components/Icon/Expensicons'; import ReceiptImage from '@components/ReceiptImage'; +import ReceiptPreview from '@components/TransactionItemRow/ReceiptPreview'; +import useHover from '@hooks/useHover'; import useStyleUtils from '@hooks/useStyleUtils'; import useTheme from '@hooks/useTheme'; import useThemeStyles from '@hooks/useThemeStyles'; @@ -18,7 +20,7 @@ function ReceiptCell({transactionItem, isSelected}: {transactionItem: Transactio const styles = useThemeStyles(); const StyleUtils = useStyleUtils(); const backgroundStyles = isSelected ? StyleUtils.getBackgroundColorStyle(theme.buttonHoveredBG) : StyleUtils.getBackgroundColorStyle(theme.border); - + const {hovered, bind} = useHover(); let source = transactionItem?.receipt?.source ?? ''; if (source && typeof source === 'string') { @@ -36,6 +38,8 @@ function ReceiptCell({transactionItem, isSelected}: {transactionItem: Transactio styles.overflowHidden, backgroundStyles, ]} + onMouseEnter={bind.onMouseEnter} + onMouseLeave={bind.onMouseLeave} > + ); } diff --git a/src/components/TransactionItemRow/ReceiptPreview/index.tsx b/src/components/TransactionItemRow/ReceiptPreview/index.tsx new file mode 100644 index 000000000000..2c5d11d71dbb --- /dev/null +++ b/src/components/TransactionItemRow/ReceiptPreview/index.tsx @@ -0,0 +1,75 @@ +import React, {useEffect, useRef, useState} from 'react'; +import ReactDOM from 'react-dom'; +import Animated, {FadeIn, FadeOut, useAnimatedStyle, useSharedValue, withSequence, withTiming} from 'react-native-reanimated'; +import Image from '@components/Image'; + +function ReceiptPreview({source, hovered}: {source: string; hovered: boolean}) { + const [shouldShow, setShouldShow] = useState(false); + const debounceTimeout = useRef(null); + const body = document.querySelector('body'); + + const translateX = useSharedValue(0); + + useEffect(() => { + if (!shouldShow) { + return; + } + translateX.set(withSequence(withTiming(-20, {duration: 100}), withTiming(20, {duration: 100}), withTiming(0, {duration: 100}))); + }, [shouldShow, translateX]); + + const animatedStyles = useAnimatedStyle(() => ({ + transform: [{translateX: translateX.get()}], + })); + + useEffect(() => { + if (hovered) { + debounceTimeout.current = setTimeout(() => { + setShouldShow(true); + }, 150); + } else { + if (debounceTimeout.current) { + clearTimeout(debounceTimeout.current); + debounceTimeout.current = null; + } + setShouldShow(false); + } + + return () => { + if (!debounceTimeout.current) { + return; + } + clearTimeout(debounceTimeout.current); + debounceTimeout.current = null; + }; + }, [hovered]); + + if (!body || !shouldShow || !source) { + return null; + } + + return ReactDOM.createPortal( + + + + + , + body, + ); +} + +export default ReceiptPreview; From 59341be0f67802197d9809a11edcc0356a9659e4 Mon Sep 17 00:00:00 2001 From: borys3kk Date: Tue, 1 Jul 2025 11:47:58 +0200 Subject: [PATCH 02/21] create styles for ReceiptPreview --- .../ReceiptPreview/index.tsx | 33 ++++--------------- src/styles/index.ts | 20 +++++++++++ 2 files changed, 27 insertions(+), 26 deletions(-) diff --git a/src/components/TransactionItemRow/ReceiptPreview/index.tsx b/src/components/TransactionItemRow/ReceiptPreview/index.tsx index 2c5d11d71dbb..ed3e8378984c 100644 --- a/src/components/TransactionItemRow/ReceiptPreview/index.tsx +++ b/src/components/TransactionItemRow/ReceiptPreview/index.tsx @@ -1,31 +1,20 @@ import React, {useEffect, useRef, useState} from 'react'; import ReactDOM from 'react-dom'; -import Animated, {FadeIn, FadeOut, useAnimatedStyle, useSharedValue, withSequence, withTiming} from 'react-native-reanimated'; +import Animated, {FadeIn, FadeOut} from 'react-native-reanimated'; import Image from '@components/Image'; +import useThemeStyles from '@hooks/useThemeStyles'; function ReceiptPreview({source, hovered}: {source: string; hovered: boolean}) { const [shouldShow, setShouldShow] = useState(false); const debounceTimeout = useRef(null); const body = document.querySelector('body'); - - const translateX = useSharedValue(0); - - useEffect(() => { - if (!shouldShow) { - return; - } - translateX.set(withSequence(withTiming(-20, {duration: 100}), withTiming(20, {duration: 100}), withTiming(0, {duration: 100}))); - }, [shouldShow, translateX]); - - const animatedStyles = useAnimatedStyle(() => ({ - transform: [{translateX: translateX.get()}], - })); + const styles = useThemeStyles(); useEffect(() => { if (hovered) { debounceTimeout.current = setTimeout(() => { setShouldShow(true); - }, 150); + }, 333); } else { if (debounceTimeout.current) { clearTimeout(debounceTimeout.current); @@ -51,20 +40,12 @@ function ReceiptPreview({source, hovered}: {source: string; hovered: boolean}) { - + , diff --git a/src/styles/index.ts b/src/styles/index.ts index 26989f245392..276d60d3bc77 100644 --- a/src/styles/index.ts +++ b/src/styles/index.ts @@ -5923,6 +5923,26 @@ const styles = (theme: ThemeColors) => aspectRatio: 1.7, }, + receiptPreview: { + position: 'absolute', + left: 60, + top: 100, + width: 400, + height: 600, + borderRadius: 24, + overflow: 'hidden', + }, + + receiptPreviewImageWrapper: { + width: '100%', + height: '100%', + }, + + receiptPreviewImage: { + width: '100%', + height: '100%', + }, + getTestToolsNavigatorOuterView: (shouldUseNarrowLayout: boolean) => ({ flex: 1, justifyContent: shouldUseNarrowLayout ? 'flex-end' : 'center', From 5b3e4a666da532c0830b315c90b874dcfcdadc85 Mon Sep 17 00:00:00 2001 From: borys3kk Date: Tue, 1 Jul 2025 19:03:14 +0200 Subject: [PATCH 03/21] fix review comments (remove unnecesary styles, define constants for timing, introduce native implementation) --- .../ReceiptPreview/index.native.tsx | 6 +++++ .../ReceiptPreview/index.tsx | 23 +++++++++---------- src/styles/index.ts | 17 +++----------- 3 files changed, 20 insertions(+), 26 deletions(-) create mode 100644 src/components/TransactionItemRow/ReceiptPreview/index.native.tsx diff --git a/src/components/TransactionItemRow/ReceiptPreview/index.native.tsx b/src/components/TransactionItemRow/ReceiptPreview/index.native.tsx new file mode 100644 index 000000000000..0f8e5e8bcafc --- /dev/null +++ b/src/components/TransactionItemRow/ReceiptPreview/index.native.tsx @@ -0,0 +1,6 @@ +// eslint-disable-next-line @typescript-eslint/no-unused-vars -- to keep the same function definition between mobile and web +function ReceiptPreview({source, hovered}: {source: string; hovered: boolean}) { + return null; +} + +export default ReceiptPreview; diff --git a/src/components/TransactionItemRow/ReceiptPreview/index.tsx b/src/components/TransactionItemRow/ReceiptPreview/index.tsx index ed3e8378984c..ae873575252b 100644 --- a/src/components/TransactionItemRow/ReceiptPreview/index.tsx +++ b/src/components/TransactionItemRow/ReceiptPreview/index.tsx @@ -4,22 +4,21 @@ import Animated, {FadeIn, FadeOut} from 'react-native-reanimated'; import Image from '@components/Image'; import useThemeStyles from '@hooks/useThemeStyles'; +const showPreviewDelay = 333; +const animationDuration = 200; + function ReceiptPreview({source, hovered}: {source: string; hovered: boolean}) { const [shouldShow, setShouldShow] = useState(false); const debounceTimeout = useRef(null); - const body = document.querySelector('body'); + const styles = useThemeStyles(); useEffect(() => { if (hovered) { debounceTimeout.current = setTimeout(() => { setShouldShow(true); - }, 333); + }, showPreviewDelay); } else { - if (debounceTimeout.current) { - clearTimeout(debounceTimeout.current); - debounceTimeout.current = null; - } setShouldShow(false); } @@ -32,24 +31,24 @@ function ReceiptPreview({source, hovered}: {source: string; hovered: boolean}) { }; }, [hovered]); - if (!body || !shouldShow || !source) { + if (!shouldShow || !source) { return null; } return ReactDOM.createPortal( - + , - body, + document.body, ); } diff --git a/src/styles/index.ts b/src/styles/index.ts index 82b28f9a1148..6b9e8e314cd7 100644 --- a/src/styles/index.ts +++ b/src/styles/index.ts @@ -5929,29 +5929,18 @@ const styles = (theme: ThemeColors) => aspectRatio: 1.7, }, - receiptPreview: { position: 'absolute', - left: 60, + left: 70, top: 100, - width: 400, + width: 360, height: 600, - borderRadius: 24, + borderRadius: variables.componentBorderRadiusRounded, overflow: 'hidden', }, - receiptPreviewImageWrapper: { - width: '100%', - height: '100%', - }, - - receiptPreviewImage: { - width: '100%', - height: '100%', - topBarWrapper: { zIndex: 15, - }, getTestToolsNavigatorOuterView: (shouldUseNarrowLayout: boolean) => ({ From df9a008c8c56c1cd5aff03f24df92fc3321fe463 Mon Sep 17 00:00:00 2001 From: borys3kk Date: Mon, 7 Jul 2025 15:57:26 +0200 Subject: [PATCH 04/21] update styles and display of image in preview --- .../ReceiptPreview/index.tsx | 43 +++++++++++++++---- src/styles/index.ts | 13 +++--- 2 files changed, 42 insertions(+), 14 deletions(-) diff --git a/src/components/TransactionItemRow/ReceiptPreview/index.tsx b/src/components/TransactionItemRow/ReceiptPreview/index.tsx index ae873575252b..5481304a9c46 100644 --- a/src/components/TransactionItemRow/ReceiptPreview/index.tsx +++ b/src/components/TransactionItemRow/ReceiptPreview/index.tsx @@ -1,18 +1,42 @@ -import React, {useEffect, useRef, useState} from 'react'; +import React, {useCallback, useEffect, useRef, useState} from 'react'; import ReactDOM from 'react-dom'; +import {View} from 'react-native'; import Animated, {FadeIn, FadeOut} from 'react-native-reanimated'; -import Image from '@components/Image'; +import BaseImage from '@components/Image/BaseImage'; +import type {ImageOnLoadEvent} from '@components/Image/types'; +import ReceiptAudit from '@components/ReceiptAudit'; import useThemeStyles from '@hooks/useThemeStyles'; -const showPreviewDelay = 333; +const showPreviewDelay = 270; const animationDuration = 200; function ReceiptPreview({source, hovered}: {source: string; hovered: boolean}) { const [shouldShow, setShouldShow] = useState(false); const debounceTimeout = useRef(null); - const styles = useThemeStyles(); + const [aspectRatio, setAspectRatio] = useState(undefined); + + const updateAspectRatio = useCallback( + (width: number, height: number) => { + if (!source) { + return; + } + + setAspectRatio(height ? width / height : 'auto'); + }, + [source], + ); + + const handleLoad = useCallback( + (event: ImageOnLoadEvent) => { + const {width, height} = event.nativeEvent; + + updateAspectRatio(width, height); + }, + [updateAspectRatio], + ); + useEffect(() => { if (hovered) { debounceTimeout.current = setTimeout(() => { @@ -39,14 +63,15 @@ function ReceiptPreview({source, hovered}: {source: string; hovered: boolean}) { - - + - + , document.body, ); diff --git a/src/styles/index.ts b/src/styles/index.ts index 6b9e8e314cd7..88e2222d6011 100644 --- a/src/styles/index.ts +++ b/src/styles/index.ts @@ -5931,12 +5931,15 @@ const styles = (theme: ThemeColors) => receiptPreview: { position: 'absolute', - left: 70, - top: 100, - width: 360, - height: 600, - borderRadius: variables.componentBorderRadiusRounded, + left: 60, + top: 60, + width: 380, + maxHeight: 620, + borderRadius: variables.componentBorderRadiusLarge, + borderWidth: 1, + borderColor: theme.text, overflow: 'hidden', + boxShadow: theme.shadow, }, topBarWrapper: { From 358eb3b1eda38fdefb44c75b6f10b88e9c195f62 Mon Sep 17 00:00:00 2001 From: borys3kk Date: Mon, 7 Jul 2025 16:11:00 +0200 Subject: [PATCH 05/21] readd ReceiptAudit --- src/components/TransactionItemRow/ReceiptPreview/index.tsx | 6 ++++++ 1 file changed, 6 insertions(+) diff --git a/src/components/TransactionItemRow/ReceiptPreview/index.tsx b/src/components/TransactionItemRow/ReceiptPreview/index.tsx index 5481304a9c46..56ebda648348 100644 --- a/src/components/TransactionItemRow/ReceiptPreview/index.tsx +++ b/src/components/TransactionItemRow/ReceiptPreview/index.tsx @@ -72,6 +72,12 @@ function ReceiptPreview({source, hovered}: {source: string; hovered: boolean}) { onLoad={handleLoad} /> + + + , document.body, ); From 6eee0e753c6fe140724cec7599b533fbdd744648 Mon Sep 17 00:00:00 2001 From: borys3kk Date: Tue, 8 Jul 2025 14:38:42 +0200 Subject: [PATCH 06/21] change border, add ereceipts, add fade, add verified/issues found --- .../DataCells/ReceiptCell.tsx | 2 + .../ReceiptHoverAudit/index.tsx | 51 +++++++++++++++++++ .../ReceiptPreview/index.native.tsx | 2 +- .../ReceiptPreview/index.tsx | 28 +++++----- src/styles/index.ts | 10 +++- 5 files changed, 79 insertions(+), 14 deletions(-) create mode 100644 src/components/TransactionItemRow/ReceiptHoverAudit/index.tsx diff --git a/src/components/TransactionItemRow/DataCells/ReceiptCell.tsx b/src/components/TransactionItemRow/DataCells/ReceiptCell.tsx index b7ca9d17313f..19d3199c2f71 100644 --- a/src/components/TransactionItemRow/DataCells/ReceiptCell.tsx +++ b/src/components/TransactionItemRow/DataCells/ReceiptCell.tsx @@ -59,6 +59,8 @@ function ReceiptCell({transactionItem, isSelected}: {transactionItem: Transactio ); diff --git a/src/components/TransactionItemRow/ReceiptHoverAudit/index.tsx b/src/components/TransactionItemRow/ReceiptHoverAudit/index.tsx new file mode 100644 index 000000000000..4cf057f40242 --- /dev/null +++ b/src/components/TransactionItemRow/ReceiptHoverAudit/index.tsx @@ -0,0 +1,51 @@ +import React from 'react'; +import {View} from 'react-native'; +import Icon from '@components/Icon'; +import * as Expensicons from '@components/Icon/Expensicons'; +import Text from '@components/Text'; +import useLocalize from '@hooks/useLocalize'; +import useTheme from '@hooks/useTheme'; +import useThemeStyles from '@hooks/useThemeStyles'; + +type ReceiptHoverAuditProps = { + /** List of audit notes */ + notes: string[]; + + /** Whether to show audit result or not (e.g.`Verified`, `Issue Found`) */ + shouldShowAuditResult: boolean; +}; + +function ReceiptHoverAudit({notes, shouldShowAuditResult}: ReceiptHoverAuditProps) { + const styles = useThemeStyles(); + const theme = useTheme(); + const {translate} = useLocalize(); + + let auditText = ''; + if (notes.length > 0 && shouldShowAuditResult) { + auditText = translate('iou.receiptIssuesFound', {count: notes.length}); + } else if (!notes.length && shouldShowAuditResult) { + auditText = translate('common.verified'); + } + + return ( + + + {translate('common.receipt')} + {!!auditText && ( + <> + {` • ${auditText}`} + + + )} + + + ); +} + +export default ReceiptHoverAudit; diff --git a/src/components/TransactionItemRow/ReceiptPreview/index.native.tsx b/src/components/TransactionItemRow/ReceiptPreview/index.native.tsx index 0f8e5e8bcafc..48dd750b6023 100644 --- a/src/components/TransactionItemRow/ReceiptPreview/index.native.tsx +++ b/src/components/TransactionItemRow/ReceiptPreview/index.native.tsx @@ -1,5 +1,5 @@ // eslint-disable-next-line @typescript-eslint/no-unused-vars -- to keep the same function definition between mobile and web -function ReceiptPreview({source, hovered}: {source: string; hovered: boolean}) { +function ReceiptPreview({source, hovered, isEReceipt = false, transactionID = ''}: {source: string; hovered: boolean; isEReceipt?: boolean; transactionID?: string}) { return null; } diff --git a/src/components/TransactionItemRow/ReceiptPreview/index.tsx b/src/components/TransactionItemRow/ReceiptPreview/index.tsx index 56ebda648348..981da3e3ea31 100644 --- a/src/components/TransactionItemRow/ReceiptPreview/index.tsx +++ b/src/components/TransactionItemRow/ReceiptPreview/index.tsx @@ -2,15 +2,16 @@ import React, {useCallback, useEffect, useRef, useState} from 'react'; import ReactDOM from 'react-dom'; import {View} from 'react-native'; import Animated, {FadeIn, FadeOut} from 'react-native-reanimated'; +import EReceipt from '@components/EReceipt'; import BaseImage from '@components/Image/BaseImage'; import type {ImageOnLoadEvent} from '@components/Image/types'; -import ReceiptAudit from '@components/ReceiptAudit'; +import ReceiptHoverAudit from '@components/TransactionItemRow/ReceiptHoverAudit'; import useThemeStyles from '@hooks/useThemeStyles'; const showPreviewDelay = 270; const animationDuration = 200; -function ReceiptPreview({source, hovered}: {source: string; hovered: boolean}) { +function ReceiptPreview({source, hovered, isEReceipt = false, transactionID = ''}: {source: string; hovered: boolean; isEReceipt?: boolean; transactionID?: string}) { const [shouldShow, setShouldShow] = useState(false); const debounceTimeout = useRef(null); const styles = useThemeStyles(); @@ -55,7 +56,7 @@ function ReceiptPreview({source, hovered}: {source: string; hovered: boolean}) { }; }, [hovered]); - if (!shouldShow || !source) { + if (!shouldShow || (!source && !isEReceipt)) { return null; } @@ -65,15 +66,18 @@ function ReceiptPreview({source, hovered}: {source: string; hovered: boolean}) { exiting={FadeOut.duration(animationDuration)} style={[styles.receiptPreview, styles.dFlex, styles.flexColumn, styles.alignItemsCenter, styles.justifyContentCenter]} > - - - - - + + + )} + {!!isEReceipt && } + + diff --git a/src/styles/index.ts b/src/styles/index.ts index 88e2222d6011..880141596ee1 100644 --- a/src/styles/index.ts +++ b/src/styles/index.ts @@ -5937,11 +5937,19 @@ const styles = (theme: ThemeColors) => maxHeight: 620, borderRadius: variables.componentBorderRadiusLarge, borderWidth: 1, - borderColor: theme.text, + borderColor: theme.border, overflow: 'hidden', boxShadow: theme.shadow, }, + receiptPreviewAuditWrapper: { + position: 'absolute', + top: '0px', + width: '100%', + height: '48px', + background: 'linear-gradient(to bottom, rgba(0, 0, 0, 0), rgba(0, 0, 0, 0.55))', + }, + topBarWrapper: { zIndex: 15, }, From f5fee55cc1544e9e3772135cbf6c648045e3bf69 Mon Sep 17 00:00:00 2001 From: Mateusz Titz Date: Tue, 8 Jul 2025 15:37:19 +0200 Subject: [PATCH 07/21] Update gradient for ReceiptHover component --- .../ReceiptHoverAudit/index.tsx | 30 +++++++++---------- src/styles/index.ts | 3 +- 2 files changed, 15 insertions(+), 18 deletions(-) diff --git a/src/components/TransactionItemRow/ReceiptHoverAudit/index.tsx b/src/components/TransactionItemRow/ReceiptHoverAudit/index.tsx index 4cf057f40242..63b4a1fc15c3 100644 --- a/src/components/TransactionItemRow/ReceiptHoverAudit/index.tsx +++ b/src/components/TransactionItemRow/ReceiptHoverAudit/index.tsx @@ -28,22 +28,20 @@ function ReceiptHoverAudit({notes, shouldShowAuditResult}: ReceiptHoverAuditProp } return ( - - - {translate('common.receipt')} - {!!auditText && ( - <> - {` • ${auditText}`} - - - )} - + + {translate('common.receipt')} + {!!auditText && ( + <> + {` • ${auditText}`} + + + )} ); } diff --git a/src/styles/index.ts b/src/styles/index.ts index 880141596ee1..1f0e52d6cdda 100644 --- a/src/styles/index.ts +++ b/src/styles/index.ts @@ -5946,8 +5946,7 @@ const styles = (theme: ThemeColors) => position: 'absolute', top: '0px', width: '100%', - height: '48px', - background: 'linear-gradient(to bottom, rgba(0, 0, 0, 0), rgba(0, 0, 0, 0.55))', + background: 'linear-gradient(0deg, rgba(0, 0, 0, 0), rgba(0, 0, 0, 0.5)', }, topBarWrapper: { From 3fa51319f61d1b944593761238c02c7ef5053f09 Mon Sep 17 00:00:00 2001 From: Mateusz Titz Date: Tue, 8 Jul 2025 15:55:22 +0200 Subject: [PATCH 08/21] Update styling for ReceiptHover component --- src/components/TransactionItemRow/ReceiptHoverAudit/index.tsx | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/components/TransactionItemRow/ReceiptHoverAudit/index.tsx b/src/components/TransactionItemRow/ReceiptHoverAudit/index.tsx index 63b4a1fc15c3..58018109d847 100644 --- a/src/components/TransactionItemRow/ReceiptHoverAudit/index.tsx +++ b/src/components/TransactionItemRow/ReceiptHoverAudit/index.tsx @@ -28,7 +28,7 @@ function ReceiptHoverAudit({notes, shouldShowAuditResult}: ReceiptHoverAuditProp } return ( - + {translate('common.receipt')} {!!auditText && ( <> From 875bce4ff640286c7a70a91a4eb96a8b7756492d Mon Sep 17 00:00:00 2001 From: borys3kk Date: Wed, 9 Jul 2025 09:52:35 +0200 Subject: [PATCH 09/21] remove receiphoveraudit, update styles --- .../ReceiptHoverAudit/index.tsx | 49 ------------------- .../ReceiptPreview/index.tsx | 12 ++--- src/styles/index.ts | 8 +-- 3 files changed, 6 insertions(+), 63 deletions(-) delete mode 100644 src/components/TransactionItemRow/ReceiptHoverAudit/index.tsx diff --git a/src/components/TransactionItemRow/ReceiptHoverAudit/index.tsx b/src/components/TransactionItemRow/ReceiptHoverAudit/index.tsx deleted file mode 100644 index 58018109d847..000000000000 --- a/src/components/TransactionItemRow/ReceiptHoverAudit/index.tsx +++ /dev/null @@ -1,49 +0,0 @@ -import React from 'react'; -import {View} from 'react-native'; -import Icon from '@components/Icon'; -import * as Expensicons from '@components/Icon/Expensicons'; -import Text from '@components/Text'; -import useLocalize from '@hooks/useLocalize'; -import useTheme from '@hooks/useTheme'; -import useThemeStyles from '@hooks/useThemeStyles'; - -type ReceiptHoverAuditProps = { - /** List of audit notes */ - notes: string[]; - - /** Whether to show audit result or not (e.g.`Verified`, `Issue Found`) */ - shouldShowAuditResult: boolean; -}; - -function ReceiptHoverAudit({notes, shouldShowAuditResult}: ReceiptHoverAuditProps) { - const styles = useThemeStyles(); - const theme = useTheme(); - const {translate} = useLocalize(); - - let auditText = ''; - if (notes.length > 0 && shouldShowAuditResult) { - auditText = translate('iou.receiptIssuesFound', {count: notes.length}); - } else if (!notes.length && shouldShowAuditResult) { - auditText = translate('common.verified'); - } - - return ( - - {translate('common.receipt')} - {!!auditText && ( - <> - {` • ${auditText}`} - - - )} - - ); -} - -export default ReceiptHoverAudit; diff --git a/src/components/TransactionItemRow/ReceiptPreview/index.tsx b/src/components/TransactionItemRow/ReceiptPreview/index.tsx index 981da3e3ea31..ccaefc9490d2 100644 --- a/src/components/TransactionItemRow/ReceiptPreview/index.tsx +++ b/src/components/TransactionItemRow/ReceiptPreview/index.tsx @@ -5,7 +5,6 @@ import Animated, {FadeIn, FadeOut} from 'react-native-reanimated'; import EReceipt from '@components/EReceipt'; import BaseImage from '@components/Image/BaseImage'; import type {ImageOnLoadEvent} from '@components/Image/types'; -import ReceiptHoverAudit from '@components/TransactionItemRow/ReceiptHoverAudit'; import useThemeStyles from '@hooks/useThemeStyles'; const showPreviewDelay = 270; @@ -75,13 +74,12 @@ function ReceiptPreview({source, hovered, isEReceipt = false, transactionID = '' /> )} - {!!isEReceipt && } - - - + )} , document.body, ); diff --git a/src/styles/index.ts b/src/styles/index.ts index 1f0e52d6cdda..25cb6fec8d90 100644 --- a/src/styles/index.ts +++ b/src/styles/index.ts @@ -5940,13 +5940,7 @@ const styles = (theme: ThemeColors) => borderColor: theme.border, overflow: 'hidden', boxShadow: theme.shadow, - }, - - receiptPreviewAuditWrapper: { - position: 'absolute', - top: '0px', - width: '100%', - background: 'linear-gradient(0deg, rgba(0, 0, 0, 0), rgba(0, 0, 0, 0.5)', + backgroundColor: theme.appBG, }, topBarWrapper: { From 2b718226e52cae92d395bc24e9913f783f5cc859 Mon Sep 17 00:00:00 2001 From: borys3kk Date: Wed, 9 Jul 2025 14:20:18 +0200 Subject: [PATCH 10/21] fix ereceipt vertical lines --- .../ReceiptPreview/index.tsx | 26 +++++++++++++++---- 1 file changed, 21 insertions(+), 5 deletions(-) diff --git a/src/components/TransactionItemRow/ReceiptPreview/index.tsx b/src/components/TransactionItemRow/ReceiptPreview/index.tsx index ccaefc9490d2..6f96e05c41af 100644 --- a/src/components/TransactionItemRow/ReceiptPreview/index.tsx +++ b/src/components/TransactionItemRow/ReceiptPreview/index.tsx @@ -1,11 +1,13 @@ import React, {useCallback, useEffect, useRef, useState} from 'react'; import ReactDOM from 'react-dom'; +import type {LayoutChangeEvent} from 'react-native'; import {View} from 'react-native'; import Animated, {FadeIn, FadeOut} from 'react-native-reanimated'; import EReceipt from '@components/EReceipt'; import BaseImage from '@components/Image/BaseImage'; import type {ImageOnLoadEvent} from '@components/Image/types'; import useThemeStyles from '@hooks/useThemeStyles'; +import variables from '@styles/variables'; const showPreviewDelay = 270; const animationDuration = 200; @@ -14,7 +16,7 @@ function ReceiptPreview({source, hovered, isEReceipt = false, transactionID = '' const [shouldShow, setShouldShow] = useState(false); const debounceTimeout = useRef(null); const styles = useThemeStyles(); - + const [scaleFactor, setScaleFactor] = useState(0); const [aspectRatio, setAspectRatio] = useState(undefined); const updateAspectRatio = useCallback( @@ -37,6 +39,11 @@ function ReceiptPreview({source, hovered, isEReceipt = false, transactionID = '' [updateAspectRatio], ); + const onLayout = (e: LayoutChangeEvent) => { + const {width} = e.nativeEvent.layout; + setScaleFactor(width / variables.eReceiptBGHWidth); + }; + useEffect(() => { if (hovered) { debounceTimeout.current = setTimeout(() => { @@ -75,10 +82,19 @@ function ReceiptPreview({source, hovered, isEReceipt = false, transactionID = '' )} {!!isEReceipt && ( - + + + )} , document.body, From e03e3340de20f3807b1e9a48ac0e52e31b6cc736 Mon Sep 17 00:00:00 2001 From: borys3kk Date: Thu, 10 Jul 2025 10:55:32 +0200 Subject: [PATCH 11/21] change maxHeight, remove hover in narrow layout --- src/components/TransactionItemRow/ReceiptPreview/index.tsx | 5 +++-- src/styles/index.ts | 2 +- 2 files changed, 4 insertions(+), 3 deletions(-) diff --git a/src/components/TransactionItemRow/ReceiptPreview/index.tsx b/src/components/TransactionItemRow/ReceiptPreview/index.tsx index 6f96e05c41af..c85a0b20ab88 100644 --- a/src/components/TransactionItemRow/ReceiptPreview/index.tsx +++ b/src/components/TransactionItemRow/ReceiptPreview/index.tsx @@ -6,6 +6,7 @@ import Animated, {FadeIn, FadeOut} from 'react-native-reanimated'; import EReceipt from '@components/EReceipt'; import BaseImage from '@components/Image/BaseImage'; import type {ImageOnLoadEvent} from '@components/Image/types'; +import useResponsiveLayout from '@hooks/useResponsiveLayout'; import useThemeStyles from '@hooks/useThemeStyles'; import variables from '@styles/variables'; @@ -18,6 +19,7 @@ function ReceiptPreview({source, hovered, isEReceipt = false, transactionID = '' const styles = useThemeStyles(); const [scaleFactor, setScaleFactor] = useState(0); const [aspectRatio, setAspectRatio] = useState(undefined); + const {shouldUseNarrowLayout} = useResponsiveLayout(); const updateAspectRatio = useCallback( (width: number, height: number) => { @@ -62,7 +64,7 @@ function ReceiptPreview({source, hovered, isEReceipt = false, transactionID = '' }; }, [hovered]); - if (!shouldShow || (!source && !isEReceipt)) { + if (shouldUseNarrowLayout || !shouldShow || (!source && !isEReceipt)) { return null; } @@ -86,7 +88,6 @@ function ReceiptPreview({source, hovered, isEReceipt = false, transactionID = '' onLayout={onLayout} style={[ styles.w100, - styles.dFlex, styles.flexColumn, styles.alignItemsCenter, styles.justifyContentCenter, diff --git a/src/styles/index.ts b/src/styles/index.ts index 25cb6fec8d90..9da8d85f64b7 100644 --- a/src/styles/index.ts +++ b/src/styles/index.ts @@ -5934,7 +5934,7 @@ const styles = (theme: ThemeColors) => left: 60, top: 60, width: 380, - maxHeight: 620, + maxHeight: 'calc(100vh - 120px)', borderRadius: variables.componentBorderRadiusLarge, borderWidth: 1, borderColor: theme.border, From 234703882c9c566d407ee96afb78ac4ed0148335 Mon Sep 17 00:00:00 2001 From: borys3kk Date: Thu, 10 Jul 2025 16:44:13 +0200 Subject: [PATCH 12/21] remove props from native.ts, update variables, extract props for ReceiptPreview --- .../DataCells/ReceiptCell.tsx | 5 ++- .../ReceiptPreview/index.native.tsx | 3 +- .../ReceiptPreview/index.tsx | 40 +++++++++---------- 3 files changed, 23 insertions(+), 25 deletions(-) diff --git a/src/components/TransactionItemRow/DataCells/ReceiptCell.tsx b/src/components/TransactionItemRow/DataCells/ReceiptCell.tsx index 19d3199c2f71..2f7409c61fa8 100644 --- a/src/components/TransactionItemRow/DataCells/ReceiptCell.tsx +++ b/src/components/TransactionItemRow/DataCells/ReceiptCell.tsx @@ -21,6 +21,7 @@ function ReceiptCell({transactionItem, isSelected}: {transactionItem: Transactio const StyleUtils = useStyleUtils(); const backgroundStyles = isSelected ? StyleUtils.getBackgroundColorStyle(theme.buttonHoveredBG) : StyleUtils.getBackgroundColorStyle(theme.border); const {hovered, bind} = useHover(); + const isEReceipt = transactionItem.hasEReceipt && !hasReceiptSource(transactionItem); let source = transactionItem?.receipt?.source ?? ''; if (source && typeof source === 'string') { @@ -43,7 +44,7 @@ function ReceiptCell({transactionItem, isSelected}: {transactionItem: Transactio > diff --git a/src/components/TransactionItemRow/ReceiptPreview/index.native.tsx b/src/components/TransactionItemRow/ReceiptPreview/index.native.tsx index 48dd750b6023..eb62a4c48e37 100644 --- a/src/components/TransactionItemRow/ReceiptPreview/index.native.tsx +++ b/src/components/TransactionItemRow/ReceiptPreview/index.native.tsx @@ -1,5 +1,4 @@ -// eslint-disable-next-line @typescript-eslint/no-unused-vars -- to keep the same function definition between mobile and web -function ReceiptPreview({source, hovered, isEReceipt = false, transactionID = ''}: {source: string; hovered: boolean; isEReceipt?: boolean; transactionID?: string}) { +function ReceiptPreview() { return null; } diff --git a/src/components/TransactionItemRow/ReceiptPreview/index.tsx b/src/components/TransactionItemRow/ReceiptPreview/index.tsx index c85a0b20ab88..cc5a9216bea7 100644 --- a/src/components/TransactionItemRow/ReceiptPreview/index.tsx +++ b/src/components/TransactionItemRow/ReceiptPreview/index.tsx @@ -12,13 +12,16 @@ import variables from '@styles/variables'; const showPreviewDelay = 270; const animationDuration = 200; +const eReceiptAspectRatio = variables.eReceiptBGHWidth / variables.eReceiptBGHeight; -function ReceiptPreview({source, hovered, isEReceipt = false, transactionID = ''}: {source: string; hovered: boolean; isEReceipt?: boolean; transactionID?: string}) { +type ReceiptPreviewProps = {source: string; hovered: boolean; isEReceipt: boolean; transactionID: string}; + +function ReceiptPreview({source, hovered, isEReceipt = false, transactionID = ''}: ReceiptPreviewProps) { const [shouldShow, setShouldShow] = useState(false); const debounceTimeout = useRef(null); const styles = useThemeStyles(); - const [scaleFactor, setScaleFactor] = useState(0); - const [aspectRatio, setAspectRatio] = useState(undefined); + const [eReceiptScaleFactor, setEReceiptScaleFactor] = useState(0); + const [imageAspectRatio, setImageAspectRatio] = useState(undefined); const {shouldUseNarrowLayout} = useResponsiveLayout(); const updateAspectRatio = useCallback( @@ -27,7 +30,7 @@ function ReceiptPreview({source, hovered, isEReceipt = false, transactionID = '' return; } - setAspectRatio(height ? width / height : 'auto'); + setImageAspectRatio(height ? width / height : 'auto'); }, [source], ); @@ -43,7 +46,7 @@ function ReceiptPreview({source, hovered, isEReceipt = false, transactionID = '' const onLayout = (e: LayoutChangeEvent) => { const {width} = e.nativeEvent.layout; - setScaleFactor(width / variables.eReceiptBGHWidth); + setEReceiptScaleFactor(width / variables.eReceiptBGHWidth); }; useEffect(() => { @@ -68,35 +71,30 @@ function ReceiptPreview({source, hovered, isEReceipt = false, transactionID = '' return null; } + const shouldShowEReceipt = !source && isEReceipt; + return ReactDOM.createPortal( - {!!source && ( + {shouldShowEReceipt ? ( + + + + ) : ( )} - {!!isEReceipt && ( - - - - )} , document.body, ); From ea7aaf5ff95fe6e228085875fb2ebb8b7e553ee1 Mon Sep 17 00:00:00 2001 From: Mateusz Titz Date: Fri, 11 Jul 2025 10:27:25 +0200 Subject: [PATCH 13/21] Add small style fixes To Receipt Preview --- src/components/TransactionItemRow/DataCells/ReceiptCell.tsx | 4 ++-- src/components/TransactionItemRow/ReceiptPreview/index.tsx | 2 +- 2 files changed, 3 insertions(+), 3 deletions(-) diff --git a/src/components/TransactionItemRow/DataCells/ReceiptCell.tsx b/src/components/TransactionItemRow/DataCells/ReceiptCell.tsx index 2f7409c61fa8..fad7b7afe000 100644 --- a/src/components/TransactionItemRow/DataCells/ReceiptCell.tsx +++ b/src/components/TransactionItemRow/DataCells/ReceiptCell.tsx @@ -24,7 +24,7 @@ function ReceiptCell({transactionItem, isSelected}: {transactionItem: Transactio const isEReceipt = transactionItem.hasEReceipt && !hasReceiptSource(transactionItem); let source = transactionItem?.receipt?.source ?? ''; - if (source && typeof source === 'string') { + if (source) { const filename = getFileName(source); const receiptURIs = getThumbnailAndImageURIs(transactionItem, null, filename); const isReceiptPDF = Str.isPDF(filename); @@ -60,7 +60,7 @@ function ReceiptCell({transactionItem, isSelected}: {transactionItem: Transactio diff --git a/src/components/TransactionItemRow/ReceiptPreview/index.tsx b/src/components/TransactionItemRow/ReceiptPreview/index.tsx index cc5a9216bea7..5d38516fbb3c 100644 --- a/src/components/TransactionItemRow/ReceiptPreview/index.tsx +++ b/src/components/TransactionItemRow/ReceiptPreview/index.tsx @@ -77,7 +77,7 @@ function ReceiptPreview({source, hovered, isEReceipt = false, transactionID = '' {shouldShowEReceipt ? ( Date: Tue, 15 Jul 2025 12:01:59 +0200 Subject: [PATCH 14/21] fix distance ereceipts/refactor --- src/components/SelectionList/types.ts | 3 ++ .../DataCells/ReceiptCell.tsx | 3 +- .../ReceiptPreview/index.tsx | 29 +++++++++++-------- src/libs/SearchUIUtils.ts | 3 +- src/types/onyx/SearchResults.ts | 3 ++ 5 files changed, 25 insertions(+), 16 deletions(-) diff --git a/src/components/SelectionList/types.ts b/src/components/SelectionList/types.ts index 7b400f1b4c70..2c8bfa732bf8 100644 --- a/src/components/SelectionList/types.ts +++ b/src/components/SelectionList/types.ts @@ -261,6 +261,9 @@ type TransactionListItemType = ListItem & /** Key used internally by React */ keyForList: string; + /** The name of the file used for a receipt */ + filename?: string; + /** Attendees in the transaction */ attendees?: Attendee[]; diff --git a/src/components/TransactionItemRow/DataCells/ReceiptCell.tsx b/src/components/TransactionItemRow/DataCells/ReceiptCell.tsx index fad7b7afe000..07ffe8ce5aa7 100644 --- a/src/components/TransactionItemRow/DataCells/ReceiptCell.tsx +++ b/src/components/TransactionItemRow/DataCells/ReceiptCell.tsx @@ -23,7 +23,6 @@ function ReceiptCell({transactionItem, isSelected}: {transactionItem: Transactio const {hovered, bind} = useHover(); const isEReceipt = transactionItem.hasEReceipt && !hasReceiptSource(transactionItem); let source = transactionItem?.receipt?.source ?? ''; - if (source) { const filename = getFileName(source); const receiptURIs = getThumbnailAndImageURIs(transactionItem, null, filename); @@ -61,7 +60,7 @@ function ReceiptCell({transactionItem, isSelected}: {transactionItem: Transactio source={source} hovered={hovered} isEReceipt={!!isEReceipt} - transactionID={transactionItem.transactionID} + transactionItem={transactionItem} /> ); diff --git a/src/components/TransactionItemRow/ReceiptPreview/index.tsx b/src/components/TransactionItemRow/ReceiptPreview/index.tsx index 5d38516fbb3c..2605efca4450 100644 --- a/src/components/TransactionItemRow/ReceiptPreview/index.tsx +++ b/src/components/TransactionItemRow/ReceiptPreview/index.tsx @@ -3,20 +3,24 @@ import ReactDOM from 'react-dom'; import type {LayoutChangeEvent} from 'react-native'; import {View} from 'react-native'; import Animated, {FadeIn, FadeOut} from 'react-native-reanimated'; +import DistanceEReceipt from '@components/DistanceEReceipt'; import EReceipt from '@components/EReceipt'; import BaseImage from '@components/Image/BaseImage'; import type {ImageOnLoadEvent} from '@components/Image/types'; import useResponsiveLayout from '@hooks/useResponsiveLayout'; import useThemeStyles from '@hooks/useThemeStyles'; +import {isDistanceRequest} from '@libs/TransactionUtils'; import variables from '@styles/variables'; +import type {Transaction} from '@src/types/onyx'; const showPreviewDelay = 270; const animationDuration = 200; const eReceiptAspectRatio = variables.eReceiptBGHWidth / variables.eReceiptBGHeight; -type ReceiptPreviewProps = {source: string; hovered: boolean; isEReceipt: boolean; transactionID: string}; +type ReceiptPreviewProps = {source: string; hovered: boolean; isEReceipt: boolean; transactionItem: Transaction}; -function ReceiptPreview({source, hovered, isEReceipt = false, transactionID = ''}: ReceiptPreviewProps) { +function ReceiptPreview({source, hovered, isEReceipt = false, transactionItem}: ReceiptPreviewProps) { + const isDistanceEReceipt = isDistanceRequest(transactionItem); const [shouldShow, setShouldShow] = useState(false); const debounceTimeout = useRef(null); const styles = useThemeStyles(); @@ -67,11 +71,12 @@ function ReceiptPreview({source, hovered, isEReceipt = false, transactionID = '' }; }, [hovered]); - if (shouldUseNarrowLayout || !shouldShow || (!source && !isEReceipt)) { + if (shouldUseNarrowLayout || !shouldShow || (!source && !isEReceipt && !isDistanceEReceipt)) { return null; } - const shouldShowEReceipt = !source && isEReceipt; + const shouldShowImage = source && !(isEReceipt || isDistanceEReceipt); + const shouldShowDistanceEReceipt = isDistanceEReceipt && !isEReceipt; return ReactDOM.createPortal( - {shouldShowEReceipt ? ( - - - - ) : ( + {shouldShowImage ? ( + ) : ( + + {shouldShowDistanceEReceipt ? : } + )} , document.body, diff --git a/src/libs/SearchUIUtils.ts b/src/libs/SearchUIUtils.ts index c342f4c2b37f..154cc5cb5867 100644 --- a/src/libs/SearchUIUtils.ts +++ b/src/libs/SearchUIUtils.ts @@ -528,7 +528,6 @@ function getTransactionsSections(data: OnyxTypes.SearchResults['data'], metadata const report = data[`${ONYXKEYS.COLLECTION.REPORT}${transactionItem.reportID}`]; const policy = data[`${ONYXKEYS.COLLECTION.POLICY}${report?.policyID}`]; const shouldShowBlankTo = !report || isOpenExpenseReport(report); - const transactionViolations = getTransactionViolations(allViolations, transactionItem); // Use Map.get() for faster lookups with default values const from = personalDetailsMap.get(transactionItem.accountID.toString()) ?? emptyPersonalDetails; @@ -554,7 +553,7 @@ function getTransactionsSections(data: OnyxTypes.SearchResults['data'], metadata isAmountColumnWide: shouldShowAmountInWideColumn, isTaxAmountColumnWide: shouldShowTaxAmountInWideColumn, violations: transactionViolations, - + filename: transactionItem.filename, // Manually copying all the properties from transactionItem transactionID: transactionItem.transactionID, created: transactionItem.created, diff --git a/src/types/onyx/SearchResults.ts b/src/types/onyx/SearchResults.ts index e20e038b80ff..6f5e7c65a2c5 100644 --- a/src/types/onyx/SearchResults.ts +++ b/src/types/onyx/SearchResults.ts @@ -391,6 +391,9 @@ type SearchTransaction = { /** The ID of the report the transaction is associated with */ reportID: string; + /** The name of the file used for a receipt */ + filename?: string; + /** The report ID of the transaction thread associated with the transaction */ transactionThreadReportID: string; From fa9930e8797d103c532339840fe9f4ade27782a3 Mon Sep 17 00:00:00 2001 From: borys3kk Date: Wed, 16 Jul 2025 11:28:28 +0200 Subject: [PATCH 15/21] fix tests --- tests/unit/Search/SearchUIUtilsTest.ts | 10 ++++++++++ 1 file changed, 10 insertions(+) diff --git a/tests/unit/Search/SearchUIUtilsTest.ts b/tests/unit/Search/SearchUIUtilsTest.ts index c72d0eb1f88c..093e8f5da066 100644 --- a/tests/unit/Search/SearchUIUtilsTest.ts +++ b/tests/unit/Search/SearchUIUtilsTest.ts @@ -269,6 +269,7 @@ const searchResults: OnyxTypes.SearchResults = { modifiedMCCGroup: undefined, moneyRequestReportActionID: undefined, errors: undefined, + filename: undefined, isActionLoading: false, }, [`transactions_${transactionID2}`]: { @@ -309,6 +310,7 @@ const searchResults: OnyxTypes.SearchResults = { moneyRequestReportActionID: undefined, pendingAction: undefined, errors: undefined, + filename: undefined, isActionLoading: false, }, ...allViolations, @@ -349,6 +351,7 @@ const searchResults: OnyxTypes.SearchResults = { moneyRequestReportActionID: undefined, pendingAction: undefined, errors: undefined, + filename: undefined, isActionLoading: false, hasViolation: undefined, }, @@ -389,6 +392,7 @@ const searchResults: OnyxTypes.SearchResults = { moneyRequestReportActionID: undefined, pendingAction: undefined, errors: undefined, + filename: undefined, isActionLoading: false, hasViolation: undefined, }, @@ -502,6 +506,7 @@ const transactionsListItems = [ modifiedMCCGroup: undefined, moneyRequestReportActionID: undefined, errors: undefined, + filename: undefined, isActionLoading: false, hasViolation: false, violations: [], @@ -566,6 +571,7 @@ const transactionsListItems = [ moneyRequestReportActionID: undefined, pendingAction: undefined, errors: undefined, + filename: undefined, isActionLoading: false, hasViolation: true, violations: [ @@ -635,6 +641,7 @@ const transactionsListItems = [ moneyRequestReportActionID: undefined, pendingAction: undefined, errors: undefined, + filename: undefined, isActionLoading: false, hasViolation: undefined, violations: [], @@ -699,6 +706,7 @@ const transactionsListItems = [ moneyRequestReportActionID: undefined, pendingAction: undefined, errors: undefined, + filename: undefined, isActionLoading: false, hasViolation: undefined, violations: [], @@ -800,6 +808,7 @@ const transactionReportGroupListItems = [ modifiedMCCGroup: undefined, moneyRequestReportActionID: undefined, errors: undefined, + filename: undefined, isActionLoading: false, violations: [], }, @@ -907,6 +916,7 @@ const transactionReportGroupListItems = [ moneyRequestReportActionID: undefined, pendingAction: undefined, errors: undefined, + filename: undefined, isActionLoading: false, }, ], From 20a98b9c713e49e98f575c9f8bf607b68e35441e Mon Sep 17 00:00:00 2001 From: borys3kk Date: Mon, 21 Jul 2025 17:34:50 +0200 Subject: [PATCH 16/21] update proptypes for future use in receipt previews/ fix display of distanceEReceipt --- src/components/SelectionList/types.ts | 6 +++++ .../ReceiptPreview/index.tsx | 22 ++++++++++++++----- src/libs/SearchUIUtils.ts | 4 +++- src/types/onyx/SearchResults.ts | 6 +++++ 4 files changed, 32 insertions(+), 6 deletions(-) diff --git a/src/components/SelectionList/types.ts b/src/components/SelectionList/types.ts index 2c8bfa732bf8..79abb11f26a3 100644 --- a/src/components/SelectionList/types.ts +++ b/src/components/SelectionList/types.ts @@ -269,6 +269,12 @@ type TransactionListItemType = ListItem & /** Precomputed violations */ violations?: TransactionViolation[]; + + /** The CC for this transaction */ + cardID?: number; + + /** The display name of the purchaser card, if any */ + cardName?: string; }; type ReportActionListItemType = ListItem & diff --git a/src/components/TransactionItemRow/ReceiptPreview/index.tsx b/src/components/TransactionItemRow/ReceiptPreview/index.tsx index 2605efca4450..90781a9e881d 100644 --- a/src/components/TransactionItemRow/ReceiptPreview/index.tsx +++ b/src/components/TransactionItemRow/ReceiptPreview/index.tsx @@ -10,6 +10,7 @@ import type {ImageOnLoadEvent} from '@components/Image/types'; import useResponsiveLayout from '@hooks/useResponsiveLayout'; import useThemeStyles from '@hooks/useThemeStyles'; import {isDistanceRequest} from '@libs/TransactionUtils'; +import colors from '@styles/theme/colors'; import variables from '@styles/variables'; import type {Transaction} from '@src/types/onyx'; @@ -93,11 +94,22 @@ function ReceiptPreview({source, hovered, isEReceipt = false, transactionItem}: /> ) : ( - - {shouldShowDistanceEReceipt ? : } + + {shouldShowDistanceEReceipt ? ( + + + + ) : ( + + + + )} )} , diff --git a/src/libs/SearchUIUtils.ts b/src/libs/SearchUIUtils.ts index 154cc5cb5867..397c3adcff3b 100644 --- a/src/libs/SearchUIUtils.ts +++ b/src/libs/SearchUIUtils.ts @@ -532,7 +532,7 @@ function getTransactionsSections(data: OnyxTypes.SearchResults['data'], metadata // Use Map.get() for faster lookups with default values const from = personalDetailsMap.get(transactionItem.accountID.toString()) ?? emptyPersonalDetails; const to = transactionItem.managerID && !shouldShowBlankTo ? (personalDetailsMap.get(transactionItem.managerID.toString()) ?? emptyPersonalDetails) : emptyPersonalDetails; - + console.log('cards info', transactionItem.cardID, transactionItem.cardName); const {formattedFrom, formattedTo, formattedTotal, formattedMerchant, date} = getTransactionItemCommonFormattedProperties(transactionItem, from, to, policy); const transactionSection: TransactionListItemType = { @@ -591,6 +591,8 @@ function getTransactionsSections(data: OnyxTypes.SearchResults['data'], metadata errors: transactionItem.errors, isActionLoading: transactionItem.isActionLoading, hasViolation: transactionItem.hasViolation, + cardID: transactionItem.cardID, + cardName: transactionItem.cardName, }; transactionsSections.push(transactionSection); diff --git a/src/types/onyx/SearchResults.ts b/src/types/onyx/SearchResults.ts index 6f5e7c65a2c5..f1fdcdf074f8 100644 --- a/src/types/onyx/SearchResults.ts +++ b/src/types/onyx/SearchResults.ts @@ -420,6 +420,12 @@ type SearchTransaction = { /** The type of action that's pending */ pendingAction?: OnyxCommon.PendingAction; + + /** The CC for this transaction */ + cardID?: number; + + /** The display name of the purchaser card, if any */ + cardName?: string; }; /** Model of tasks search result */ From d72c556c4a41d20d4db8a8d806c4018d418339e8 Mon Sep 17 00:00:00 2001 From: borys3kk Date: Mon, 21 Jul 2025 17:37:19 +0200 Subject: [PATCH 17/21] remove console log --- src/libs/SearchUIUtils.ts | 1 - 1 file changed, 1 deletion(-) diff --git a/src/libs/SearchUIUtils.ts b/src/libs/SearchUIUtils.ts index 397c3adcff3b..aed8e8cb2438 100644 --- a/src/libs/SearchUIUtils.ts +++ b/src/libs/SearchUIUtils.ts @@ -532,7 +532,6 @@ function getTransactionsSections(data: OnyxTypes.SearchResults['data'], metadata // Use Map.get() for faster lookups with default values const from = personalDetailsMap.get(transactionItem.accountID.toString()) ?? emptyPersonalDetails; const to = transactionItem.managerID && !shouldShowBlankTo ? (personalDetailsMap.get(transactionItem.managerID.toString()) ?? emptyPersonalDetails) : emptyPersonalDetails; - console.log('cards info', transactionItem.cardID, transactionItem.cardName); const {formattedFrom, formattedTo, formattedTotal, formattedMerchant, date} = getTransactionItemCommonFormattedProperties(transactionItem, from, to, policy); const transactionSection: TransactionListItemType = { From 5d0961f0e6f3652bd47ad2fbbf04f6eed65bfb4e Mon Sep 17 00:00:00 2001 From: borys3kk Date: Wed, 23 Jul 2025 11:58:58 +0200 Subject: [PATCH 18/21] fix test --- tests/unit/Search/SearchUIUtilsTest.ts | 22 ++++++++++++++++++++++ 1 file changed, 22 insertions(+) diff --git a/tests/unit/Search/SearchUIUtilsTest.ts b/tests/unit/Search/SearchUIUtilsTest.ts index 093e8f5da066..a3adf8526b3e 100644 --- a/tests/unit/Search/SearchUIUtilsTest.ts +++ b/tests/unit/Search/SearchUIUtilsTest.ts @@ -238,6 +238,8 @@ const searchResults: OnyxTypes.SearchResults = { canDelete: true, canHold: true, canUnhold: false, + cardID: undefined, + cardName: undefined, category: '', comment: { comment: '', @@ -279,6 +281,8 @@ const searchResults: OnyxTypes.SearchResults = { canDelete: true, canHold: true, canUnhold: false, + cardID: undefined, + cardName: undefined, category: '', comment: { comment: '', @@ -321,6 +325,8 @@ const searchResults: OnyxTypes.SearchResults = { canDelete: true, canHold: true, canUnhold: false, + cardID: undefined, + cardName: undefined, category: '', comment: { comment: '', @@ -362,6 +368,8 @@ const searchResults: OnyxTypes.SearchResults = { canDelete: true, canHold: true, canUnhold: false, + cardID: undefined, + cardName: undefined, category: '', comment: { comment: '', @@ -453,6 +461,8 @@ const transactionsListItems = [ canDelete: true, canHold: true, canUnhold: false, + cardID: undefined, + cardName: undefined, category: '', comment: {comment: ''}, created: '2024-12-21', @@ -518,6 +528,8 @@ const transactionsListItems = [ canDelete: true, canHold: true, canUnhold: false, + cardID: undefined, + cardName: undefined, category: '', comment: {comment: ''}, created: '2024-12-21', @@ -588,6 +600,8 @@ const transactionsListItems = [ canDelete: true, canHold: true, canUnhold: false, + cardID: undefined, + cardName: undefined, category: '', comment: {comment: ''}, created: '2025-03-05', @@ -653,6 +667,8 @@ const transactionsListItems = [ canDelete: true, canHold: true, canUnhold: false, + cardID: undefined, + cardName: undefined, category: '', comment: {comment: ''}, created: '2025-03-05', @@ -754,6 +770,8 @@ const transactionReportGroupListItems = [ canDelete: true, canHold: true, canUnhold: false, + cardID: undefined, + cardName: undefined, category: '', comment: {comment: ''}, created: '2024-12-21', @@ -856,6 +874,8 @@ const transactionReportGroupListItems = [ canDelete: true, canHold: true, canUnhold: false, + cardID: undefined, + cardName: undefined, category: '', comment: {comment: ''}, created: '2024-12-21', @@ -1460,6 +1480,8 @@ describe('SearchUIUtils', () => { canDelete: false, canHold: true, canUnhold: false, + cardID: undefined, + cardName: undefined, category: 'Employee Meals Remote (Fringe Benefit)', action: 'approve', comment: { From 8e9d8c282486208f6594fb6e5d540613404bd9e8 Mon Sep 17 00:00:00 2001 From: borys3kk Date: Fri, 25 Jul 2025 15:45:13 +0200 Subject: [PATCH 19/21] refactor styles, receiptPreview component, fix displaying of distanceEReceipt --- src/CONST/index.ts | 2 + src/components/DistanceEReceipt.tsx | 7 +- .../ReceiptPreview/index.tsx | 95 ++++++++++++------- src/styles/index.ts | 12 +++ 4 files changed, 80 insertions(+), 36 deletions(-) diff --git a/src/CONST/index.ts b/src/CONST/index.ts index 4edcd477c26b..0dcca71e6761 100755 --- a/src/CONST/index.ts +++ b/src/CONST/index.ts @@ -1503,6 +1503,8 @@ const CONST = { SEARCH_MOST_RECENT_OPTIONS: 'search_most_recent_options', DEBOUNCE_HANDLE_SEARCH: 'debounce_handle_search', FAST_SEARCH_TREE_CREATION: 'fast_search_tree_creation', + SHOW_HOVER_PREVIEW_DELAY: 270, + SHOW_HOVER_PREVIEW_ANIMATION_DURATION: 200, }, PRIORITY_MODE: { GSD: 'gsd', diff --git a/src/components/DistanceEReceipt.tsx b/src/components/DistanceEReceipt.tsx index 8de8546150a6..19fdc23c2412 100644 --- a/src/components/DistanceEReceipt.tsx +++ b/src/components/DistanceEReceipt.tsx @@ -22,9 +22,12 @@ import Text from './Text'; type DistanceEReceiptProps = { /** The transaction for the distance expense */ transaction: Transaction; + + /** Whether the distanceEReceipt is shown as hover preview */ + hoverPreview?: boolean; }; -function DistanceEReceipt({transaction}: DistanceEReceiptProps) { +function DistanceEReceipt({transaction, hoverPreview = false}: DistanceEReceiptProps) { const styles = useThemeStyles(); const {translate} = useLocalize(); const thumbnail = hasReceipt(transaction) ? getThumbnailAndImageURIs(transaction).thumbnail : null; @@ -42,7 +45,7 @@ function DistanceEReceipt({transaction}: DistanceEReceiptProps) { [waypoints], ); return ( - + (null); const styles = useThemeStyles(); const [eReceiptScaleFactor, setEReceiptScaleFactor] = useState(0); const [imageAspectRatio, setImageAspectRatio] = useState(undefined); + const [distanceEReceiptAspectRatio, setDistanceEReceiptAspectRatio] = useState(undefined); + const [shouldShow, debounceShouldShow, setShouldShow] = useDebouncedState(false, CONST.TIMING.SHOW_HOVER_PREVIEW_DELAY); const {shouldUseNarrowLayout} = useResponsiveLayout(); + const hasMeasured = useRef(false); + const {windowHeight} = useWindowDimensions(); + + const handleDistanceEReceiptLayout = (e: LayoutChangeEvent) => { + if (hasMeasured.current) { + return; + } + hasMeasured.current = true; - const updateAspectRatio = useCallback( + const {height, width} = e.nativeEvent.layout; + if (height === 0) { + // on the initial layout, measured height is 0, so we want to set everything on the second one + hasMeasured.current = false; + return; + } + if (height * eReceiptScaleFactor > windowHeight - 120) { + // 120 value is taken from this comment: https://github.com/Expensify/App/pull/65184#issuecomment-3052852162 + setDistanceEReceiptAspectRatio(variables.eReceiptBGHWidth / (windowHeight - 120)); + return; + } + setDistanceEReceiptAspectRatio(variables.eReceiptBGHWidth / height); + setEReceiptScaleFactor(width / variables.eReceiptBGHWidth); + }; + + const updateImageAspectRatio = useCallback( (width: number, height: number) => { if (!source) { return; @@ -44,35 +79,21 @@ function ReceiptPreview({source, hovered, isEReceipt = false, transactionItem}: (event: ImageOnLoadEvent) => { const {width, height} = event.nativeEvent; - updateAspectRatio(width, height); + updateImageAspectRatio(width, height); }, - [updateAspectRatio], + [updateImageAspectRatio], ); - const onLayout = (e: LayoutChangeEvent) => { + const handleEReceiptLayout = (e: LayoutChangeEvent) => { const {width} = e.nativeEvent.layout; setEReceiptScaleFactor(width / variables.eReceiptBGHWidth); }; useEffect(() => { - if (hovered) { - debounceTimeout.current = setTimeout(() => { - setShouldShow(true); - }, showPreviewDelay); - } else { - setShouldShow(false); - } - - return () => { - if (!debounceTimeout.current) { - return; - } - clearTimeout(debounceTimeout.current); - debounceTimeout.current = null; - }; - }, [hovered]); + setShouldShow(hovered); + }, [hovered, setShouldShow]); - if (shouldUseNarrowLayout || !shouldShow || (!source && !isEReceipt && !isDistanceEReceipt)) { + if (shouldUseNarrowLayout || !debounceShouldShow || !shouldShow || (!source && !isEReceipt && !isDistanceEReceipt)) { return null; } @@ -81,8 +102,8 @@ function ReceiptPreview({source, hovered, isEReceipt = false, transactionItem}: return ReactDOM.createPortal( {shouldShowImage ? ( @@ -94,15 +115,21 @@ function ReceiptPreview({source, hovered, isEReceipt = false, transactionItem}: /> ) : ( - + {shouldShowDistanceEReceipt ? ( - - + + ) : ( backgroundColor: theme.appBG, }, + receiptPreviewEReceiptsContainer: { + ...sizing.w100, + ...sizing.h100, + backgroundColor: colors.green800, + }, + + receiptPreviewEReceipt: { + ...flex.flexColumn, + ...flex.justifyContentCenter, + ...flex.alignItemsCenter, + }, + topBarWrapper: { zIndex: 15, }, From d5d4a5937b6dbde7d3baea73eeced0a7f616d145 Mon Sep 17 00:00:00 2001 From: borys3kk Date: Fri, 25 Jul 2025 16:51:49 +0200 Subject: [PATCH 20/21] move to const --- src/components/TransactionItemRow/ReceiptPreview/index.tsx | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/src/components/TransactionItemRow/ReceiptPreview/index.tsx b/src/components/TransactionItemRow/ReceiptPreview/index.tsx index efe2aa53e5e4..c180b9a4804b 100644 --- a/src/components/TransactionItemRow/ReceiptPreview/index.tsx +++ b/src/components/TransactionItemRow/ReceiptPreview/index.tsx @@ -17,6 +17,7 @@ import CONST from '@src/CONST'; import type {Transaction} from '@src/types/onyx'; const eReceiptAspectRatio = variables.eReceiptBGHWidth / variables.eReceiptBGHeight; +const verticalPreviewMargin = 120; // 60px top margin and 60px bottom margin type ReceiptPreviewProps = { /** Path to the image to be opened in the preview */ @@ -55,9 +56,8 @@ function ReceiptPreview({source, hovered, isEReceipt = false, transactionItem}: hasMeasured.current = false; return; } - if (height * eReceiptScaleFactor > windowHeight - 120) { - // 120 value is taken from this comment: https://github.com/Expensify/App/pull/65184#issuecomment-3052852162 - setDistanceEReceiptAspectRatio(variables.eReceiptBGHWidth / (windowHeight - 120)); + if (height * eReceiptScaleFactor > windowHeight - verticalPreviewMargin) { + setDistanceEReceiptAspectRatio(variables.eReceiptBGHWidth / (windowHeight - verticalPreviewMargin)); return; } setDistanceEReceiptAspectRatio(variables.eReceiptBGHWidth / height); From 818cfe7c2d5bce0bbad4e37dd8b22cf3e15b5773 Mon Sep 17 00:00:00 2001 From: borys3kk Date: Mon, 28 Jul 2025 10:25:00 +0200 Subject: [PATCH 21/21] move to const --- src/CONST/index.ts | 1 + src/components/TransactionItemRow/ReceiptPreview/index.tsx | 5 ++--- 2 files changed, 3 insertions(+), 3 deletions(-) diff --git a/src/CONST/index.ts b/src/CONST/index.ts index 0dcca71e6761..6e1dcb4aac76 100755 --- a/src/CONST/index.ts +++ b/src/CONST/index.ts @@ -1035,6 +1035,7 @@ const CONST = { SHUTTER_SIZE: 90, MAX_REPORT_PREVIEW_RECEIPTS: 3, }, + RECEIPT_PREVIEW_TOP_BOTTOM_MARGIN: 120, REPORT: { ROLE: { ADMIN: 'admin', diff --git a/src/components/TransactionItemRow/ReceiptPreview/index.tsx b/src/components/TransactionItemRow/ReceiptPreview/index.tsx index c180b9a4804b..a444b4cc5772 100644 --- a/src/components/TransactionItemRow/ReceiptPreview/index.tsx +++ b/src/components/TransactionItemRow/ReceiptPreview/index.tsx @@ -17,7 +17,6 @@ import CONST from '@src/CONST'; import type {Transaction} from '@src/types/onyx'; const eReceiptAspectRatio = variables.eReceiptBGHWidth / variables.eReceiptBGHeight; -const verticalPreviewMargin = 120; // 60px top margin and 60px bottom margin type ReceiptPreviewProps = { /** Path to the image to be opened in the preview */ @@ -56,8 +55,8 @@ function ReceiptPreview({source, hovered, isEReceipt = false, transactionItem}: hasMeasured.current = false; return; } - if (height * eReceiptScaleFactor > windowHeight - verticalPreviewMargin) { - setDistanceEReceiptAspectRatio(variables.eReceiptBGHWidth / (windowHeight - verticalPreviewMargin)); + if (height * eReceiptScaleFactor > windowHeight - CONST.RECEIPT_PREVIEW_TOP_BOTTOM_MARGIN) { + setDistanceEReceiptAspectRatio(variables.eReceiptBGHWidth / (windowHeight - CONST.RECEIPT_PREVIEW_TOP_BOTTOM_MARGIN)); return; } setDistanceEReceiptAspectRatio(variables.eReceiptBGHWidth / height);