diff --git a/src/App.tsx b/src/App.tsx
index 21025d34a661..98b5d4afeb1d 100644
--- a/src/App.tsx
+++ b/src/App.tsx
@@ -20,6 +20,7 @@ import OnyxProvider from './components/OnyxProvider';
import PopoverContextProvider from './components/PopoverProvider';
import SafeArea from './components/SafeArea';
import ScrollOffsetContextProvider from './components/ScrollOffsetContextProvider';
+import {SearchContextProvider} from './components/Search/SearchContext';
import ThemeIllustrationsProvider from './components/ThemeIllustrationsProvider';
import ThemeProvider from './components/ThemeProvider';
import ThemeStylesProvider from './components/ThemeStylesProvider';
@@ -91,6 +92,7 @@ function App({url}: AppProps) {
VolumeContextProvider,
VideoPopoverMenuContextProvider,
KeyboardProvider,
+ SearchContextProvider,
]}
>
diff --git a/src/CONST.ts b/src/CONST.ts
index b47844a14b14..42be5a24cca3 100755
--- a/src/CONST.ts
+++ b/src/CONST.ts
@@ -5164,6 +5164,9 @@ const CONST = {
DONE: 'done',
PAID: 'paid',
VIEW: 'view',
+ REVIEW: 'review',
+ HOLD: 'hold',
+ UNHOLD: 'unhold',
},
TRANSACTION_TYPE: {
CASH: 'cash',
diff --git a/src/ROUTES.ts b/src/ROUTES.ts
index 054f38b9ec92..a54bb4f5cca5 100644
--- a/src/ROUTES.ts
+++ b/src/ROUTES.ts
@@ -54,6 +54,11 @@ const ROUTES = {
getRoute: (query: string, reportID: string) => `search/${query}/view/${reportID}` as const,
},
+ TRANSACTION_HOLD_REASON_RHP: {
+ route: '/search/:query/hold/:transactionID',
+ getRoute: (query: string, transactionID: string) => `search/${query}/hold/${transactionID}` as const,
+ },
+
// This is a utility route used to go to the user's concierge chat, or the sign-in page if the user's not authenticated
CONCIERGE: 'concierge',
FLAG_COMMENT: {
diff --git a/src/SCREENS.ts b/src/SCREENS.ts
index c6b7da12e572..d2a6b7c19ddd 100644
--- a/src/SCREENS.ts
+++ b/src/SCREENS.ts
@@ -30,6 +30,7 @@ const SCREENS = {
SEARCH: {
CENTRAL_PANE: 'Search_Central_Pane',
REPORT_RHP: 'Search_Report_RHP',
+ TRANSACTION_HOLD_REASON_RHP: 'Search_Transaction_Hold_Reason_RHP',
BOTTOM_TAB: 'Search_Bottom_Tab',
},
SETTINGS: {
diff --git a/src/components/ReceiptImage.tsx b/src/components/ReceiptImage.tsx
index a520693cff57..8c980838b841 100644
--- a/src/components/ReceiptImage.tsx
+++ b/src/components/ReceiptImage.tsx
@@ -74,8 +74,11 @@ type ReceiptImageProps = (
/** The size of the fallback icon */
fallbackIconSize?: number;
- /** The colod of the fallback icon */
+ /** The color of the fallback icon */
fallbackIconColor?: string;
+
+ /** The background color of fallback icon */
+ fallbackIconBackground?: string;
};
function ReceiptImage({
@@ -93,6 +96,7 @@ function ReceiptImage({
fallbackIconSize,
shouldUseInitialObjectPosition = false,
fallbackIconColor,
+ fallbackIconBackground,
}: ReceiptImageProps) {
const styles = useThemeStyles();
@@ -129,6 +133,7 @@ function ReceiptImage({
fallbackIcon={fallbackIcon}
fallbackIconSize={fallbackIconSize}
fallbackIconColor={fallbackIconColor}
+ fallbackIconBackground={fallbackIconBackground}
objectPosition={shouldUseInitialObjectPosition ? CONST.IMAGE_OBJECT_POSITION.INITIAL : CONST.IMAGE_OBJECT_POSITION.TOP}
/>
);
diff --git a/src/components/Search/SearchContext.tsx b/src/components/Search/SearchContext.tsx
new file mode 100644
index 000000000000..3911780d3965
--- /dev/null
+++ b/src/components/Search/SearchContext.tsx
@@ -0,0 +1,58 @@
+import React, {useCallback, useContext, useMemo, useState} from 'react';
+import type ChildrenProps from '@src/types/utils/ChildrenProps';
+import type {SearchContext} from './types';
+
+const defaultSearchContext = {
+ currentSearchHash: -1,
+ selectedTransactionIDs: [],
+ setCurrentSearchHash: () => {},
+ setSelectedTransactionIds: () => {},
+};
+
+const Context = React.createContext(defaultSearchContext);
+
+function SearchContextProvider({children}: ChildrenProps) {
+ const [searchContextData, setSearchContextData] = useState>({
+ currentSearchHash: defaultSearchContext.currentSearchHash,
+ selectedTransactionIDs: defaultSearchContext.selectedTransactionIDs,
+ });
+
+ const setCurrentSearchHash = useCallback(
+ (searchHash: number) => {
+ setSearchContextData({
+ ...searchContextData,
+ currentSearchHash: searchHash,
+ });
+ },
+ [searchContextData],
+ );
+
+ const setSelectedTransactionIds = useCallback(
+ (selectedTransactionIDs: string[]) => {
+ setSearchContextData({
+ ...searchContextData,
+ selectedTransactionIDs,
+ });
+ },
+ [searchContextData],
+ );
+
+ const searchContext = useMemo(
+ () => ({
+ ...searchContextData,
+ setCurrentSearchHash,
+ setSelectedTransactionIds,
+ }),
+ [searchContextData, setCurrentSearchHash, setSelectedTransactionIds],
+ );
+
+ return {children};
+}
+
+function useSearchContext() {
+ return useContext(Context);
+}
+
+SearchContextProvider.displayName = 'SearchContextProvider';
+
+export {SearchContextProvider, useSearchContext};
diff --git a/src/components/Search/index.tsx b/src/components/Search/index.tsx
index 8445cb3bc72e..76e0ca3563ee 100644
--- a/src/components/Search/index.tsx
+++ b/src/components/Search/index.tsx
@@ -13,7 +13,6 @@ import * as SearchActions from '@libs/actions/Search';
import * as DeviceCapabilities from '@libs/DeviceCapabilities';
import Log from '@libs/Log';
import * as ReportUtils from '@libs/ReportUtils';
-import type {SearchColumnType, SortOrder} from '@libs/SearchUtils';
import * as SearchUtils from '@libs/SearchUtils';
import Navigation from '@navigation/Navigation';
import type {AuthScreensParamList} from '@navigation/types';
@@ -25,8 +24,10 @@ import ROUTES from '@src/ROUTES';
import type SearchResults from '@src/types/onyx/SearchResults';
import type {SearchDataTypes, SearchQuery} from '@src/types/onyx/SearchResults';
import isLoadingOnyxValue from '@src/types/utils/isLoadingOnyxValue';
+import {useSearchContext} from './SearchContext';
import SearchListWithHeader from './SearchListWithHeader';
import SearchPageHeader from './SearchPageHeader';
+import type {SearchColumnType, SortOrder} from './types';
type SearchProps = {
query: SearchQuery;
@@ -47,6 +48,7 @@ function Search({query, policyIDs, sortBy, sortOrder}: SearchProps) {
const {isLargeScreenWidth} = useWindowDimensions();
const navigation = useNavigation>();
const lastSearchResultsRef = useRef>();
+ const {setCurrentSearchHash} = useSearchContext();
const getItemHeight = useCallback(
(item: TransactionListItemType | ReportListItemType) => {
@@ -83,6 +85,7 @@ function Search({query, policyIDs, sortBy, sortOrder}: SearchProps) {
return;
}
+ setCurrentSearchHash(hash);
SearchActions.search({hash, query, policyIDs, offset: 0, sortBy, sortOrder});
// eslint-disable-next-line react-compiler/react-compiler, react-hooks/exhaustive-deps
}, [hash, isOffline]);
diff --git a/src/components/Search/types.ts b/src/components/Search/types.ts
index 3ebc2797947a..cff74fe08a0a 100644
--- a/src/components/Search/types.ts
+++ b/src/components/Search/types.ts
@@ -1,3 +1,6 @@
+import type {ValueOf} from 'react-native-gesture-handler/lib/typescript/typeUtils';
+import type CONST from '@src/CONST';
+
/** Model of the selected transaction */
type SelectedTransactionInfo = {
/** Whether the transaction is selected */
@@ -13,5 +16,14 @@ type SelectedTransactionInfo = {
/** Model of selected results */
type SelectedTransactions = Record;
-// eslint-disable-next-line import/prefer-default-export
-export type {SelectedTransactionInfo, SelectedTransactions};
+type SortOrder = ValueOf;
+type SearchColumnType = ValueOf;
+
+type SearchContext = {
+ currentSearchHash: number;
+ selectedTransactionIDs: string[];
+ setCurrentSearchHash: (hash: number) => void;
+ setSelectedTransactionIds: (selectedTransactionIds: string[]) => void;
+};
+
+export type {SelectedTransactionInfo, SelectedTransactions, SearchColumnType, SortOrder, SearchContext};
diff --git a/src/components/SelectionList/Search/ActionCell.tsx b/src/components/SelectionList/Search/ActionCell.tsx
index 5af3d84bf32f..ad77070c1b99 100644
--- a/src/components/SelectionList/Search/ActionCell.tsx
+++ b/src/components/SelectionList/Search/ActionCell.tsx
@@ -1,46 +1,78 @@
-import React from 'react';
+import React, {useCallback} from 'react';
import {View} from 'react-native';
import Badge from '@components/Badge';
import Button from '@components/Button';
import * as Expensicons from '@components/Icon/Expensicons';
+import {useSearchContext} from '@components/Search/SearchContext';
import useLocalize from '@hooks/useLocalize';
import useStyleUtils from '@hooks/useStyleUtils';
import useTheme from '@hooks/useTheme';
import useThemeStyles from '@hooks/useThemeStyles';
+import Navigation from '@navigation/Navigation';
import variables from '@styles/variables';
+import * as SearchActions from '@userActions/Search';
import CONST from '@src/CONST';
+import type {TranslationPaths} from '@src/languages/types';
+import ROUTES from '@src/ROUTES';
+import type {SearchTransactionAction} from '@src/types/onyx/SearchResults';
+
+const actionTranslationsMap: Record = {
+ view: 'common.view',
+ review: 'common.review',
+ done: 'common.done',
+ paid: 'iou.settledExpensify',
+ hold: 'iou.hold',
+ unhold: 'iou.unhold',
+};
type ActionCellProps = {
- onButtonPress: () => void;
- action?: string;
+ action?: SearchTransactionAction;
+ transactionID?: string;
isLargeScreenWidth?: boolean;
isSelected?: boolean;
+ goToItem: () => void;
};
-function ActionCell({onButtonPress, action = CONST.SEARCH.ACTION_TYPES.VIEW, isLargeScreenWidth = true, isSelected = false}: ActionCellProps) {
+function ActionCell({action = CONST.SEARCH.ACTION_TYPES.VIEW, transactionID, isLargeScreenWidth = true, isSelected = false, goToItem}: ActionCellProps) {
const {translate} = useLocalize();
- const styles = useThemeStyles();
const theme = useTheme();
+ const styles = useThemeStyles();
const StyleUtils = useStyleUtils();
+
+ const {currentSearchHash} = useSearchContext();
+
+ const onButtonPress = useCallback(() => {
+ if (!transactionID) {
+ return;
+ }
+
+ if (action === CONST.SEARCH.ACTION_TYPES.HOLD) {
+ Navigation.navigate(ROUTES.TRANSACTION_HOLD_REASON_RHP.getRoute(CONST.SEARCH.TAB.ALL, transactionID));
+ } else if (action === CONST.SEARCH.ACTION_TYPES.UNHOLD) {
+ SearchActions.unholdMoneyRequestOnSearch(currentSearchHash, [transactionID]);
+ }
+ }, [action, currentSearchHash, transactionID]);
+
if (!isLargeScreenWidth) {
return null;
}
+ const text = translate(actionTranslationsMap[action]);
+
if (action === CONST.SEARCH.ACTION_TYPES.PAID || action === CONST.SEARCH.ACTION_TYPES.DONE) {
- const buttonTextKey = action === CONST.SEARCH.ACTION_TYPES.PAID ? 'iou.settledExpensify' : 'common.done';
return (
+ );
+ }
+
return (
);
}
diff --git a/src/components/SelectionList/Search/ExpenseItemHeaderNarrow.tsx b/src/components/SelectionList/Search/ExpenseItemHeaderNarrow.tsx
index 8f46a5388da8..f634f84509b1 100644
--- a/src/components/SelectionList/Search/ExpenseItemHeaderNarrow.tsx
+++ b/src/components/SelectionList/Search/ExpenseItemHeaderNarrow.tsx
@@ -6,7 +6,7 @@ import useStyleUtils from '@hooks/useStyleUtils';
import useTheme from '@hooks/useTheme';
import useThemeStyles from '@hooks/useThemeStyles';
import variables from '@styles/variables';
-import type {SearchAccountDetails} from '@src/types/onyx/SearchResults';
+import type {SearchAccountDetails, SearchTransactionAction} from '@src/types/onyx/SearchResults';
import ActionCell from './ActionCell';
import UserInfoCell from './UserInfoCell';
@@ -15,11 +15,12 @@ type ExpenseItemHeaderNarrowProps = {
participantTo: SearchAccountDetails;
participantFromDisplayName: string;
participantToDisplayName: string;
+ action?: SearchTransactionAction;
+ transactionID?: string;
onButtonPress: () => void;
- action?: string;
};
-function ExpenseItemHeaderNarrow({participantFrom, participantFromDisplayName, participantTo, participantToDisplayName, onButtonPress, action}: ExpenseItemHeaderNarrowProps) {
+function ExpenseItemHeaderNarrow({participantFrom, participantFromDisplayName, participantTo, participantToDisplayName, action, transactionID, onButtonPress}: ExpenseItemHeaderNarrowProps) {
const styles = useThemeStyles();
const StyleUtils = useStyleUtils();
const theme = useTheme();
@@ -48,9 +49,9 @@ function ExpenseItemHeaderNarrow({participantFrom, participantFromDisplayName, p
diff --git a/src/components/SelectionList/Search/ReportListItem.tsx b/src/components/SelectionList/Search/ReportListItem.tsx
index 7119cee06cd9..29c7bc2ca60c 100644
--- a/src/components/SelectionList/Search/ReportListItem.tsx
+++ b/src/components/SelectionList/Search/ReportListItem.tsx
@@ -145,7 +145,7 @@ function ReportListItem({
onButtonPress={handleOnButtonPress}
/>
)}
-
+
{canSelectMultiple && (
@@ -177,9 +177,8 @@ function ReportListItem({
diff --git a/src/components/SelectionList/Search/TransactionListItem.tsx b/src/components/SelectionList/Search/TransactionListItem.tsx
index b00ae0703c2e..6db308831baa 100644
--- a/src/components/SelectionList/Search/TransactionListItem.tsx
+++ b/src/components/SelectionList/Search/TransactionListItem.tsx
@@ -22,7 +22,7 @@ function TransactionListItem({
const {isLargeScreenWidth} = useWindowDimensions();
- const listItemPressableStyle = [styles.selectionListPressableItemWrapper, styles.pv3, item.isSelected && styles.activeComponentBG, isFocused && styles.sidebarLinkActive];
+ const listItemPressableStyle = [styles.selectionListPressableItemWrapper, styles.pv3, styles.ph3, item.isSelected && styles.activeComponentBG, isFocused && styles.sidebarLinkActive];
const listItemWrapperStyle = [
styles.flex1,
diff --git a/src/components/SelectionList/Search/TransactionListItemRow.tsx b/src/components/SelectionList/Search/TransactionListItemRow.tsx
index 23f9234819c3..f9ca70536e4b 100644
--- a/src/components/SelectionList/Search/TransactionListItemRow.tsx
+++ b/src/components/SelectionList/Search/TransactionListItemRow.tsx
@@ -72,13 +72,15 @@ function ReceiptCell({transactionItem}: TransactionCellProps) {
const styles = useThemeStyles();
const StyleUtils = useStyleUtils();
+ const backgroundStyles = transactionItem.isSelected ? StyleUtils.getBackgroundColorStyle(theme.buttonHoveredBG) : StyleUtils.getBackgroundColorStyle(theme.border);
+
return (
@@ -251,8 +254,9 @@ function TransactionListItemRow({
participantFromDisplayName={item.formattedFrom}
participantTo={item.to}
participantToDisplayName={item.formattedTo}
- onButtonPress={onButtonPress}
action={item.action}
+ transactionID={item.transactionID}
+ onButtonPress={onButtonPress}
/>
)}
@@ -314,7 +318,7 @@ function TransactionListItemRow({
style={[styles.cursorUnset, StyleUtils.getCheckboxPressableStyle(), item.isDisabledCheckbox && styles.cursorDisabled]}
/>
)}
-
+
diff --git a/src/components/SelectionList/SearchTableHeader.tsx b/src/components/SelectionList/SearchTableHeader.tsx
index 95e4b680692b..4fc53211b734 100644
--- a/src/components/SelectionList/SearchTableHeader.tsx
+++ b/src/components/SelectionList/SearchTableHeader.tsx
@@ -1,11 +1,11 @@
import React from 'react';
import {View} from 'react-native';
+import type {SearchColumnType, SortOrder} from '@components/Search/types';
import useLocalize from '@hooks/useLocalize';
import useStyleUtils from '@hooks/useStyleUtils';
import useThemeStyles from '@hooks/useThemeStyles';
import useWindowDimensions from '@hooks/useWindowDimensions';
import * as SearchUtils from '@libs/SearchUtils';
-import type {SearchColumnType, SortOrder} from '@libs/SearchUtils';
import CONST from '@src/CONST';
import type {TranslationPaths} from '@src/languages/types';
import type * as OnyxTypes from '@src/types/onyx';
diff --git a/src/components/SelectionList/SortableHeaderText.tsx b/src/components/SelectionList/SortableHeaderText.tsx
index 8b0accf45711..47a894d79f53 100644
--- a/src/components/SelectionList/SortableHeaderText.tsx
+++ b/src/components/SelectionList/SortableHeaderText.tsx
@@ -4,10 +4,10 @@ import type {StyleProp, TextStyle, ViewStyle} from 'react-native';
import Icon from '@components/Icon';
import * as Expensicons from '@components/Icon/Expensicons';
import PressableWithFeedback from '@components/Pressable/PressableWithFeedback';
+import type {SortOrder} from '@components/Search/types';
import Text from '@components/Text';
import useTheme from '@hooks/useTheme';
import useThemeStyles from '@hooks/useThemeStyles';
-import type {SortOrder} from '@libs/SearchUtils';
import CONST from '@src/CONST';
type SearchTableHeaderColumnProps = {
diff --git a/src/components/ThumbnailImage.tsx b/src/components/ThumbnailImage.tsx
index 3b89b7c3a7ad..04d0200ea228 100644
--- a/src/components/ThumbnailImage.tsx
+++ b/src/components/ThumbnailImage.tsx
@@ -41,9 +41,12 @@ type ThumbnailImageProps = {
/** The size of the fallback icon */
fallbackIconSize?: number;
- /** The colod of the fallback icon */
+ /** The color of the fallback icon */
fallbackIconColor?: string;
+ /** The background color of fallback icon */
+ fallbackIconBackground?: string;
+
/** Should the image be resized on load or just fit container */
shouldDynamicallyResize?: boolean;
@@ -66,6 +69,7 @@ function ThumbnailImage({
fallbackIcon = Expensicons.Gallery,
fallbackIconSize = variables.iconSizeSuperLarge,
fallbackIconColor,
+ fallbackIconBackground,
objectPosition = CONST.IMAGE_OBJECT_POSITION.INITIAL,
}: ThumbnailImageProps) {
const styles = useThemeStyles();
@@ -107,8 +111,10 @@ function ThumbnailImage({
const sizeStyles = shouldDynamicallyResize ? [thumbnailDimensionsStyles] : [styles.w100, styles.h100];
if (failedToLoad || previewSourceURL === '') {
+ const fallbackColor = StyleUtils.getBackgroundColorStyle(fallbackIconBackground ?? theme.border);
+
return (
-
+
({
[SCREENS.SEARCH.REPORT_RHP]: () => require('../../../../pages/home/ReportScreen').default,
+ [SCREENS.SEARCH.TRANSACTION_HOLD_REASON_RHP]: () => require('../../../../pages/Search/SearchHoldReasonPage').default,
});
const RestrictedActionModalStackNavigator = createModalStackNavigator({
diff --git a/src/libs/Navigation/linkingConfig/CENTRAL_PANE_TO_RHP_MAPPING.ts b/src/libs/Navigation/linkingConfig/CENTRAL_PANE_TO_RHP_MAPPING.ts
index 1192e4649ea0..83929b7e7d02 100755
--- a/src/libs/Navigation/linkingConfig/CENTRAL_PANE_TO_RHP_MAPPING.ts
+++ b/src/libs/Navigation/linkingConfig/CENTRAL_PANE_TO_RHP_MAPPING.ts
@@ -38,7 +38,7 @@ const CENTRAL_PANE_TO_RHP_MAPPING: Partial> =
[SCREENS.SETTINGS.ABOUT]: [SCREENS.SETTINGS.APP_DOWNLOAD_LINKS],
[SCREENS.SETTINGS.SAVE_THE_WORLD]: [SCREENS.I_KNOW_A_TEACHER, SCREENS.INTRO_SCHOOL_PRINCIPAL, SCREENS.I_AM_A_TEACHER],
[SCREENS.SETTINGS.TROUBLESHOOT]: [SCREENS.SETTINGS.CONSOLE],
- [SCREENS.SEARCH.CENTRAL_PANE]: [SCREENS.SEARCH.REPORT_RHP],
+ [SCREENS.SEARCH.CENTRAL_PANE]: [SCREENS.SEARCH.REPORT_RHP, SCREENS.SEARCH.TRANSACTION_HOLD_REASON_RHP],
[SCREENS.SETTINGS.SUBSCRIPTION.ROOT]: [
SCREENS.SETTINGS.SUBSCRIPTION.ADD_PAYMENT_CARD,
SCREENS.SETTINGS.SUBSCRIPTION.SIZE,
diff --git a/src/libs/Navigation/linkingConfig/config.ts b/src/libs/Navigation/linkingConfig/config.ts
index bdccc65ad2a0..c0d1a79f635f 100644
--- a/src/libs/Navigation/linkingConfig/config.ts
+++ b/src/libs/Navigation/linkingConfig/config.ts
@@ -929,6 +929,7 @@ const config: LinkingOptions['config'] = {
[SCREENS.RIGHT_MODAL.SEARCH_REPORT]: {
screens: {
[SCREENS.SEARCH.REPORT_RHP]: ROUTES.SEARCH_REPORT.route,
+ [SCREENS.SEARCH.TRANSACTION_HOLD_REASON_RHP]: ROUTES.TRANSACTION_HOLD_REASON_RHP.route,
},
},
[SCREENS.RIGHT_MODAL.RESTRICTED_ACTION]: {
diff --git a/src/libs/Navigation/types.ts b/src/libs/Navigation/types.ts
index aefeada462e0..fc67fe6b8cc0 100644
--- a/src/libs/Navigation/types.ts
+++ b/src/libs/Navigation/types.ts
@@ -11,8 +11,8 @@ import type {
Route,
} from '@react-navigation/native';
import type {TupleToUnion, ValueOf} from 'type-fest';
+import type {SearchColumnType, SortOrder} from '@components/Search/types';
import type {IOURequestType} from '@libs/actions/IOU';
-import type {SearchColumnType, SortOrder} from '@libs/SearchUtils';
import type CONST from '@src/CONST';
import type {Country, IOUAction, IOUType} from '@src/CONST';
import type NAVIGATORS from '@src/NAVIGATORS';
diff --git a/src/libs/SearchUtils.ts b/src/libs/SearchUtils.ts
index cb579e44b95d..91d742f44e62 100644
--- a/src/libs/SearchUtils.ts
+++ b/src/libs/SearchUtils.ts
@@ -1,4 +1,4 @@
-import type {ValueOf} from 'react-native-gesture-handler/lib/typescript/typeUtils';
+import type {SearchColumnType, SortOrder} from '@components/Search/types';
import ReportListItem from '@components/SelectionList/Search/ReportListItem';
import TransactionListItem from '@components/SelectionList/Search/TransactionListItem';
import type {ListItem, ReportListItemType, TransactionListItemType} from '@components/SelectionList/types';
@@ -14,9 +14,6 @@ import type {AuthScreensParamList, RootStackParamList, State} from './Navigation
import * as TransactionUtils from './TransactionUtils';
import * as UserUtils from './UserUtils';
-type SortOrder = ValueOf;
-type SearchColumnType = ValueOf;
-
const columnNamesToSortingProperty = {
[CONST.SEARCH.TABLE_COLUMNS.TO]: 'formattedTo' as const,
[CONST.SEARCH.TABLE_COLUMNS.FROM]: 'formattedFrom' as const,
@@ -317,4 +314,3 @@ export {
isTransactionListItemType,
isSearchResultsEmpty,
};
-export type {SearchColumnType, SortOrder};
diff --git a/src/libs/actions/Search.ts b/src/libs/actions/Search.ts
index 70f7d2d5b7e0..4ce82a027a12 100644
--- a/src/libs/actions/Search.ts
+++ b/src/libs/actions/Search.ts
@@ -63,17 +63,18 @@ function createTransactionThread(hash: number, transactionID: string, reportID:
},
},
};
-
Onyx.merge(`${ONYXKEYS.COLLECTION.SNAPSHOT}${hash}`, onyxUpdate);
}
function holdMoneyRequestOnSearch(hash: number, transactionIDList: string[], comment: string) {
const {optimisticData, finallyData} = getOnyxLoadingData(hash);
+
API.write(WRITE_COMMANDS.HOLD_MONEY_REQUEST_ON_SEARCH, {hash, transactionIDList, comment}, {optimisticData, finallyData});
}
function unholdMoneyRequestOnSearch(hash: number, transactionIDList: string[]) {
const {optimisticData, finallyData} = getOnyxLoadingData(hash);
+
API.write(WRITE_COMMANDS.UNHOLD_MONEY_REQUEST_ON_SEARCH, {hash, transactionIDList}, {optimisticData, finallyData});
}
diff --git a/src/pages/Search/SearchHoldReasonPage.tsx b/src/pages/Search/SearchHoldReasonPage.tsx
new file mode 100644
index 000000000000..2506f6bf2453
--- /dev/null
+++ b/src/pages/Search/SearchHoldReasonPage.tsx
@@ -0,0 +1,69 @@
+import type {RouteProp} from '@react-navigation/native';
+import React, {useCallback, useEffect} from 'react';
+import type {FormInputErrors, FormOnyxValues} from '@components/Form/types';
+import {useSearchContext} from '@components/Search/SearchContext';
+import useLocalize from '@hooks/useLocalize';
+import Navigation from '@libs/Navigation/Navigation';
+import * as ValidationUtils from '@libs/ValidationUtils';
+import HoldReasonFormView from '@pages/iou/HoldReasonFormView';
+import * as FormActions from '@userActions/FormActions';
+import * as SearchActions from '@src/libs/actions/Search';
+import ONYXKEYS from '@src/ONYXKEYS';
+import type {Route} from '@src/ROUTES';
+import INPUT_IDS from '@src/types/form/MoneyRequestHoldReasonForm';
+
+type SearchHoldReasonPageRouteParams = {
+ /** ID of the transaction the page was opened for */
+ transactionID: string;
+
+ /** Link to previous page */
+ backTo: Route;
+};
+
+type SearchHoldReasonPageProps = {
+ /** Navigation route context info provided by react navigation */
+ route: RouteProp<{params: SearchHoldReasonPageRouteParams}>;
+};
+
+function SearchHoldReasonPage({route}: SearchHoldReasonPageProps) {
+ const {translate} = useLocalize();
+
+ const {currentSearchHash} = useSearchContext();
+ const {transactionID, backTo} = route.params;
+
+ const onSubmit = (values: FormOnyxValues) => {
+ SearchActions.holdMoneyRequestOnSearch(currentSearchHash, [transactionID], values.comment);
+
+ Navigation.goBack();
+ };
+
+ const validate = useCallback(
+ (values: FormOnyxValues) => {
+ const errors: FormInputErrors = ValidationUtils.getFieldRequiredErrors(values, [INPUT_IDS.COMMENT]);
+
+ if (!values.comment) {
+ errors.comment = translate('common.error.fieldRequired');
+ }
+
+ return errors;
+ },
+ [translate],
+ );
+
+ useEffect(() => {
+ FormActions.clearErrors(ONYXKEYS.FORMS.MONEY_REQUEST_HOLD_FORM);
+ FormActions.clearErrorFields(ONYXKEYS.FORMS.MONEY_REQUEST_HOLD_FORM);
+ }, []);
+
+ return (
+
+ );
+}
+
+SearchHoldReasonPage.displayName = 'SearchHoldReasonPage';
+
+export default SearchHoldReasonPage;
diff --git a/src/pages/iou/HoldReasonFormView.tsx b/src/pages/iou/HoldReasonFormView.tsx
new file mode 100644
index 000000000000..197d00b6b0ed
--- /dev/null
+++ b/src/pages/iou/HoldReasonFormView.tsx
@@ -0,0 +1,72 @@
+import React from 'react';
+import {View} from 'react-native';
+import FormProvider from '@components/Form/FormProvider';
+import InputWrapper from '@components/Form/InputWrapper';
+import type {FormOnyxValues} from '@components/Form/types';
+import HeaderWithBackButton from '@components/HeaderWithBackButton';
+import ScreenWrapper from '@components/ScreenWrapper';
+import Text from '@components/Text';
+import TextInput from '@components/TextInput';
+import useAutoFocusInput from '@hooks/useAutoFocusInput';
+import useLocalize from '@hooks/useLocalize';
+import useThemeStyles from '@hooks/useThemeStyles';
+import Navigation from '@libs/Navigation/Navigation';
+import type ONYXKEYS from '@src/ONYXKEYS';
+import type {Route} from '@src/ROUTES';
+import INPUT_IDS from '@src/types/form/MoneyRequestHoldReasonForm';
+
+type HoldReasonFormViewProps = {
+ /** Submit function for submitting form */
+ onSubmit: (values: FormOnyxValues) => void;
+
+ /** Submit function for validating form */
+ validate: (values: FormOnyxValues) => Partial>;
+
+ /** Link to previous page */
+ backTo: Route;
+};
+
+function HoldReasonFormView({backTo, validate, onSubmit}: HoldReasonFormViewProps) {
+ const styles = useThemeStyles();
+ const {translate} = useLocalize();
+ const {inputCallbackRef} = useAutoFocusInput();
+
+ return (
+
+ Navigation.goBack(backTo)}
+ />
+
+ {translate('iou.explainHold')}
+
+
+
+
+
+ );
+}
+
+HoldReasonFormView.displayName = 'HoldReasonFormViewProps';
+
+export default HoldReasonFormView;
diff --git a/src/pages/iou/HoldReasonPage.tsx b/src/pages/iou/HoldReasonPage.tsx
index 2fc789f77c6c..82f29acf7d10 100644
--- a/src/pages/iou/HoldReasonPage.tsx
+++ b/src/pages/iou/HoldReasonPage.tsx
@@ -1,17 +1,8 @@
import type {RouteProp} from '@react-navigation/native';
import React, {useCallback, useEffect} from 'react';
-import {View} from 'react-native';
import {useOnyx} from 'react-native-onyx';
-import FormProvider from '@components/Form/FormProvider';
-import InputWrapper from '@components/Form/InputWrapper';
import type {FormInputErrors, FormOnyxValues} from '@components/Form/types';
-import HeaderWithBackButton from '@components/HeaderWithBackButton';
-import ScreenWrapper from '@components/ScreenWrapper';
-import Text from '@components/Text';
-import TextInput from '@components/TextInput';
-import useAutoFocusInput from '@hooks/useAutoFocusInput';
import useLocalize from '@hooks/useLocalize';
-import useThemeStyles from '@hooks/useThemeStyles';
import * as ErrorUtils from '@libs/ErrorUtils';
import Navigation from '@libs/Navigation/Navigation';
import * as ReportActionsUtils from '@libs/ReportActionsUtils';
@@ -22,6 +13,7 @@ import * as IOU from '@userActions/IOU';
import ONYXKEYS from '@src/ONYXKEYS';
import type {Route} from '@src/ROUTES';
import INPUT_IDS from '@src/types/form/MoneyRequestHoldReasonForm';
+import HoldReasonFormView from './HoldReasonFormView';
type HoldReasonPageRouteParams = {
/** ID of the transaction the page was opened for */
@@ -40,9 +32,7 @@ type HoldReasonPageProps = {
};
function HoldReasonPage({route}: HoldReasonPageProps) {
- const styles = useThemeStyles();
const {translate} = useLocalize();
- const {inputCallbackRef} = useAutoFocusInput();
const {transactionID, reportID, backTo} = route.params;
@@ -53,10 +43,6 @@ function HoldReasonPage({route}: HoldReasonPageProps) {
const isWorkspaceRequest = ReportUtils.isReportInGroupPolicy(report);
const parentReportAction = ReportActionsUtils.getReportAction(report?.parentReportID ?? '-1', report?.parentReportActionID ?? '-1');
- const navigateBack = () => {
- Navigation.navigate(backTo);
- };
-
const onSubmit = (values: FormOnyxValues) => {
// We have extra isWorkspaceRequest condition since, for 1:1 requests, canEditMoneyRequest will rightly return false
// as we do not allow requestee to edit fields like description and amount.
@@ -66,7 +52,7 @@ function HoldReasonPage({route}: HoldReasonPageProps) {
}
IOU.putOnHold(transactionID, values.comment, reportID);
- navigateBack();
+ Navigation.navigate(backTo);
};
const validate = useCallback(
@@ -96,38 +82,11 @@ function HoldReasonPage({route}: HoldReasonPageProps) {
}, []);
return (
-
- Navigation.goBack(backTo)}
- />
-
- {translate('iou.explainHold')}
-
-
-
-
-
+
);
}
diff --git a/src/types/onyx/SearchResults.ts b/src/types/onyx/SearchResults.ts
index 89d1b6c72566..05cd2db18006 100644
--- a/src/types/onyx/SearchResults.ts
+++ b/src/types/onyx/SearchResults.ts
@@ -1,8 +1,8 @@
import type {ValueOf} from 'type-fest';
+import type {SearchColumnType, SortOrder} from '@components/Search/types';
import type ReportListItem from '@components/SelectionList/Search/ReportListItem';
import type TransactionListItem from '@components/SelectionList/Search/TransactionListItem';
import type {ReportListItemType, TransactionListItemType} from '@components/SelectionList/types';
-import type {SearchColumnType, SortOrder} from '@libs/SearchUtils';
import type CONST from '@src/CONST';
/** Types of search data */
@@ -93,6 +93,9 @@ type SearchPolicyDetails = {
name: string;
};
+/** The action that can be performed for the transaction */
+type SearchTransactionAction = ValueOf;
+
/** Model of report search result */
type SearchReport = {
/** The ID of the report */
@@ -120,7 +123,7 @@ type SearchReport = {
created?: string;
/** The action that can be performed for the report */
- action?: string;
+ action?: SearchTransactionAction;
};
/** Model of transaction search result */
@@ -213,7 +216,7 @@ type SearchTransaction = {
transactionThreadReportID: string;
/** The action that can be performed for the transaction */
- action: string;
+ action: SearchTransactionAction;
/** The MCC Group associated with the transaction */
mccGroup?: ValueOf;
@@ -241,6 +244,9 @@ type SearchResults = {
/** Search results data */
data: Record> & Record & Record;
+
+ /** Whether search data is being fetched from server */
+ isLoading?: boolean;
};
export default SearchResults;
@@ -249,6 +255,7 @@ export type {
SearchQuery,
SearchTransaction,
SearchTransactionType,
+ SearchTransactionAction,
SearchPersonalDetails,
SearchPolicyDetails,
SearchAccountDetails,