From c1afaca6da097ad5640e31d61873c45568ca32b5 Mon Sep 17 00:00:00 2001 From: nkdengineer Date: Thu, 1 Aug 2024 12:57:43 +0700 Subject: [PATCH 1/8] fix back button in the selection mode --- src/CONFIG.ts | 2 +- src/components/Search/SearchContext.tsx | 8 ++++++-- src/components/Search/index.tsx | 22 ++++++++------------- src/components/Search/types.ts | 3 ++- src/pages/Search/SearchPageBottomTab.tsx | 25 ++++++++++++++++++------ 5 files changed, 36 insertions(+), 24 deletions(-) diff --git a/src/CONFIG.ts b/src/CONFIG.ts index 8800cc907588..ec7f4b39af91 100644 --- a/src/CONFIG.ts +++ b/src/CONFIG.ts @@ -96,5 +96,5 @@ export default { IOS_CLIENT_ID: '921154746561-s3uqn2oe4m85tufi6mqflbfbuajrm2i3.apps.googleusercontent.com', }, GCP_GEOLOCATION_API_KEY: googleGeolocationAPIKey, - USE_REACT_STRICT_MODE: true, + USE_REACT_STRICT_MODE: false, } as const; diff --git a/src/components/Search/SearchContext.tsx b/src/components/Search/SearchContext.tsx index 3408ffbc4803..266d3563dea2 100644 --- a/src/components/Search/SearchContext.tsx +++ b/src/components/Search/SearchContext.tsx @@ -4,6 +4,7 @@ import type {SearchContext, SelectedTransactions} from './types'; const defaultSearchContext = { currentSearchHash: -1, + shouldTurnOffSelectionMode: false, selectedTransactions: {}, setCurrentSearchHash: () => {}, setSelectedTransactions: () => {}, @@ -13,9 +14,10 @@ const defaultSearchContext = { const Context = React.createContext(defaultSearchContext); function SearchContextProvider({children}: ChildrenProps) { - const [searchContextData, setSearchContextData] = useState>({ + const [searchContextData, setSearchContextData] = useState>({ currentSearchHash: defaultSearchContext.currentSearchHash, selectedTransactions: defaultSearchContext.selectedTransactions, + shouldTurnOffSelectionMode: false, }); const setCurrentSearchHash = useCallback((searchHash: number) => { @@ -29,16 +31,18 @@ function SearchContextProvider({children}: ChildrenProps) { setSearchContextData((prevState) => ({ ...prevState, selectedTransactions, + shouldTurnOffSelectionMode: false, })); }, []); const clearSelectedTransactions = useCallback( - (searchHash?: number) => { + (searchHash?: number, shouldTurnOffSelectionMode = false) => { if (searchHash === searchContextData.currentSearchHash) { return; } setSearchContextData((prevState) => ({ ...prevState, + shouldTurnOffSelectionMode, selectedTransactions: {}, })); }, diff --git a/src/components/Search/index.tsx b/src/components/Search/index.tsx index c2d7b52fa3e9..c3d2455196d1 100644 --- a/src/components/Search/index.tsx +++ b/src/components/Search/index.tsx @@ -79,7 +79,7 @@ function Search({queryJSON, policyIDs, isCustomQuery}: SearchProps) { const {isLargeScreenWidth, isSmallScreenWidth} = useWindowDimensions(); const navigation = useNavigation>(); const lastSearchResultsRef = useRef>(); - const {setCurrentSearchHash, setSelectedTransactions, selectedTransactions, clearSelectedTransactions} = useSearchContext(); + const {setCurrentSearchHash, setSelectedTransactions, selectedTransactions, clearSelectedTransactions, shouldTurnOffSelectionMode} = useSearchContext(); const [selectionMode] = useOnyx(ONYXKEYS.MOBILE_SELECTION_MODE); const [offset, setOffset] = useState(0); const [offlineModalVisible, setOfflineModalVisible] = useState(false); @@ -101,6 +101,13 @@ function Search({queryJSON, policyIDs, isCustomQuery}: SearchProps) { // eslint-disable-next-line react-compiler/react-compiler, react-hooks/exhaustive-deps }, [hash]); + useEffect(() => { + const selectedKeys = Object.keys(selectedTransactions).filter((key) => selectedTransactions[key]); + if (selectedKeys.length === 0 && selectionMode?.isEnabled && shouldTurnOffSelectionMode) { + turnOffMobileSelectionMode(); + } + }, [selectedTransactions, selectionMode?.isEnabled, shouldTurnOffSelectionMode]); + useEffect(() => { const selectedKeys = Object.keys(selectedTransactions).filter((key) => selectedTransactions[key]); if (!isSmallScreenWidth) { @@ -134,19 +141,6 @@ function Search({queryJSON, policyIDs, isCustomQuery}: SearchProps) { setDeleteExpensesConfirmModalVisible(true); }; - useEffect(() => { - const selectedKeys = Object.keys(selectedTransactions).filter((key) => selectedTransactions[key]); - if (!isSmallScreenWidth) { - if (selectedKeys.length === 0) { - turnOffMobileSelectionMode(); - } - return; - } - if (selectedKeys.length > 0 && !selectionMode?.isEnabled) { - turnOnMobileSelectionMode(); - } - }, [isSmallScreenWidth, selectedTransactions, selectionMode?.isEnabled]); - const getItemHeight = useCallback( (item: TransactionListItemType | ReportListItemType) => { if (SearchUtils.isTransactionListItemType(item)) { diff --git a/src/components/Search/types.ts b/src/components/Search/types.ts index 52adf28166f7..1c90b7bc76c4 100644 --- a/src/components/Search/types.ts +++ b/src/components/Search/types.ts @@ -31,7 +31,8 @@ type SearchContext = { selectedTransactions: SelectedTransactions; setCurrentSearchHash: (hash: number) => void; setSelectedTransactions: (selectedTransactions: SelectedTransactions) => void; - clearSelectedTransactions: (hash?: number) => void; + clearSelectedTransactions: (hash?: number, shouldTurnOffSelectionMode?: boolean) => void; + shouldTurnOffSelectionMode: boolean; }; type ASTNode = { diff --git a/src/pages/Search/SearchPageBottomTab.tsx b/src/pages/Search/SearchPageBottomTab.tsx index f80aed9b8c28..93bab08b1ec5 100644 --- a/src/pages/Search/SearchPageBottomTab.tsx +++ b/src/pages/Search/SearchPageBottomTab.tsx @@ -1,4 +1,5 @@ -import React, {useMemo} from 'react'; +import React, {useCallback, useEffect, useMemo} from 'react'; +import {BackHandler} from 'react-native'; import {useOnyx} from 'react-native-onyx'; import FullPageNotFoundView from '@components/BlockingViews/FullPageNotFoundView'; import HeaderWithBackButton from '@components/HeaderWithBackButton'; @@ -9,7 +10,6 @@ import useActiveCentralPaneRoute from '@hooks/useActiveCentralPaneRoute'; import useLocalize from '@hooks/useLocalize'; import useResponsiveLayout from '@hooks/useResponsiveLayout'; import useThemeStyles from '@hooks/useThemeStyles'; -import {turnOffMobileSelectionMode} from '@libs/actions/MobileSelectionMode'; import Navigation from '@libs/Navigation/Navigation'; import type {AuthScreensParamList} from '@libs/Navigation/types'; import {buildSearchQueryJSON} from '@libs/SearchUtils'; @@ -28,6 +28,22 @@ function SearchPageBottomTab() { const {clearSelectedTransactions} = useSearchContext(); const [selectionMode] = useOnyx(ONYXKEYS.MOBILE_SELECTION_MODE); + const handleBackButtonPress = useCallback(() => { + if (!selectionMode?.isEnabled) { + return false; + } + if (selectionMode?.isEnabled) { + clearSelectedTransactions(undefined, true); + return true; + } + }, [selectionMode, clearSelectedTransactions]); + + useEffect(() => { + const backHandler = BackHandler.addEventListener('hardwareBackPress', handleBackButtonPress); + + return () => backHandler.remove(); + }, [handleBackButtonPress]); + const {queryJSON, policyIDs, isCustomQuery} = useMemo(() => { if (!activeCentralPaneRoute || activeCentralPaneRoute.name !== SCREENS.SEARCH.CENTRAL_PANE) { return {queryJSON: undefined, policyIDs: undefined}; @@ -71,10 +87,7 @@ function SearchPageBottomTab() { ) : ( { - clearSelectedTransactions(); - turnOffMobileSelectionMode(); - }} + onBackButtonPress={handleBackButtonPress} /> )} {shouldUseNarrowLayout && queryJSON && ( From 53ea108a4e43a1d0dbe635c74dbc3fe835972db1 Mon Sep 17 00:00:00 2001 From: nkdengineer Date: Wed, 7 Aug 2024 10:17:20 +0700 Subject: [PATCH 2/8] remove useless code --- src/CONFIG.ts | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/CONFIG.ts b/src/CONFIG.ts index ec7f4b39af91..8800cc907588 100644 --- a/src/CONFIG.ts +++ b/src/CONFIG.ts @@ -96,5 +96,5 @@ export default { IOS_CLIENT_ID: '921154746561-s3uqn2oe4m85tufi6mqflbfbuajrm2i3.apps.googleusercontent.com', }, GCP_GEOLOCATION_API_KEY: googleGeolocationAPIKey, - USE_REACT_STRICT_MODE: false, + USE_REACT_STRICT_MODE: true, } as const; From 3b9161571499abbb5686253ba967bf00f3876829 Mon Sep 17 00:00:00 2001 From: nkdengineer Date: Wed, 2 Oct 2024 16:31:25 +0700 Subject: [PATCH 3/8] implement for mWeb --- src/pages/Search/SearchPage.tsx | 31 +++++++++++++++++-- src/pages/Search/SearchPageBottomTab.tsx | 17 ++++------ .../useHandleBackButton/index.android.ts | 11 +++++++ src/pages/Search/useHandleBackButton/index.ts | 4 +++ src/pages/Search/useHandleBackButton/type.ts | 3 ++ 5 files changed, 53 insertions(+), 13 deletions(-) create mode 100644 src/pages/Search/useHandleBackButton/index.android.ts create mode 100644 src/pages/Search/useHandleBackButton/index.ts create mode 100644 src/pages/Search/useHandleBackButton/type.ts diff --git a/src/pages/Search/SearchPage.tsx b/src/pages/Search/SearchPage.tsx index 7f82aaabfc6f..8400301d55dd 100644 --- a/src/pages/Search/SearchPage.tsx +++ b/src/pages/Search/SearchPage.tsx @@ -1,15 +1,19 @@ -import type {StackScreenProps} from '@react-navigation/stack'; -import React, {useMemo} from 'react'; +import {useNavigation} from '@react-navigation/native'; +import type {StackNavigationProp, StackScreenProps} from '@react-navigation/stack'; +import React, {useEffect, useMemo, useRef} from 'react'; import FullPageNotFoundView from '@components/BlockingViews/FullPageNotFoundView'; import ScreenWrapper from '@components/ScreenWrapper'; import Search from '@components/Search'; +import {useSearchContext} from '@components/Search/SearchContext'; import SearchPageHeader from '@components/Search/SearchPageHeader'; import SearchStatusBar from '@components/Search/SearchStatusBar'; +import useMobileSelectionMode from '@hooks/useMobileSelectionMode'; import useResponsiveLayout from '@hooks/useResponsiveLayout'; import useThemeStyles from '@hooks/useThemeStyles'; import Navigation from '@libs/Navigation/Navigation'; import type {AuthScreensParamList} from '@libs/Navigation/types'; import * as SearchUtils from '@libs/SearchUtils'; +import CONST from '@src/CONST'; import ROUTES from '@src/ROUTES'; import type SCREENS from '@src/SCREENS'; @@ -19,10 +23,33 @@ function SearchPage({route}: SearchPageProps) { const {shouldUseNarrowLayout} = useResponsiveLayout(); const styles = useThemeStyles(); const {q} = route.params; + const navigation = useNavigation>(); + const {clearSelectedTransactions} = useSearchContext(); + const {selectionMode} = useMobileSelectionMode(); + const clearSelectedTransactionsRef = useRef(clearSelectedTransactions); + const selectionModeRef = useRef(selectionMode); + selectionModeRef.current = selectionMode; + clearSelectedTransactionsRef.current = clearSelectedTransactions; const queryJSON = useMemo(() => SearchUtils.buildSearchQueryJSON(q), [q]); const handleOnBackButtonPress = () => Navigation.goBack(ROUTES.SEARCH_CENTRAL_PANE.getRoute({query: SearchUtils.buildCannedSearchQuery()})); + useEffect(() => { + navigation.addListener('beforeRemove', (e) => { + if (!selectionModeRef?.current?.isEnabled) { + navigation.dispatch(e.data.action); + return; + } + e.preventDefault(); + // When the navigation type is not reset that means we're not using the back button browser to navigate + // So we should still dispatch the action to go to the target page + if (e.data.action.type !== CONST.NAVIGATION_ACTIONS.RESET) { + navigation.dispatch(e.data.action); + } + clearSelectedTransactionsRef?.current?.(undefined, true); + }); + }, [navigation]); + // On small screens this page is not displayed, the configuration is in the file: src/libs/Navigation/AppNavigator/createCustomStackNavigator/index.tsx // To avoid calling hooks in the Search component when this page isn't visible, we return null here. if (shouldUseNarrowLayout) { diff --git a/src/pages/Search/SearchPageBottomTab.tsx b/src/pages/Search/SearchPageBottomTab.tsx index c3bf019e6542..70d846b6559f 100644 --- a/src/pages/Search/SearchPageBottomTab.tsx +++ b/src/pages/Search/SearchPageBottomTab.tsx @@ -1,5 +1,5 @@ -import React, {useCallback, useEffect} from 'react'; -import {BackHandler, View} from 'react-native'; +import React, {useCallback} from 'react'; +import {View} from 'react-native'; import {useOnyx} from 'react-native-onyx'; import Animated, {clamp, useAnimatedScrollHandler, useAnimatedStyle, useSharedValue, withTiming} from 'react-native-reanimated'; import FullPageNotFoundView from '@components/BlockingViews/FullPageNotFoundView'; @@ -22,6 +22,7 @@ import ROUTES from '@src/ROUTES'; import SCREENS from '@src/SCREENS'; import SearchSelectionModeHeader from './SearchSelectionModeHeader'; import SearchTypeMenu from './SearchTypeMenu'; +import useHandleBackButton from './useHandleBackButton'; const TOO_CLOSE_TO_TOP_DISTANCE = 10; const TOO_CLOSE_TO_BOTTOM_DISTANCE = 10; @@ -40,17 +41,11 @@ function SearchPageBottomTab() { if (!selectionMode?.isEnabled) { return false; } - if (selectionMode?.isEnabled) { - clearSelectedTransactions(undefined, true); - return true; - } + clearSelectedTransactions(undefined, true); + return true; }, [selectionMode, clearSelectedTransactions]); - useEffect(() => { - const backHandler = BackHandler.addEventListener('hardwareBackPress', handleBackButtonPress); - - return () => backHandler.remove(); - }, [handleBackButtonPress]); + useHandleBackButton(handleBackButtonPress); const scrollOffset = useSharedValue(0); const topBarOffset = useSharedValue(variables.searchHeaderHeight); diff --git a/src/pages/Search/useHandleBackButton/index.android.ts b/src/pages/Search/useHandleBackButton/index.android.ts new file mode 100644 index 000000000000..69c36e8e06d4 --- /dev/null +++ b/src/pages/Search/useHandleBackButton/index.android.ts @@ -0,0 +1,11 @@ +import {useEffect} from 'react'; +import {BackHandler} from 'react-native'; +import type UseHandleBackButtonCallback from './type'; + +export default function useHandleBackButton(callback: UseHandleBackButtonCallback) { + useEffect(() => { + const backHandler = BackHandler.addEventListener('hardwareBackPress', callback); + + return () => backHandler.remove(); + }, [callback]); +} diff --git a/src/pages/Search/useHandleBackButton/index.ts b/src/pages/Search/useHandleBackButton/index.ts new file mode 100644 index 000000000000..691e2dd0378b --- /dev/null +++ b/src/pages/Search/useHandleBackButton/index.ts @@ -0,0 +1,4 @@ +import type UseHandleBackButtonCallback from './type'; + +// eslint-disable-next-line @typescript-eslint/no-unused-vars +export default function useHandleBackButton(_callback: UseHandleBackButtonCallback) {} diff --git a/src/pages/Search/useHandleBackButton/type.ts b/src/pages/Search/useHandleBackButton/type.ts new file mode 100644 index 000000000000..e32f48555bde --- /dev/null +++ b/src/pages/Search/useHandleBackButton/type.ts @@ -0,0 +1,3 @@ +type UseHandleBackButtonCallback = () => boolean; + +export default UseHandleBackButtonCallback; From 9201fd52691b9e620482ca357b06fc1116e9128e Mon Sep 17 00:00:00 2001 From: nkdengineer Date: Tue, 12 Nov 2024 23:48:44 +0700 Subject: [PATCH 4/8] remove change on web --- src/pages/Search/SearchPage.tsx | 31 ++----------------------------- 1 file changed, 2 insertions(+), 29 deletions(-) diff --git a/src/pages/Search/SearchPage.tsx b/src/pages/Search/SearchPage.tsx index f7a4b7dbe017..4c68d84a0520 100644 --- a/src/pages/Search/SearchPage.tsx +++ b/src/pages/Search/SearchPage.tsx @@ -1,19 +1,15 @@ -import {useNavigation} from '@react-navigation/native'; -import type {StackNavigationProp, StackScreenProps} from '@react-navigation/stack'; -import React, {useEffect, useMemo, useRef} from 'react'; +import type {StackScreenProps} from '@react-navigation/stack'; +import React, {useMemo} from 'react'; import FullPageNotFoundView from '@components/BlockingViews/FullPageNotFoundView'; import ScreenWrapper from '@components/ScreenWrapper'; import Search from '@components/Search'; -import {useSearchContext} from '@components/Search/SearchContext'; import SearchPageHeader from '@components/Search/SearchPageHeader'; import SearchStatusBar from '@components/Search/SearchStatusBar'; -import useMobileSelectionMode from '@hooks/useMobileSelectionMode'; import useResponsiveLayout from '@hooks/useResponsiveLayout'; import useThemeStyles from '@hooks/useThemeStyles'; import Navigation from '@libs/Navigation/Navigation'; import type {AuthScreensParamList} from '@libs/Navigation/types'; import * as SearchQueryUtils from '@libs/SearchQueryUtils'; -import CONST from '@src/CONST'; import ROUTES from '@src/ROUTES'; import type SCREENS from '@src/SCREENS'; @@ -23,33 +19,10 @@ function SearchPage({route}: SearchPageProps) { const {shouldUseNarrowLayout} = useResponsiveLayout(); const styles = useThemeStyles(); const {q} = route.params; - const navigation = useNavigation>(); - const {clearSelectedTransactions} = useSearchContext(); - const {selectionMode} = useMobileSelectionMode(); - const clearSelectedTransactionsRef = useRef(clearSelectedTransactions); - const selectionModeRef = useRef(selectionMode); - selectionModeRef.current = selectionMode; - clearSelectedTransactionsRef.current = clearSelectedTransactions; const queryJSON = useMemo(() => SearchQueryUtils.buildSearchQueryJSON(q), [q]); const handleOnBackButtonPress = () => Navigation.goBack(ROUTES.SEARCH_CENTRAL_PANE.getRoute({query: SearchQueryUtils.buildCannedSearchQuery()})); - useEffect(() => { - navigation.addListener('beforeRemove', (e) => { - if (!selectionModeRef?.current?.isEnabled) { - navigation.dispatch(e.data.action); - return; - } - e.preventDefault(); - // When the navigation type is not reset that means we're not using the back button browser to navigate - // So we should still dispatch the action to go to the target page - if (e.data.action.type !== CONST.NAVIGATION_ACTIONS.RESET) { - navigation.dispatch(e.data.action); - } - clearSelectedTransactionsRef?.current?.(undefined, true); - }); - }, [navigation]); - // On small screens this page is not displayed, the configuration is in the file: src/libs/Navigation/AppNavigator/createCustomStackNavigator/index.tsx // To avoid calling hooks in the Search component when this page isn't visible, we return null here. if (shouldUseNarrowLayout) { From 76c3a616062d0ba04c90da085a9ab598c69aaef5 Mon Sep 17 00:00:00 2001 From: nkdengineer Date: Mon, 23 Dec 2024 11:56:15 +0700 Subject: [PATCH 5/8] fix lint --- src/components/Search/SearchContext.tsx | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/components/Search/SearchContext.tsx b/src/components/Search/SearchContext.tsx index c47a23794bfa..8f7814597f31 100644 --- a/src/components/Search/SearchContext.tsx +++ b/src/components/Search/SearchContext.tsx @@ -30,7 +30,7 @@ function getReportsFromSelectedTransactions(data: TransactionListItemType[] | Re isMoneyRequestReport(item) && item?.transactions?.every((transaction: {keyForList: string | number}) => selectedTransactions[transaction.keyForList]?.isSelected), ) - .map((item) => ({reportID: item.reportID, action: item.action ?? CONST.SEARCH.ACTION_TYPES.VIEW, total: item.total ?? 0, policyID: item.policyID ?? ''})); + .map((item) => ({reportID: item.reportID, action: item.action ?? CONST.SEARCH.ACTION_TYPES.VIEW, total: item.total ?? CONST.DEFAULT_NUMBER_ID, policyID: item.policyID ?? ''})); } function SearchContextProvider({children}: ChildrenProps) { From 18cbbe9042b0bdb25f742bffb4de5d1c427f8b05 Mon Sep 17 00:00:00 2001 From: nkdengineer Date: Fri, 24 Jan 2025 11:22:17 +0700 Subject: [PATCH 6/8] fix lint --- src/components/Search/SearchContext.tsx | 6 +-- src/components/Search/index.tsx | 67 ++++++++++++++----------- 2 files changed, 41 insertions(+), 32 deletions(-) diff --git a/src/components/Search/SearchContext.tsx b/src/components/Search/SearchContext.tsx index 8f7814597f31..73091abf045b 100644 --- a/src/components/Search/SearchContext.tsx +++ b/src/components/Search/SearchContext.tsx @@ -1,7 +1,7 @@ import React, {useCallback, useContext, useMemo, useState} from 'react'; import type {ReportActionListItemType, ReportListItemType, TransactionListItemType} from '@components/SelectionList/types'; import {isMoneyRequestReport} from '@libs/ReportUtils'; -import * as SearchUIUtils from '@libs/SearchUIUtils'; +import {isReportListItemType} from '@libs/SearchUIUtils'; import CONST from '@src/CONST'; import type ChildrenProps from '@src/types/utils/ChildrenProps'; import type {SearchContext, SelectedTransactions} from './types'; @@ -26,11 +26,11 @@ function getReportsFromSelectedTransactions(data: TransactionListItemType[] | Re return (data ?? []) .filter( (item): item is ReportListItemType => - SearchUIUtils.isReportListItemType(item) && + isReportListItemType(item) && isMoneyRequestReport(item) && item?.transactions?.every((transaction: {keyForList: string | number}) => selectedTransactions[transaction.keyForList]?.isSelected), ) - .map((item) => ({reportID: item.reportID, action: item.action ?? CONST.SEARCH.ACTION_TYPES.VIEW, total: item.total ?? CONST.DEFAULT_NUMBER_ID, policyID: item.policyID ?? ''})); + .map((item) => ({reportID: item.reportID, action: item.action ?? CONST.SEARCH.ACTION_TYPES.VIEW, total: item.total ?? CONST.DEFAULT_NUMBER_ID, policyID: item.policyID})); } function SearchContextProvider({children}: ChildrenProps) { diff --git a/src/components/Search/index.tsx b/src/components/Search/index.tsx index 6d20b14ae62c..1f5443b11a58 100644 --- a/src/components/Search/index.tsx +++ b/src/components/Search/index.tsx @@ -15,16 +15,25 @@ import useResponsiveLayout from '@hooks/useResponsiveLayout'; import useSearchHighlightAndScroll from '@hooks/useSearchHighlightAndScroll'; import useThemeStyles from '@hooks/useThemeStyles'; import {turnOffMobileSelectionMode, turnOnMobileSelectionMode} from '@libs/actions/MobileSelectionMode'; -import * as SearchActions from '@libs/actions/Search'; -import * as DeviceCapabilities from '@libs/DeviceCapabilities'; +import {createTransactionThread, search} from '@libs/actions/Search'; +import {canUseTouchScreen} from '@libs/DeviceCapabilities'; import Log from '@libs/Log'; import memoize from '@libs/memoize'; import isSearchTopmostCentralPane from '@libs/Navigation/isSearchTopmostCentralPane'; import type {PlatformStackNavigationProp} from '@libs/Navigation/PlatformStackNavigation/types'; -import * as ReportUtils from '@libs/ReportUtils'; -import * as SearchQueryUtils from '@libs/SearchQueryUtils'; -import * as SearchUIUtils from '@libs/SearchUIUtils'; -import * as TransactionUtils from '@libs/TransactionUtils'; +import {generateReportID} from '@libs/ReportUtils'; +import {buildSearchQueryString} from '@libs/SearchQueryUtils'; +import { + getListItem, + getSections, + getSortedSections, + isReportActionListItemType, + isReportListItemType, + isSearchResultsEmpty as isSearchResultsEmptyUtil, + isTransactionListItemType, + shouldShowYear as shouldShowYearUtil, +} from '@libs/SearchUIUtils'; +import {isOnHold} from '@libs/TransactionUtils'; import Navigation from '@navigation/Navigation'; import type {AuthScreensParamList} from '@navigation/types'; import EmptySearchView from '@pages/Search/EmptySearchView'; @@ -57,7 +66,7 @@ function mapTransactionItemToSelectedEntry(item: TransactionListItemType): [stri isSelected: true, canDelete: item.canDelete, canHold: item.canHold, - isHeld: TransactionUtils.isOnHold(item), + isHeld: isOnHold(item), canUnhold: item.canUnhold, action: item.action, reportID: item.reportID, @@ -77,14 +86,14 @@ function mapToItemWithSelectionInfo( canSelectMultiple: boolean, shouldAnimateInHighlight: boolean, ) { - if (SearchUIUtils.isReportActionListItemType(item)) { + if (isReportActionListItemType(item)) { return { ...item, shouldAnimateInHighlight, }; } - return SearchUIUtils.isTransactionListItemType(item) + return isTransactionListItemType(item) ? mapToTransactionItemWithSelectionInfo(item, selectedTransactions, canSelectMultiple, shouldAnimateInHighlight) : { ...item, @@ -107,7 +116,7 @@ function prepareTransactionsList(item: TransactionListItemType, selectedTransact isSelected: true, canDelete: item.canDelete, canHold: item.canHold, - isHeld: TransactionUtils.isOnHold(item), + isHeld: isOnHold(item), canUnhold: item.canUnhold, action: item.action, reportID: item.reportID, @@ -191,12 +200,12 @@ function Search({queryJSON, onSearchListScroll, isSearchScreenFocused, contentCo return; } - SearchActions.search({queryJSON, offset}); + search({queryJSON, offset}); }, [isOffline, offset, queryJSON]); const getItemHeight = useCallback( (item: TransactionListItemType | ReportListItemType | ReportActionListItemType) => { - if (SearchUIUtils.isTransactionListItemType(item) || SearchUIUtils.isReportActionListItemType(item)) { + if (isTransactionListItemType(item) || isReportActionListItemType(item)) { return isLargeScreenWidth ? variables.optionRowHeight + listItemPadding : transactionItemMobileHeight + listItemPadding; } @@ -244,14 +253,14 @@ function Search({queryJSON, onSearchListScroll, isSearchScreenFocused, contentCo const shouldShowLoadingState = !isOffline && !isDataLoaded; const shouldShowLoadingMoreItems = !shouldShowLoadingState && searchResults?.search?.isLoading && searchResults?.search?.offset > 0; - const isSearchResultsEmpty = !searchResults?.data || SearchUIUtils.isSearchResultsEmpty(searchResults); + const isSearchResultsEmpty = !searchResults?.data || isSearchResultsEmptyUtil(searchResults); const prevIsSearchResultEmpty = usePrevious(isSearchResultsEmpty); const data = useMemo(() => { if (searchResults === undefined) { return []; } - return SearchUIUtils.getSections(type, status, searchResults.data, searchResults.search); + return getSections(type, status, searchResults.data, searchResults.search); }, [searchResults, status, type]); useEffect(() => { @@ -275,7 +284,7 @@ function Search({queryJSON, onSearchListScroll, isSearchScreenFocused, contentCo newTransactionList[transaction.transactionID] = { action: transaction.action, canHold: transaction.canHold, - isHeld: TransactionUtils.isOnHold(transaction), + isHeld: isOnHold(transaction), canUnhold: transaction.canUnhold, isSelected: selectedTransactions[transaction.transactionID].isSelected, canDelete: transaction.canDelete, @@ -296,7 +305,7 @@ function Search({queryJSON, onSearchListScroll, isSearchScreenFocused, contentCo newTransactionList[transaction.transactionID] = { action: transaction.action, canHold: transaction.canHold, - isHeld: TransactionUtils.isOnHold(transaction), + isHeld: isOnHold(transaction), canUnhold: transaction.canUnhold, isSelected: selectedTransactions[transaction.transactionID].isSelected, canDelete: transaction.canDelete, @@ -343,8 +352,8 @@ function Search({queryJSON, onSearchListScroll, isSearchScreenFocused, contentCo return {null}; } - const ListItem = SearchUIUtils.getListItem(type, status); - const sortedData = SearchUIUtils.getSortedSections(type, status, data, sortBy, sortOrder); + const ListItem = getListItem(type, status); + const sortedData = getSortedSections(type, status, data, sortBy, sortOrder); const isChat = type === CONST.SEARCH.DATA_TYPES.CHAT; const sortedSelectedData = sortedData.map((item) => { const baseKey = isChat @@ -379,10 +388,10 @@ function Search({queryJSON, onSearchListScroll, isSearchScreenFocused, contentCo } const toggleTransaction = (item: TransactionListItemType | ReportListItemType | ReportActionListItemType) => { - if (SearchUIUtils.isReportActionListItemType(item)) { + if (isReportActionListItemType(item)) { return; } - if (SearchUIUtils.isTransactionListItemType(item)) { + if (isTransactionListItemType(item)) { if (!item.keyForList) { return; } @@ -413,21 +422,21 @@ function Search({queryJSON, onSearchListScroll, isSearchScreenFocused, contentCo const openReport = (item: TransactionListItemType | ReportListItemType | ReportActionListItemType) => { const isFromSelfDM = item.reportID === CONST.REPORT.UNREPORTED_REPORTID; - let reportID = SearchUIUtils.isTransactionListItemType(item) && (!item.isFromOneTransactionReport || isFromSelfDM) ? item.transactionThreadReportID : item.reportID; + let reportID = isTransactionListItemType(item) && (!item.isFromOneTransactionReport || isFromSelfDM) ? item.transactionThreadReportID : item.reportID; if (!reportID) { return; } // If we're trying to open a legacy transaction without a transaction thread, let's create the thread and navigate the user - if (SearchUIUtils.isTransactionListItemType(item) && reportID === '0' && item.moneyRequestReportActionID) { - reportID = ReportUtils.generateReportID(); - SearchActions.createTransactionThread(hash, item.transactionID, reportID, item.moneyRequestReportActionID); + if (isTransactionListItemType(item) && reportID === '0' && item.moneyRequestReportActionID) { + reportID = generateReportID(); + createTransactionThread(hash, item.transactionID, reportID, item.moneyRequestReportActionID); } const backTo = Navigation.getActiveRoute(); - if (SearchUIUtils.isReportActionListItemType(item)) { + if (isReportActionListItemType(item)) { const reportActionID = item.reportActionID; Navigation.navigate(ROUTES.SEARCH_REPORT.getRoute({reportID, reportActionID, backTo})); return; @@ -463,11 +472,11 @@ function Search({queryJSON, onSearchListScroll, isSearchScreenFocused, contentCo }; const onSortPress = (column: SearchColumnType, order: SortOrder) => { - const newQuery = SearchQueryUtils.buildSearchQueryString({...queryJSON, sortBy: column, sortOrder: order}); + const newQuery = buildSearchQueryString({...queryJSON, sortBy: column, sortOrder: order}); navigation.setParams({q: newQuery}); }; - const shouldShowYear = SearchUIUtils.shouldShowYear(searchResults?.data); + const shouldShowYear = shouldShowYearUtil(searchResults?.data); const shouldShowSorting = !Array.isArray(status) && sortableSearchStatuses.includes(status); return ( @@ -492,7 +501,7 @@ function Search({queryJSON, onSearchListScroll, isSearchScreenFocused, contentCo ) } isSelected={(item) => - status !== CONST.SEARCH.STATUS.EXPENSE.ALL && SearchUIUtils.isReportListItemType(item) + status !== CONST.SEARCH.STATUS.EXPENSE.ALL && isReportListItemType(item) ? item.transactions.some((transaction) => selectedTransactions[transaction.keyForList]?.isSelected) : !!item.isSelected } @@ -516,7 +525,7 @@ function Search({queryJSON, onSearchListScroll, isSearchScreenFocused, contentCo onSelectRow={openReport} getItemHeight={getItemHeightMemoized} shouldSingleExecuteRowSelect - shouldPreventDefaultFocusOnSelectRow={!DeviceCapabilities.canUseTouchScreen()} + shouldPreventDefaultFocusOnSelectRow={!canUseTouchScreen()} shouldPreventDefault={false} listHeaderWrapperStyle={[styles.ph8, styles.pt3]} containerStyle={[styles.pv0, type === CONST.SEARCH.DATA_TYPES.CHAT && !isSmallScreenWidth && styles.pt3]} From 3dfac09f975a54ef7a28a599503717dd409bf6b5 Mon Sep 17 00:00:00 2001 From: nkdengineer Date: Fri, 24 Jan 2025 11:28:25 +0700 Subject: [PATCH 7/8] update type --- src/components/Search/types.ts | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/components/Search/types.ts b/src/components/Search/types.ts index 47234b8b2fe0..3dc408cc27c3 100644 --- a/src/components/Search/types.ts +++ b/src/components/Search/types.ts @@ -39,7 +39,7 @@ type SelectedTransactions = Record; /** Model of selected reports */ type SelectedReports = { reportID: string; - policyID: string; + policyID: string | undefined; action: ValueOf; total: number; }; From 9135bd0027ad896f8766b855749644173725126e Mon Sep 17 00:00:00 2001 From: nkdengineer Date: Fri, 24 Jan 2025 11:32:34 +0700 Subject: [PATCH 8/8] fix typecheck --- src/components/Search/SearchPageHeader.tsx | 2 +- src/libs/PolicyUtils.ts | 2 +- 2 files changed, 2 insertions(+), 2 deletions(-) diff --git a/src/components/Search/SearchPageHeader.tsx b/src/components/Search/SearchPageHeader.tsx index a36368293cb2..8930d41c74f4 100644 --- a/src/components/Search/SearchPageHeader.tsx +++ b/src/components/Search/SearchPageHeader.tsx @@ -183,7 +183,7 @@ function SearchPageHeader({queryJSON}: SearchPageHeaderProps) { const paymentData = ( selectedReports.length - ? selectedReports.map((report) => ({reportID: report.reportID, amount: report.total, paymentType: lastPaymentMethods[report.policyID]})) + ? selectedReports.map((report) => ({reportID: report.reportID, amount: report.total, paymentType: lastPaymentMethods[`${report.policyID}`]})) : Object.values(selectedTransactions).map((transaction) => ({ reportID: transaction.reportID, amount: transaction.amount, diff --git a/src/libs/PolicyUtils.ts b/src/libs/PolicyUtils.ts index 9e5e03acd1b0..99b94d31fd81 100644 --- a/src/libs/PolicyUtils.ts +++ b/src/libs/PolicyUtils.ts @@ -1114,7 +1114,7 @@ function getWorkspaceAccountID(policyID?: string) { return policy.workspaceAccountID ?? CONST.DEFAULT_NUMBER_ID; } -function hasVBBA(policyID: string) { +function hasVBBA(policyID: string | undefined) { const policy = getPolicy(policyID); return !!policy?.achAccount?.bankAccountID; }