From 35419da97302b962f6776355b8c92426af679866 Mon Sep 17 00:00:00 2001 From: Jakub Szymczak Date: Thu, 10 Jul 2025 17:52:46 +0200 Subject: [PATCH 01/10] add MoneyRequestReportTransactionItem and put all table report view related logic in in --- .../MoneyRequestReportTransactionItem.tsx | 126 ++++++++ .../MoneyRequestReportTransactionList.tsx | 125 +++----- .../Search/TransactionListItem.tsx | 2 +- src/components/TransactionItemRow/index.tsx | 276 ++++++++---------- 4 files changed, 285 insertions(+), 244 deletions(-) create mode 100644 src/components/MoneyRequestReportView/MoneyRequestReportTransactionItem.tsx diff --git a/src/components/MoneyRequestReportView/MoneyRequestReportTransactionItem.tsx b/src/components/MoneyRequestReportView/MoneyRequestReportTransactionItem.tsx new file mode 100644 index 000000000000..e185b4858cd8 --- /dev/null +++ b/src/components/MoneyRequestReportView/MoneyRequestReportTransactionItem.tsx @@ -0,0 +1,126 @@ +import React, {useEffect, useRef} from 'react'; +import type {View} from 'react-native'; +import {getButtonRole} from '@components/Button/utils'; +import OfflineWithFeedback from '@components/OfflineWithFeedback'; +import {PressableWithFeedback} from '@components/Pressable'; +import type {TableColumnSize} from '@components/Search/types'; +import TransactionItemRow from '@components/TransactionItemRow'; +import useAnimatedHighlightStyle from '@hooks/useAnimatedHighlightStyle'; +import useLocalize from '@hooks/useLocalize'; +import useResponsiveLayout from '@hooks/useResponsiveLayout'; +import useTheme from '@hooks/useTheme'; +import useThemeStyles from '@hooks/useThemeStyles'; +import ControlSelection from '@libs/ControlSelection'; +import canUseTouchScreen from '@libs/DeviceCapabilities/canUseTouchScreen'; +import {getTransactionPendingAction, isTransactionPendingDelete} from '@libs/TransactionUtils'; +import variables from '@styles/variables'; +import CONST from '@src/CONST'; +import type {TransactionWithOptionalHighlight} from './MoneyRequestReportTransactionList'; + +const allReportColumns = [ + CONST.REPORT.TRANSACTION_LIST.COLUMNS.RECEIPT, + CONST.REPORT.TRANSACTION_LIST.COLUMNS.TYPE, + CONST.REPORT.TRANSACTION_LIST.COLUMNS.DATE, + CONST.REPORT.TRANSACTION_LIST.COLUMNS.MERCHANT, + CONST.REPORT.TRANSACTION_LIST.COLUMNS.CATEGORY, + CONST.REPORT.TRANSACTION_LIST.COLUMNS.TAG, + CONST.REPORT.TRANSACTION_LIST.COLUMNS.COMMENTS, + CONST.REPORT.TRANSACTION_LIST.COLUMNS.TOTAL_AMOUNT, +]; + +type MoneyRequestReportTransactionItemProps = { + transaction: TransactionWithOptionalHighlight; + isSelectionModeEnabled: boolean; + toggleTransaction: (transactionID: string) => void; + handleOnPress: (transactionID: string) => void; + handleLongPress: (transactionID: string) => void; + isSelected: boolean; + dateColumnSize: TableColumnSize; + amountColumnSize: TableColumnSize; + taxAmountColumnSize: TableColumnSize; + scrollToNewTransaction?: (offset: number) => void; +}; + +function MoneyRequestReportTransactionItem({ + transaction, + isSelectionModeEnabled, + toggleTransaction, + isSelected, + handleOnPress, + handleLongPress, + dateColumnSize, + amountColumnSize, + taxAmountColumnSize, + scrollToNewTransaction, +}: MoneyRequestReportTransactionItemProps) { + const {translate} = useLocalize(); + const styles = useThemeStyles(); + // eslint-disable-next-line rulesdir/prefer-shouldUseNarrowLayout-instead-of-isSmallScreenWidth + const {isSmallScreenWidth, isMediumScreenWidth, shouldUseNarrowLayout} = useResponsiveLayout(); + const theme = useTheme(); + const isPendingDelete = isTransactionPendingDelete(transaction); + const pendingAction = getTransactionPendingAction(transaction); + + const viewRef = useRef(null); + + useEffect(() => { + if (!transaction.shouldBeHighlighted || !scrollToNewTransaction) { + return; + } + viewRef?.current?.measure((x, y, width, height, pageX, pageY) => { + scrollToNewTransaction?.(pageY); + }); + }, [scrollToNewTransaction, transaction.shouldBeHighlighted]); + + const animatedHighlightStyle = useAnimatedHighlightStyle({ + borderRadius: variables.componentBorderRadius, + shouldHighlight: transaction.shouldBeHighlighted ?? false, + highlightColor: theme.messageHighlightBG, + backgroundColor: theme.highlightBG, + }); + + return ( + + { + handleOnPress(transaction.transactionID); + }} + accessibilityLabel={translate('iou.viewDetails')} + role={getButtonRole(true)} + isNested + id={transaction.transactionID} + style={[styles.transactionListItemStyle]} + hoverStyle={[!isPendingDelete && styles.hoveredComponentBG, isSelected && styles.activeComponentBG]} + dataSet={{[CONST.SELECTION_SCRAPER_HIDDEN_ELEMENT]: true}} + onPressIn={() => canUseTouchScreen() && ControlSelection.block()} + onPressOut={() => ControlSelection.unblock()} + onLongPress={() => { + handleLongPress(transaction.transactionID); + }} + disabled={isTransactionPendingDelete(transaction)} + ref={viewRef} + wrapperStyle={animatedHighlightStyle} + > + + + + ); +} + +export default MoneyRequestReportTransactionItem; diff --git a/src/components/MoneyRequestReportView/MoneyRequestReportTransactionList.tsx b/src/components/MoneyRequestReportView/MoneyRequestReportTransactionList.tsx index ca5baba732e6..b69ebecd2020 100644 --- a/src/components/MoneyRequestReportView/MoneyRequestReportTransactionList.tsx +++ b/src/components/MoneyRequestReportView/MoneyRequestReportTransactionList.tsx @@ -4,29 +4,22 @@ import React, {memo, useCallback, useMemo, useState} from 'react'; import {View} from 'react-native'; import Animated, {FadeIn, FadeOut} from 'react-native-reanimated'; import type {TupleToUnion} from 'type-fest'; -import {getButtonRole} from '@components/Button/utils'; import Checkbox from '@components/Checkbox'; import * as Expensicons from '@components/Icon/Expensicons'; import MenuItem from '@components/MenuItem'; import Modal from '@components/Modal'; -import PressableWithFeedback from '@components/Pressable/PressableWithFeedback'; import {useSearchContext} from '@components/Search/SearchContext'; import type {SortOrder} from '@components/Search/types'; import Text from '@components/Text'; -import TransactionItemRow from '@components/TransactionItemRow'; import useCopySelectionHelper from '@hooks/useCopySelectionHelper'; -import useHover from '@hooks/useHover'; import useLocalize from '@hooks/useLocalize'; import useMobileSelectionMode from '@hooks/useMobileSelectionMode'; -import {useMouseContext} from '@hooks/useMouseContext'; import useResponsiveLayout from '@hooks/useResponsiveLayout'; import useStyleUtils from '@hooks/useStyleUtils'; import useThemeStyles from '@hooks/useThemeStyles'; import {turnOnMobileSelectionMode} from '@libs/actions/MobileSelectionMode'; import {setActiveTransactionThreadIDs} from '@libs/actions/TransactionThreadNavigation'; -import ControlSelection from '@libs/ControlSelection'; import {convertToDisplayString} from '@libs/CurrencyUtils'; -import {canUseTouchScreen} from '@libs/DeviceCapabilities'; import {getThreadReportIDsForTransactions} from '@libs/MoneyRequestReportUtils'; import {navigationRef} from '@libs/Navigation/Navigation'; import {getIOUActionForTransactionID} from '@libs/ReportActionsUtils'; @@ -41,6 +34,7 @@ import NAVIGATORS from '@src/NAVIGATORS'; import ROUTES from '@src/ROUTES'; import type * as OnyxTypes from '@src/types/onyx'; import MoneyRequestReportTableHeader from './MoneyRequestReportTableHeader'; +import MoneyRequestReportTransactionItem from './MoneyRequestReportTransactionItem'; import SearchMoneyRequestReportEmptyState from './SearchMoneyRequestReportEmptyState'; type MoneyRequestReportTransactionListProps = { @@ -80,17 +74,6 @@ const sortableColumnNames = [ CONST.SEARCH.TABLE_COLUMNS.TOTAL_AMOUNT, ]; -const allReportColumns = [ - CONST.REPORT.TRANSACTION_LIST.COLUMNS.RECEIPT, - CONST.REPORT.TRANSACTION_LIST.COLUMNS.TYPE, - CONST.REPORT.TRANSACTION_LIST.COLUMNS.DATE, - CONST.REPORT.TRANSACTION_LIST.COLUMNS.MERCHANT, - CONST.REPORT.TRANSACTION_LIST.COLUMNS.CATEGORY, - CONST.REPORT.TRANSACTION_LIST.COLUMNS.TAG, - CONST.REPORT.TRANSACTION_LIST.COLUMNS.COMMENTS, - CONST.REPORT.TRANSACTION_LIST.COLUMNS.TOTAL_AMOUNT, -]; - type SortableColumnName = TupleToUnion; type SortedTransactions = { @@ -135,9 +118,6 @@ function MoneyRequestReportTransactionList({ return pendingAction && styles.opacitySemiTransparent; }, [styles.opacitySemiTransparent, transactions]); - const {bind} = useHover(); - const {isMouseDownOnInput, setMouseUp} = useMouseContext(); - const {selectedTransactionIDs, setSelectedTransactions, clearSelectedTransactions} = useSearchContext(); const isMobileSelectionModeEnabled = useMobileSelectionMode(); @@ -167,12 +147,6 @@ function MoneyRequestReportTransactionList({ }, [clearSelectedTransactions]), ); - const handleMouseLeave = (e: React.MouseEvent) => { - bind.onMouseLeave(); - e.stopPropagation(); - setMouseUp(); - }; - const [sortConfig, setSortConfig] = useState({ sortBy: CONST.SEARCH.TABLE_COLUMNS.DATE, sortOrder: CONST.SEARCH.SORT_ORDER.ASC, @@ -190,8 +164,8 @@ function MoneyRequestReportTransactionList({ }, [newTransactions, sortBy, sortOrder, transactions]); const navigateToTransaction = useCallback( - (activeTransaction: OnyxTypes.Transaction) => { - const iouAction = getIOUActionForTransactionID(reportActions, activeTransaction.transactionID); + (activeTransactionID: string) => { + const iouAction = getIOUActionForTransactionID(reportActions, activeTransactionID); const reportIDToNavigate = iouAction?.childReportID; if (!reportIDToNavigate) { return; @@ -220,9 +194,35 @@ function MoneyRequestReportTransactionList({ }; }, [transactions]); - const pressableStyle = [styles.overflowHidden]; const isEmptyTransactions = isEmpty(transactions); + const handleLongPress = useCallback( + (transactionID: string) => { + if (!isSmallScreenWidth) { + return; + } + if (isMobileSelectionModeEnabled) { + toggleTransaction(transactionID); + return; + } + setSelectedTransactionID(transactionID); + setIsModalVisible(true); + }, + [isSmallScreenWidth, isMobileSelectionModeEnabled, toggleTransaction, setSelectedTransactionID, setIsModalVisible], + ); + + const handleOnPress = useCallback( + (transactionID: string) => { + if (isMobileSelectionModeEnabled) { + toggleTransaction(transactionID); + return; + } + + navigateToTransaction(transactionID); + }, + [isMobileSelectionModeEnabled, toggleTransaction, navigateToTransaction], + ); + const listHorizontalPadding = styles.ph5; return ( <> @@ -268,60 +268,19 @@ function MoneyRequestReportTransactionList({ {sortedTransactions.map((transaction) => { return ( - { - if (isMouseDownOnInput) { - e?.stopPropagation(); - return; - } - - if (isMobileSelectionModeEnabled) { - toggleTransaction(transaction.transactionID); - return; - } - - navigateToTransaction(transaction); - }} - accessibilityLabel={translate('iou.viewDetails')} - role={getButtonRole(true)} - isNested - hoverDimmingValue={1} - onMouseDown={(e) => e.preventDefault()} - id={transaction.transactionID} - style={[pressableStyle, styles.userSelectNone]} - dataSet={{[CONST.SELECTION_SCRAPER_HIDDEN_ELEMENT]: true}} - onMouseLeave={handleMouseLeave} - onPressIn={() => canUseTouchScreen() && ControlSelection.block()} - onPressOut={() => ControlSelection.unblock()} - onLongPress={() => { - if (!isSmallScreenWidth) { - return; - } - if (isMobileSelectionModeEnabled) { - toggleTransaction(transaction.transactionID); - return; - } - setSelectedTransactionID(transaction.transactionID); - setIsModalVisible(true); - }} - disabled={isTransactionPendingDelete(transaction)} - > - - + transaction={transaction} + isSelectionModeEnabled={isMobileSelectionModeEnabled} + toggleTransaction={toggleTransaction} + isSelected={isTransactionSelected(transaction.transactionID)} + handleOnPress={handleOnPress} + handleLongPress={handleLongPress} + dateColumnSize={dateColumnSize} + amountColumnSize={amountColumnSize} + taxAmountColumnSize={taxAmountColumnSize} + scrollToNewTransaction={scrollToNewTransaction} + /> ); })} diff --git a/src/components/SelectionList/Search/TransactionListItem.tsx b/src/components/SelectionList/Search/TransactionListItem.tsx index cacecdd3a75a..d54a84384aa9 100644 --- a/src/components/SelectionList/Search/TransactionListItem.tsx +++ b/src/components/SelectionList/Search/TransactionListItem.tsx @@ -136,7 +136,7 @@ function TransactionListItem({ onCheckboxPress={handleCheckboxPress} shouldUseNarrowLayout={!isLargeScreenWidth} columns={columns} - isParentHovered={hovered} + // isParentHovered={hovered} isActionLoading={isLoading ?? transactionItem.isActionLoading} isSelected={!!transactionItem.isSelected} dateColumnSize={dateColumnSize} diff --git a/src/components/TransactionItemRow/index.tsx b/src/components/TransactionItemRow/index.tsx index aec7002ad4eb..8cefb4ed39a8 100644 --- a/src/components/TransactionItemRow/index.tsx +++ b/src/components/TransactionItemRow/index.tsx @@ -1,21 +1,16 @@ -import React, {useEffect, useMemo, useRef} from 'react'; +import React, {useMemo} from 'react'; import {View} from 'react-native'; import type {ViewStyle} from 'react-native'; -import Animated from 'react-native-reanimated'; import type {ValueOf} from 'type-fest'; import Checkbox from '@components/Checkbox'; import type {TransactionWithOptionalHighlight} from '@components/MoneyRequestReportView/MoneyRequestReportTransactionList'; -import OfflineWithFeedback from '@components/OfflineWithFeedback'; import type {TableColumnSize} from '@components/Search/types'; import ActionCell from '@components/SelectionList/Search/ActionCell'; import DateCell from '@components/SelectionList/Search/DateCell'; import UserInfoCell from '@components/SelectionList/Search/UserInfoCell'; import Text from '@components/Text'; -import useAnimatedHighlightStyle from '@hooks/useAnimatedHighlightStyle'; -import useHover from '@hooks/useHover'; import useLocalize from '@hooks/useLocalize'; import useStyleUtils from '@hooks/useStyleUtils'; -import useTheme from '@hooks/useTheme'; import useThemeStyles from '@hooks/useThemeStyles'; import {isCategoryMissing} from '@libs/CategoryUtils'; import Parser from '@libs/Parser'; @@ -24,15 +19,12 @@ import { getDescription, getMerchant, getCreated as getTransactionCreated, - getTransactionPendingAction, hasMissingSmartscanFields, isAmountMissing, isMerchantMissing, isScanning, - isTransactionPendingDelete, isUnreportedAndHasInvalidDistanceRateTransaction, } from '@libs/TransactionUtils'; -import variables from '@styles/variables'; import CONST from '@src/CONST'; import type {TranslationPaths} from '@src/languages/types'; import type {TransactionViolation} from '@src/types/onyx'; @@ -95,13 +87,11 @@ type TransactionItemRowProps = { shouldShowCheckbox: boolean; columns?: Array>; onButtonPress?: () => void; - isParentHovered?: boolean; columnWrapperStyles?: ViewStyle[]; - scrollToNewTransaction?: ((offset: number) => void) | undefined; isReportItemChild?: boolean; isActionLoading?: boolean; - isInReportTableView?: boolean; isInSingleTransactionReport?: boolean; + isDisabled?: boolean; }; /** If merchant name is empty or (none), then it falls back to description if screen is narrow */ @@ -136,21 +126,15 @@ function TransactionItemRow({ shouldShowCheckbox = false, columns, onButtonPress = () => {}, - isParentHovered, columnWrapperStyles, - scrollToNewTransaction, isReportItemChild = false, isActionLoading, - isInReportTableView = false, isInSingleTransactionReport = false, + isDisabled = false, }: TransactionItemRowProps) { const styles = useThemeStyles(); const {translate} = useLocalize(); const StyleUtils = useStyleUtils(); - const theme = useTheme(); - const pendingAction = getTransactionPendingAction(transactionItem); - const isPendingDelete = isTransactionPendingDelete(transactionItem); - const viewRef = useRef(null); const hasCategoryOrTag = !isCategoryMissing(transactionItem?.category) || !!transactionItem.tag; const createdAt = getTransactionCreated(transactionItem); @@ -159,23 +143,12 @@ function TransactionItemRow({ const isAmountColumnWide = amountColumnSize === CONST.SEARCH.TABLE_COLUMN_SIZES.WIDE; const isTaxAmountColumnWide = taxAmountColumnSize === CONST.SEARCH.TABLE_COLUMN_SIZES.WIDE; - const animatedHighlightStyle = useAnimatedHighlightStyle({ - shouldHighlight: transactionItem.shouldBeHighlighted ?? false, - borderRadius: variables.componentBorderRadius, - highlightColor: theme.messageHighlightBG, - backgroundColor: theme.highlightBG, - }); - - const {hovered, bind: bindHover} = useHover(); const bgActiveStyles = useMemo(() => { - if (isSelected) { - return styles.activeComponentBG; - } - - if (hovered || isParentHovered) { - return styles.hoveredComponentBG; + if (!isSelected) { + return []; } - }, [hovered, isParentHovered, isSelected, styles.activeComponentBG, styles.hoveredComponentBG]); + return styles.activeComponentBG; + }, [isSelected, styles.activeComponentBG]); const merchantOrDescriptionName = useMemo(() => getMerchantNameWithFallback(transactionItem, translate, shouldUseNarrowLayout), [shouldUseNarrowLayout, transactionItem, translate]); const missingFieldError = useMemo(() => { @@ -199,15 +172,6 @@ function TransactionItemRow({ } }, [transactionItem, translate]); - useEffect(() => { - if (!transactionItem.shouldBeHighlighted || !scrollToNewTransaction) { - return; - } - viewRef?.current?.measure((x, y, width, height, pageX, pageY) => { - scrollToNewTransaction?.(pageY); - }); - }, [scrollToNewTransaction, transactionItem.shouldBeHighlighted]); - const columnComponent: ColumnComponents = useMemo( () => ({ [CONST.REPORT.TRANSACTION_LIST.COLUMNS.TYPE]: ( @@ -382,129 +346,121 @@ function TransactionItemRow({ ], ); const safeColumnWrapperStyle = columnWrapperStyles ?? [styles.p3, styles.expenseWidgetRadius]; - return ( - - - {shouldUseNarrowLayout ? ( - - - - {shouldShowCheckbox && ( - - { - onCheckboxPress(transactionItem.transactionID); - }} - accessibilityLabel={CONST.ROLE.CHECKBOX} - isChecked={isSelected} - /> - - )} - - + + {shouldShowCheckbox && ( + + { + onCheckboxPress(transactionItem.transactionID); + }} + accessibilityLabel={CONST.ROLE.CHECKBOX} + isChecked={isSelected} + /> + + )} + {/* CAN REDUCE */} + + + + + + + + + {!merchantOrDescriptionName && ( + + - - - - - - - {!merchantOrDescriptionName && ( - - - - )} - - {!!merchantOrDescriptionName && ( - - - - - )} - - - - - {hasCategoryOrTag && ( - - - - - )} - - + {!!merchantOrDescriptionName && ( + + + - - - ) : ( - - - - - { - onCheckboxPress(transactionItem.transactionID); - }} - accessibilityLabel={CONST.ROLE.CHECKBOX} - isChecked={isSelected} - /> - - {columns?.map((column) => columnComponent[column])} + )} + + + + + {hasCategoryOrTag && ( + + + - - - - )} - + )} + + + + + + ); + } + + return ( + + + {/* CAN REDUCE */} + + { + onCheckboxPress(transactionItem.transactionID); + }} + accessibilityLabel={CONST.ROLE.CHECKBOX} + isChecked={isSelected} + /> + + {columns?.map((column) => columnComponent[column])} + + ); } From 3b5a50275a74cafa317efd900715f44e028ab93e Mon Sep 17 00:00:00 2001 From: Jakub Szymczak Date: Thu, 10 Jul 2025 17:53:15 +0200 Subject: [PATCH 02/10] remove unused MobieSelectionMode type --- src/types/onyx/MobileSelectionMode.ts | 11 ----------- src/types/onyx/index.ts | 2 -- 2 files changed, 13 deletions(-) delete mode 100644 src/types/onyx/MobileSelectionMode.ts diff --git a/src/types/onyx/MobileSelectionMode.ts b/src/types/onyx/MobileSelectionMode.ts deleted file mode 100644 index a54909a1d19f..000000000000 --- a/src/types/onyx/MobileSelectionMode.ts +++ /dev/null @@ -1,11 +0,0 @@ -/** - * Model of mobile selection mode - */ -type MobileSelectionMode = { - /** - * Whether the mobile selection mode is enabled or not - */ - isEnabled: boolean; -}; - -export default MobileSelectionMode; diff --git a/src/types/onyx/index.ts b/src/types/onyx/index.ts index 5157dc67e06f..ae3a4955ae45 100644 --- a/src/types/onyx/index.ts +++ b/src/types/onyx/index.ts @@ -44,7 +44,6 @@ import type LockAccountDetails from './LockAccountDetails'; import type {LoginList} from './Login'; import type Login from './Login'; import type MapboxAccessToken from './MapboxAccessToken'; -import type MobileSelectionMode from './MobileSelectionMode'; import type Modal from './Modal'; import type Network from './Network'; import type NewGroupChatDraft from './NewGroupChatDraft'; @@ -243,7 +242,6 @@ export type { BillingStatus, CancellationDetails, ApprovalWorkflowOnyx, - MobileSelectionMode, CardFeeds, SaveSearch, RecentSearchItem, From dec4b0478575b702ed8cf4f91e7d7dd082f645b3 Mon Sep 17 00:00:00 2001 From: Jakub Szymczak Date: Fri, 11 Jul 2025 10:22:38 +0200 Subject: [PATCH 03/10] fix SearchFiltersBar no dropdown bug --- src/pages/Search/SearchPage.tsx | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/pages/Search/SearchPage.tsx b/src/pages/Search/SearchPage.tsx index 3ca23cb139bc..d27c4b347fcb 100644 --- a/src/pages/Search/SearchPage.tsx +++ b/src/pages/Search/SearchPage.tsx @@ -110,7 +110,7 @@ function SearchPage({route}: SearchPageProps) { const selectedTransactionsKeys = Object.keys(selectedTransactions ?? {}); const headerButtonsOptions = useMemo(() => { - if (selectedTransactionsKeys.length === 0 || !status || !hash) { + if (selectedTransactionsKeys.length === 0 || status == null || !hash) { return CONST.EMPTY_ARRAY as unknown as Array>; } From 44b4bbbb2819a6eaa80f610a4ec6b2af62306660 Mon Sep 17 00:00:00 2001 From: Jakub Szymczak Date: Fri, 11 Jul 2025 10:34:55 +0200 Subject: [PATCH 04/10] clean up --- .../Search/TransactionGroupListItem.tsx | 9 ++------- .../SelectionList/Search/TransactionListItem.tsx | 8 ++------ src/stories/TransactionItemRow.stories.tsx | 7 +------ tests/ui/TransactionItemRowRBRTest.tsx | 13 ++++++------- 4 files changed, 11 insertions(+), 26 deletions(-) diff --git a/src/components/SelectionList/Search/TransactionGroupListItem.tsx b/src/components/SelectionList/Search/TransactionGroupListItem.tsx index aab50acb0eaa..8970495c4455 100644 --- a/src/components/SelectionList/Search/TransactionGroupListItem.tsx +++ b/src/components/SelectionList/Search/TransactionGroupListItem.tsx @@ -18,7 +18,6 @@ import type { import Text from '@components/Text'; import TransactionItemRow from '@components/TransactionItemRow'; import useAnimatedHighlightStyle from '@hooks/useAnimatedHighlightStyle'; -import useHover from '@hooks/useHover'; import useLocalize from '@hooks/useLocalize'; import useOnyx from '@hooks/useOnyx'; import useResponsiveLayout from '@hooks/useResponsiveLayout'; @@ -80,7 +79,7 @@ function TransactionGroupListItem({ backgroundColor: theme.highlightBG, }); - const mergedListItemStyle = [styles.transactionGroupListItemStyle, item.isSelected && styles.activeComponentBG]; + const pressableStyle = [styles.transactionGroupListItemStyle, item.isSelected && styles.activeComponentBG]; const openReportInRHP = (transactionItem: TransactionListItemType) => { const backTo = Navigation.getActiveRoute(); @@ -152,7 +151,6 @@ function TransactionGroupListItem({ }, [groupItem, policy, onSelectRow, onCheckboxPress, isDisabledOrEmpty, isFocused, canSelectMultiple, groupBy]); const StyleUtils = useStyleUtils(); - const {hovered, bind} = useHover(); const pressableRef = useRef(null); const onPress = useCallback(() => { @@ -168,8 +166,6 @@ function TransactionGroupListItem({ return ( ({ dataSet={{[CONST.SELECTION_SCRAPER_HIDDEN_ELEMENT]: true, [CONST.INNER_BOX_SHADOW_ELEMENT]: false}} id={item.keyForList ?? ''} style={[ - mergedListItemStyle, + pressableStyle, isFocused && StyleUtils.getItemBackgroundColorStyle(!!item.isSelected, !!isFocused, !!item.isDisabled, theme.activeComponentBG, theme.hoverComponentBG), ]} onFocus={onFocus} @@ -214,7 +210,6 @@ function TransactionGroupListItem({ onButtonPress={() => { openReportInRHP(transaction); }} - isParentHovered={hovered} columnWrapperStyles={[styles.ph3, styles.pv1Half]} isReportItemChild isInSingleTransactionReport={groupItem.transactions.length === 1} diff --git a/src/components/SelectionList/Search/TransactionListItem.tsx b/src/components/SelectionList/Search/TransactionListItem.tsx index d54a84384aa9..e4e8cafb88c5 100644 --- a/src/components/SelectionList/Search/TransactionListItem.tsx +++ b/src/components/SelectionList/Search/TransactionListItem.tsx @@ -8,7 +8,6 @@ import {useSearchContext} from '@components/Search/SearchContext'; import type {ListItem, TransactionListItemProps, TransactionListItemType} from '@components/SelectionList/types'; import TransactionItemRow from '@components/TransactionItemRow'; import useAnimatedHighlightStyle from '@hooks/useAnimatedHighlightStyle'; -import useHover from '@hooks/useHover'; import useResponsiveLayout from '@hooks/useResponsiveLayout'; import useStyleUtils from '@hooks/useStyleUtils'; import useSyncFocus from '@hooks/useSyncFocus'; @@ -39,7 +38,7 @@ function TransactionListItem({ const {isLargeScreenWidth, shouldUseNarrowLayout} = useResponsiveLayout(); const {currentSearchHash} = useSearchContext(); - const mergedListItemStyle = [ + const pressableStyle = [ styles.transactionListItemStyle, !isLargeScreenWidth && styles.pt3, item.isSelected && styles.activeComponentBG, @@ -96,7 +95,6 @@ function TransactionListItem({ }, [item, onLongPressRow]); const StyleUtils = useStyleUtils(); - const {hovered, bind} = useHover(); const pressableRef = useRef(null); useSyncFocus(pressableRef, !!isFocused, shouldSyncFocus); @@ -104,8 +102,6 @@ function TransactionListItem({ return ( ({ dataSet={{[CONST.SELECTION_SCRAPER_HIDDEN_ELEMENT]: true, [CONST.INNER_BOX_SHADOW_ELEMENT]: false}} id={item.keyForList ?? ''} style={[ - mergedListItemStyle, + pressableStyle, isFocused && StyleUtils.getItemBackgroundColorStyle(!!item.isSelected, !!isFocused, !!item.isDisabled, theme.activeComponentBG, theme.hoverComponentBG), ]} onFocus={onFocus} diff --git a/src/stories/TransactionItemRow.stories.tsx b/src/stories/TransactionItemRow.stories.tsx index 35e326bb987c..f21d81fcee20 100644 --- a/src/stories/TransactionItemRow.stories.tsx +++ b/src/stories/TransactionItemRow.stories.tsx @@ -32,7 +32,6 @@ type TransactionItemRowProps = { shouldShowTooltip: boolean; shouldShowCheckbox: boolean; columns?: Array>; - isParentHovered?: boolean; }; const story: Meta = { @@ -62,9 +61,6 @@ const story: Meta = { shouldShowCheckbox: { control: 'boolean', }, - isParentHovered: { - control: 'boolean', - }, columns: { control: { @@ -79,7 +75,7 @@ const story: Meta = { }; function Template( - {transactionItem, shouldUseNarrowLayout, isSelected, shouldShowTooltip, shouldShowCheckbox, columns, isParentHovered}: TransactionItemRowProps, + {transactionItem, shouldUseNarrowLayout, isSelected, shouldShowTooltip, shouldShowCheckbox, columns}: TransactionItemRowProps, {parameters}: {parameters: {useLightTheme?: boolean}}, ) { const theme = parameters.useLightTheme ? CONST.THEME.LIGHT : CONST.THEME.DARK; @@ -99,7 +95,6 @@ function Template( onCheckboxPress={() => {}} shouldShowCheckbox={shouldShowCheckbox} columns={columns} - isParentHovered={isParentHovered} onButtonPress={() => {}} /> diff --git a/tests/ui/TransactionItemRowRBRTest.tsx b/tests/ui/TransactionItemRowRBRTest.tsx index 7cf82ef59ea9..d9f5d6835732 100644 --- a/tests/ui/TransactionItemRowRBRTest.tsx +++ b/tests/ui/TransactionItemRowRBRTest.tsx @@ -38,14 +38,13 @@ const defaultProps = { }; // Helper function to render TransactionItemRow with providers -const renderTransactionItemRow = (transactionItem: TransactionWithOptionalSearchFields, isInReportTableView = true) => { +const renderTransactionItemRow = (transactionItem: TransactionWithOptionalSearchFields) => { return render( , ); @@ -292,7 +291,7 @@ describe('TransactionItemRowRBR', () => { await Onyx.merge(`${ONYXKEYS.COLLECTION.TRANSACTION_VIOLATIONS}${MOCK_TRANSACTION_ID}`, mockViolations); // When rendering the transaction item row - renderTransactionItemRow(mockTransaction, false); + renderTransactionItemRow(mockTransaction); await waitForBatchedUpdates(); // Then the RBR message should be displayed @@ -316,7 +315,7 @@ describe('TransactionItemRowRBR', () => { await Onyx.merge(`${ONYXKEYS.COLLECTION.TRANSACTION_VIOLATIONS}${MOCK_TRANSACTION_ID}`, mockViolations); // When rendering the transaction item row - renderTransactionItemRow(mockTransaction, false); + renderTransactionItemRow(mockTransaction); await waitForBatchedUpdates(); // Then the RBR message should be displayed with both violations @@ -346,7 +345,7 @@ describe('TransactionItemRowRBR', () => { await Onyx.merge(`${ONYXKEYS.COLLECTION.TRANSACTION_VIOLATIONS}${MOCK_TRANSACTION_ID}`, mockViolations); // When rendering the transaction item row - renderTransactionItemRow(mockTransaction, false); + renderTransactionItemRow(mockTransaction); await waitForBatchedUpdates(); // Then the RBR message should be displayed with missing merchant error and violations @@ -368,7 +367,7 @@ describe('TransactionItemRowRBR', () => { await Onyx.merge(`${ONYXKEYS.COLLECTION.REPORT}${MOCK_REPORT_ID}`, mockReport); // When rendering the transaction item row - renderTransactionItemRow(mockTransaction, false); + renderTransactionItemRow(mockTransaction); await waitForBatchedUpdates(); // Then the RBR message should be displayed with missing merchant error @@ -382,7 +381,7 @@ describe('TransactionItemRowRBR', () => { await Onyx.merge(`${ONYXKEYS.COLLECTION.TRANSACTION_VIOLATIONS}${MOCK_TRANSACTION_ID}`, []); // When rendering the transaction item row - renderTransactionItemRow(mockTransaction, false); + renderTransactionItemRow(mockTransaction); await waitForBatchedUpdates(); // Then the RBR message should not be displayed From cc70c4d4b76f1f6fa72da9fe4ba53e292f0168ed Mon Sep 17 00:00:00 2001 From: Jakub Szymczak Date: Fri, 11 Jul 2025 12:07:40 +0200 Subject: [PATCH 05/10] fix wrong desktop styles --- src/components/Checkbox.tsx | 5 ++ .../MoneyRequestReportTransactionItem.tsx | 7 +-- .../Search/TransactionListItem.tsx | 3 +- .../DataCells/ReceiptCell.tsx | 4 +- src/components/TransactionItemRow/index.tsx | 55 +++++++++---------- 5 files changed, 37 insertions(+), 37 deletions(-) diff --git a/src/components/Checkbox.tsx b/src/components/Checkbox.tsx index 2f105594cf98..6bda82e668cb 100644 --- a/src/components/Checkbox.tsx +++ b/src/components/Checkbox.tsx @@ -53,6 +53,9 @@ type CheckboxProps = Partial & { /** Whether the checkbox should be selected when pressing Enter key */ shouldSelectOnPressEnter?: boolean; + + /** Additional styles to add to checkbox wrapper */ + wrapperStyle?: StyleProp; }; function Checkbox( @@ -72,6 +75,7 @@ function Checkbox( accessibilityLabel, shouldStopMouseDownPropagation, shouldSelectOnPressEnter, + wrapperStyle, }: CheckboxProps, ref: ForwardedRef, ) { @@ -123,6 +127,7 @@ function Checkbox( aria-checked={isIndeterminate ? 'mixed' : isChecked} accessibilityLabel={accessibilityLabel} pressDimmingValue={1} + wrapperStyle={wrapperStyle} > {children ?? ( + { @@ -103,7 +100,7 @@ function MoneyRequestReportTransactionItem({ }} disabled={isTransactionPendingDelete(transaction)} ref={viewRef} - wrapperStyle={animatedHighlightStyle} + wrapperStyle={[animatedHighlightStyle]} > ({ isFocused && StyleUtils.getItemBackgroundColorStyle(!!item.isSelected, !!isFocused, !!item.isDisabled, theme.activeComponentBG, theme.hoverComponentBG), ]} onFocus={onFocus} - wrapperStyle={[styles.mb2, styles.mh5, animatedHighlightStyle]} + wrapperStyle={[styles.mb2, styles.mh5, styles.flex1, animatedHighlightStyle]} > {!isLargeScreenWidth && ( ({ onCheckboxPress={handleCheckboxPress} shouldUseNarrowLayout={!isLargeScreenWidth} columns={columns} - // isParentHovered={hovered} isActionLoading={isLoading ?? transactionItem.isActionLoading} isSelected={!!transactionItem.isSelected} dateColumnSize={dateColumnSize} diff --git a/src/components/TransactionItemRow/DataCells/ReceiptCell.tsx b/src/components/TransactionItemRow/DataCells/ReceiptCell.tsx index 91840db16dc1..8a38fb3c3bea 100644 --- a/src/components/TransactionItemRow/DataCells/ReceiptCell.tsx +++ b/src/components/TransactionItemRow/DataCells/ReceiptCell.tsx @@ -1,6 +1,7 @@ import {Str} from 'expensify-common'; import React from 'react'; import {View} from 'react-native'; +import type {ViewStyle} from 'react-native'; import {Receipt} from '@components/Icon/Expensicons'; import ReceiptImage from '@components/ReceiptImage'; import useStyleUtils from '@hooks/useStyleUtils'; @@ -13,7 +14,7 @@ import tryResolveUrlFromApiRoot from '@libs/tryResolveUrlFromApiRoot'; import variables from '@styles/variables'; import type {Transaction} from '@src/types/onyx'; -function ReceiptCell({transactionItem, isSelected}: {transactionItem: Transaction; isSelected: boolean}) { +function ReceiptCell({transactionItem, isSelected, style}: {transactionItem: Transaction; isSelected: boolean; style?: ViewStyle}) { const theme = useTheme(); const styles = useThemeStyles(); const StyleUtils = useStyleUtils(); @@ -35,6 +36,7 @@ function ReceiptCell({transactionItem, isSelected}: {transactionItem: Transactio StyleUtils.getBorderRadiusStyle(variables.componentBorderRadiusSmall), styles.overflowHidden, backgroundStyles, + style, ]} > {shouldShowCheckbox && ( - - { - onCheckboxPress(transactionItem.transactionID); - }} - accessibilityLabel={CONST.ROLE.CHECKBOX} - isChecked={isSelected} - /> - - )} - {/* CAN REDUCE */} - - { + onCheckboxPress(transactionItem.transactionID); + }} + accessibilityLabel={CONST.ROLE.CHECKBOX} + isChecked={isSelected} + style={styles.mr3} + wrapperStyle={styles.justifyContentCenter} /> - + )} + + - {/* CAN REDUCE */} - - { - onCheckboxPress(transactionItem.transactionID); - }} - accessibilityLabel={CONST.ROLE.CHECKBOX} - isChecked={isSelected} - /> - + { + onCheckboxPress(transactionItem.transactionID); + }} + accessibilityLabel={CONST.ROLE.CHECKBOX} + isChecked={isSelected} + style={styles.mr1} + wrapperStyle={styles.justifyContentCenter} + /> {columns?.map((column) => columnComponent[column])} Date: Tue, 15 Jul 2025 16:41:36 +0200 Subject: [PATCH 06/10] remove redundant views in Search components --- .../SearchPageHeader/SearchPageHeader.tsx | 11 ++--- .../Search/UserInfoAndActionButtonRow.tsx | 29 ++++++------ .../SelectionList/Search/UserInfoCell.tsx | 7 +-- .../Search/UserInfoCellsWithArrow.tsx | 46 +++++++++---------- 4 files changed, 45 insertions(+), 48 deletions(-) diff --git a/src/components/Search/SearchPageHeader/SearchPageHeader.tsx b/src/components/Search/SearchPageHeader/SearchPageHeader.tsx index f14f52478edc..f89be887c298 100644 --- a/src/components/Search/SearchPageHeader/SearchPageHeader.tsx +++ b/src/components/Search/SearchPageHeader/SearchPageHeader.tsx @@ -1,5 +1,4 @@ import React from 'react'; -import {View} from 'react-native'; import type {DropdownOption} from '@components/ButtonWithDropdownMenu/types'; import {useSearchContext} from '@components/Search/SearchContext'; import type {SearchQueryJSON} from '@components/Search/types'; @@ -37,12 +36,10 @@ function SearchPageHeader({ if (shouldUseNarrowLayout && isMobileSelectionModeEnabled) { return ( - - - + ); } diff --git a/src/components/SelectionList/Search/UserInfoAndActionButtonRow.tsx b/src/components/SelectionList/Search/UserInfoAndActionButtonRow.tsx index eb45c4ca05f6..a1ea917c6ceb 100644 --- a/src/components/SelectionList/Search/UserInfoAndActionButtonRow.tsx +++ b/src/components/SelectionList/Search/UserInfoAndActionButtonRow.tsx @@ -28,21 +28,20 @@ function UserInfoAndActionButtonRow({ return ( - - {shouldShowUserInfo && ( - - )} - + {shouldShowUserInfo && ( + + )} ; textStyle?: TextStyle; avatarStyle?: ViewStyle; }; -function UserInfoCell({avatar, accountID, displayName, avatarSize, textStyle, avatarStyle}: UserInfoCellProps) { +function UserInfoCell({avatar, accountID, displayName, avatarSize, containerStyle, textStyle, avatarStyle}: UserInfoCellProps) { const styles = useThemeStyles(); const {isLargeScreenWidth} = useResponsiveLayout(); @@ -28,7 +29,7 @@ function UserInfoCell({avatar, accountID, displayName, avatarSize, textStyle, av } return ( - + ; avatarSize?: AvatarSizeName; infoCellsTextStyle?: TextStyle; infoCellsAvatarStyle?: ViewStyle; @@ -40,17 +42,16 @@ function UserInfoCellsWithArrow({ } return ( - <> - - - + + {shouldShowToRecipient && ( <> - - - + )} - + ); } From ad134d711257330033320a48569f0aa956da0ea3 Mon Sep 17 00:00:00 2001 From: Jakub Szymczak Date: Tue, 15 Jul 2025 16:57:31 +0200 Subject: [PATCH 07/10] remove redundant views from MoneyRequestReportTransactionList --- .../MoneyRequestReportTransactionList.tsx | 196 +++++++++--------- 1 file changed, 98 insertions(+), 98 deletions(-) diff --git a/src/components/MoneyRequestReportView/MoneyRequestReportTransactionList.tsx b/src/components/MoneyRequestReportView/MoneyRequestReportTransactionList.tsx index 6f61697edf66..92cd9c141a97 100644 --- a/src/components/MoneyRequestReportView/MoneyRequestReportTransactionList.tsx +++ b/src/components/MoneyRequestReportView/MoneyRequestReportTransactionList.tsx @@ -221,111 +221,111 @@ function MoneyRequestReportTransactionList({ ); const listHorizontalPadding = styles.ph5; + + if (isEmptyTransactions) { + return ; + } + return ( <> - {!isEmptyTransactions ? ( - <> - {!shouldUseNarrowLayout && ( - - - { - if (selectedTransactionIDs.length !== 0) { - clearSelectedTransactions(true); - } else { - setSelectedTransactions(transactionsWithoutPendingDelete.map((t) => t.transactionID)); - } - }} - accessibilityLabel={CONST.ROLE.CHECKBOX} - isIndeterminate={selectedTransactionIDs.length > 0 && selectedTransactionIDs.length !== transactionsWithoutPendingDelete.length} - isChecked={selectedTransactionIDs.length > 0 && selectedTransactionIDs.length === transactionsWithoutPendingDelete.length} - /> - {isMediumScreenWidth && {translate('workspace.people.selectAll')}} - - {!isMediumScreenWidth && ( - { - if (!isSortableColumnName(selectedSortBy)) { - return; - } - - setSortConfig((prevState) => ({...prevState, sortBy: selectedSortBy, sortOrder: selectedSortOrder})); - }} - isIOUReport={isIOUReport(report)} - /> - )} - - )} - - {sortedTransactions.map((transaction) => { - return ( - - ); - })} - - {shouldShowBreakdown && ( - - {[ - {text: translate('cardTransactions.outOfPocket'), value: formattedOutOfPocketAmount}, - {text: translate('cardTransactions.companySpend'), value: formattedCompanySpendAmount}, - ].map(({text, value}) => ( - - - {text} - - - {value} - - - ))} - - )} - setIsModalVisible(false)} - shouldPreventScrollOnFocus - > - + + { - if (!isMobileSelectionModeEnabled) { - turnOnMobileSelectionMode(); + if (selectedTransactionIDs.length !== 0) { + clearSelectedTransactions(true); + } else { + setSelectedTransactions(transactionsWithoutPendingDelete.map((t) => t.transactionID)); + } + }} + accessibilityLabel={CONST.ROLE.CHECKBOX} + isIndeterminate={selectedTransactionIDs.length > 0 && selectedTransactionIDs.length !== transactionsWithoutPendingDelete.length} + isChecked={selectedTransactionIDs.length > 0 && selectedTransactionIDs.length === transactionsWithoutPendingDelete.length} + /> + {isMediumScreenWidth && {translate('workspace.people.selectAll')}} + + {!isMediumScreenWidth && ( + { + if (!isSortableColumnName(selectedSortBy)) { + return; } - toggleTransaction(selectedTransactionID); - setIsModalVisible(false); + + setSortConfig((prevState) => ({...prevState, sortBy: selectedSortBy, sortOrder: selectedSortOrder})); }} + isIOUReport={isIOUReport(report)} /> - - - ) : ( - + )} + )} + + {sortedTransactions.map((transaction) => { + return ( + + ); + })} + + {shouldShowBreakdown && ( + + {[ + {text: translate('cardTransactions.outOfPocket'), value: formattedOutOfPocketAmount}, + {text: translate('cardTransactions.companySpend'), value: formattedCompanySpendAmount}, + ].map(({text, value}) => ( + + + {text} + + + {value} + + + ))} + + )} + setIsModalVisible(false)} + shouldPreventScrollOnFocus + > + { + if (!isMobileSelectionModeEnabled) { + turnOnMobileSelectionMode(); + } + toggleTransaction(selectedTransactionID); + setIsModalVisible(false); + }} + /> + + Date: Wed, 16 Jul 2025 10:08:35 +0200 Subject: [PATCH 08/10] add userSelect none to block unwanted text selection on long press --- .../MoneyRequestReportTransactionItem.tsx | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/components/MoneyRequestReportView/MoneyRequestReportTransactionItem.tsx b/src/components/MoneyRequestReportView/MoneyRequestReportTransactionItem.tsx index 29d084c72082..39480eb58c4d 100644 --- a/src/components/MoneyRequestReportView/MoneyRequestReportTransactionItem.tsx +++ b/src/components/MoneyRequestReportView/MoneyRequestReportTransactionItem.tsx @@ -100,7 +100,7 @@ function MoneyRequestReportTransactionItem({ }} disabled={isTransactionPendingDelete(transaction)} ref={viewRef} - wrapperStyle={[animatedHighlightStyle]} + wrapperStyle={[animatedHighlightStyle, styles.userSelectNone]} > Date: Wed, 16 Jul 2025 12:33:33 +0200 Subject: [PATCH 09/10] fix PR comments --- .../MoneyRequestReportTotalSpend.tsx | 48 +++++++++++++++++ .../MoneyRequestReportTransactionItem.tsx | 3 ++ .../MoneyRequestReportTransactionList.tsx | 52 ++++++++++--------- .../Search/UserInfoAndActionButtonRow.tsx | 2 +- 4 files changed, 79 insertions(+), 26 deletions(-) create mode 100644 src/components/MoneyRequestReportView/MoneyRequestReportTotalSpend.tsx diff --git a/src/components/MoneyRequestReportView/MoneyRequestReportTotalSpend.tsx b/src/components/MoneyRequestReportView/MoneyRequestReportTotalSpend.tsx new file mode 100644 index 000000000000..d56428ccf3c2 --- /dev/null +++ b/src/components/MoneyRequestReportView/MoneyRequestReportTotalSpend.tsx @@ -0,0 +1,48 @@ +import React from 'react'; +import {View} from 'react-native'; +import Animated, {FadeIn, FadeOut} from 'react-native-reanimated'; +import Text from '@components/Text'; +import useLocalize from '@hooks/useLocalize'; +import useResponsiveLayout from '@hooks/useResponsiveLayout'; +import useThemeStyles from '@hooks/useThemeStyles'; +import {convertToDisplayString} from '@libs/CurrencyUtils'; +import type * as OnyxTypes from '@src/types/onyx'; + +type MoneyRequestReportTotalSpendProps = { + hasComments: boolean; + isLoadingReportActions: boolean; + isEmptyTransactions: boolean; + totalDisplaySpend: number; + report: OnyxTypes.Report; + hasPendingAction: boolean; +}; + +function MoneyRequestReportTotalSpend({hasComments, isLoadingReportActions, isEmptyTransactions, totalDisplaySpend, report, hasPendingAction}: MoneyRequestReportTotalSpendProps) { + const styles = useThemeStyles(); + const {translate} = useLocalize(); + const {shouldUseNarrowLayout} = useResponsiveLayout(); + + return ( + + + {hasComments || isLoadingReportActions ? translate('common.comments') : ''} + + {!isEmptyTransactions && ( + + {translate('common.total')} + + {convertToDisplayString(totalDisplaySpend, report?.currency)} + + + )} + + ); +} + +MoneyRequestReportTotalSpend.displayName = 'MoneyRequestReportTotalSpend'; + +export default MoneyRequestReportTotalSpend; diff --git a/src/components/MoneyRequestReportView/MoneyRequestReportTransactionItem.tsx b/src/components/MoneyRequestReportView/MoneyRequestReportTransactionItem.tsx index 39480eb58c4d..bb1b22514344 100644 --- a/src/components/MoneyRequestReportView/MoneyRequestReportTransactionItem.tsx +++ b/src/components/MoneyRequestReportView/MoneyRequestReportTransactionItem.tsx @@ -63,6 +63,7 @@ function MoneyRequestReportTransactionItem({ const viewRef = useRef(null); + // This useEffect scrolls to this transaction when it is newly added to the report useEffect(() => { if (!transaction.shouldBeHighlighted || !scrollToNewTransaction) { return; @@ -120,4 +121,6 @@ function MoneyRequestReportTransactionItem({ ); } +MoneyRequestReportTransactionItem.displayName = 'MoneyRequestReportTransactionItem'; + export default MoneyRequestReportTransactionItem; diff --git a/src/components/MoneyRequestReportView/MoneyRequestReportTransactionList.tsx b/src/components/MoneyRequestReportView/MoneyRequestReportTransactionList.tsx index 92cd9c141a97..5fb04f36f229 100644 --- a/src/components/MoneyRequestReportView/MoneyRequestReportTransactionList.tsx +++ b/src/components/MoneyRequestReportView/MoneyRequestReportTransactionList.tsx @@ -2,7 +2,6 @@ import {useFocusEffect} from '@react-navigation/native'; import isEmpty from 'lodash/isEmpty'; import React, {memo, useCallback, useMemo, useState} from 'react'; import {View} from 'react-native'; -import Animated, {FadeIn, FadeOut} from 'react-native-reanimated'; import type {TupleToUnion} from 'type-fest'; import Checkbox from '@components/Checkbox'; import * as Expensicons from '@components/Icon/Expensicons'; @@ -34,6 +33,7 @@ import NAVIGATORS from '@src/NAVIGATORS'; import ROUTES from '@src/ROUTES'; import type * as OnyxTypes from '@src/types/onyx'; import MoneyRequestReportTableHeader from './MoneyRequestReportTableHeader'; +import MoneyRequestReportTotalSpend from './MoneyRequestReportTotalSpend'; import MoneyRequestReportTransactionItem from './MoneyRequestReportTransactionItem'; import SearchMoneyRequestReportEmptyState from './SearchMoneyRequestReportEmptyState'; @@ -110,10 +110,9 @@ function MoneyRequestReportTransactionList({ const shouldShowBreakdown = !!nonReimbursableSpend && !!reimbursableSpend; const transactionsWithoutPendingDelete = useMemo(() => transactions.filter((t) => !isTransactionPendingDelete(t)), [transactions]); - const pendingActionsOpacity = useMemo(() => { - const pendingAction = transactions.some(getTransactionPendingAction); - return pendingAction && styles.opacitySemiTransparent; - }, [styles.opacitySemiTransparent, transactions]); + const hasPendingAction = useMemo(() => { + return transactions.some(getTransactionPendingAction); + }, [transactions]); const {selectedTransactionIDs, setSelectedTransactions, clearSelectedTransactions} = useSearchContext(); const isMobileSelectionModeEnabled = useMobileSelectionMode(); @@ -223,7 +222,19 @@ function MoneyRequestReportTransactionList({ const listHorizontalPadding = styles.ph5; if (isEmptyTransactions) { - return ; + return ( + <> + + + + ); } return ( @@ -279,7 +290,8 @@ function MoneyRequestReportTransactionList({ dateColumnSize={dateColumnSize} amountColumnSize={amountColumnSize} taxAmountColumnSize={taxAmountColumnSize} - scrollToNewTransaction={scrollToNewTransaction} + // if we add few new transactions, then we need to scroll to the first one + scrollToNewTransaction={transaction.transactionID === newTransactions?.at(0)?.transactionID ? scrollToNewTransaction : undefined} /> ); })} @@ -307,6 +319,14 @@ function MoneyRequestReportTransactionList({ ))} )} + - - - - {hasComments || isLoadingReportActions ? translate('common.comments') : ''} - - {!isEmptyTransactions && ( - - {translate('common.total')} - - {convertToDisplayString(totalDisplaySpend, report?.currency)} - - - )} - ); } diff --git a/src/components/SelectionList/Search/UserInfoAndActionButtonRow.tsx b/src/components/SelectionList/Search/UserInfoAndActionButtonRow.tsx index 258b82c145de..14e321613725 100644 --- a/src/components/SelectionList/Search/UserInfoAndActionButtonRow.tsx +++ b/src/components/SelectionList/Search/UserInfoAndActionButtonRow.tsx @@ -36,7 +36,7 @@ function UserInfoAndActionButtonRow({ participantToDisplayName={participantToDisplayName} participantTo={item?.to} avatarSize={CONST.AVATAR_SIZE.MID_SUBSCRIPT} - style={[styles.flex1, styles.flexRow, styles.alignItemsCenter, styles.gap2]} + style={[styles.flexRow, styles.alignItemsCenter, styles.gap2]} infoCellsTextStyle={{...styles.textMicroBold, lineHeight: 14}} infoCellsAvatarStyle={styles.pr1} fromRecipientStyle={!shouldShowToRecipient ? styles.mw100 : {}} From 2e663de4bdae4f129cd9b6811a3d8a6049db7d81 Mon Sep 17 00:00:00 2001 From: Jakub Szymczak Date: Fri, 25 Jul 2025 13:04:38 +0200 Subject: [PATCH 10/10] add explanatory comments --- .../MoneyRequestReportTotalSpend.tsx | 13 ++++++++++++- .../MoneyRequestReportTransactionItem.tsx | 19 +++++++++++++++++++ 2 files changed, 31 insertions(+), 1 deletion(-) diff --git a/src/components/MoneyRequestReportView/MoneyRequestReportTotalSpend.tsx b/src/components/MoneyRequestReportView/MoneyRequestReportTotalSpend.tsx index bf94059f4196..db4573ba39e6 100644 --- a/src/components/MoneyRequestReportView/MoneyRequestReportTotalSpend.tsx +++ b/src/components/MoneyRequestReportView/MoneyRequestReportTotalSpend.tsx @@ -10,11 +10,22 @@ import {convertToDisplayString} from '@libs/CurrencyUtils'; import type * as OnyxTypes from '@src/types/onyx'; type MoneyRequestReportTotalSpendProps = { + /** Report for which the total spend is being displayed */ + report: OnyxTypes.Report; + + /** Whether the report has any comments */ hasComments: boolean; + + /** Whether the report is loading report actions */ isLoadingReportActions: boolean; + + /** Whether the report has any transactions */ isEmptyTransactions: boolean; + + /** The total display spend of the report */ totalDisplaySpend: number; - report: OnyxTypes.Report; + + /** Whether the report has any pending actions */ hasPendingAction: boolean; }; diff --git a/src/components/MoneyRequestReportView/MoneyRequestReportTransactionItem.tsx b/src/components/MoneyRequestReportView/MoneyRequestReportTransactionItem.tsx index bb1b22514344..b4b7473e2b8f 100644 --- a/src/components/MoneyRequestReportView/MoneyRequestReportTransactionItem.tsx +++ b/src/components/MoneyRequestReportView/MoneyRequestReportTransactionItem.tsx @@ -29,15 +29,34 @@ const allReportColumns = [ ]; type MoneyRequestReportTransactionItemProps = { + /** The transaction that is being displayed */ transaction: TransactionWithOptionalHighlight; + + /** Whether the mobile selection mode is enabled */ isSelectionModeEnabled: boolean; + + /** Callback function triggered upon pressing a transaction checkbox. */ toggleTransaction: (transactionID: string) => void; + + /** Callback function triggered upon pressing a transaction. */ handleOnPress: (transactionID: string) => void; + + /** Callback function triggered upon long pressing a transaction. */ handleLongPress: (transactionID: string) => void; + + /** Whether the transaction is selected */ isSelected: boolean; + + /** The size of the date column */ dateColumnSize: TableColumnSize; + + /** The size of the amount column */ amountColumnSize: TableColumnSize; + + /** The size of the tax amount column */ taxAmountColumnSize: TableColumnSize; + + /** Callback function that scrolls to this transaction in case it is newly added */ scrollToNewTransaction?: (offset: number) => void; };