Skip to content

Commit

Permalink
Merge pull request #41725 from software-mansion-labs/search/kicu/3989…
Browse files Browse the repository at this point in the history
…0-action-buttons

[Search v1] Add handling of actions and improve Search list items
  • Loading branch information
luacmartins authored Jul 9, 2024
2 parents 1296392 + a224fe2 commit 5499988
Show file tree
Hide file tree
Showing 29 changed files with 346 additions and 91 deletions.
2 changes: 2 additions & 0 deletions src/App.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -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';
Expand Down Expand Up @@ -91,6 +92,7 @@ function App({url}: AppProps) {
VolumeContextProvider,
VideoPopoverMenuContextProvider,
KeyboardProvider,
SearchContextProvider,
]}
>
<CustomStatusBarAndBackground />
Expand Down
3 changes: 3 additions & 0 deletions src/CONST.ts
Original file line number Diff line number Diff line change
Expand Up @@ -5164,6 +5164,9 @@ const CONST = {
DONE: 'done',
PAID: 'paid',
VIEW: 'view',
REVIEW: 'review',
HOLD: 'hold',
UNHOLD: 'unhold',
},
TRANSACTION_TYPE: {
CASH: 'cash',
Expand Down
5 changes: 5 additions & 0 deletions src/ROUTES.ts
Original file line number Diff line number Diff line change
Expand Up @@ -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: {
Expand Down
1 change: 1 addition & 0 deletions src/SCREENS.ts
Original file line number Diff line number Diff line change
Expand Up @@ -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: {
Expand Down
7 changes: 6 additions & 1 deletion src/components/ReceiptImage.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -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({
Expand All @@ -93,6 +96,7 @@ function ReceiptImage({
fallbackIconSize,
shouldUseInitialObjectPosition = false,
fallbackIconColor,
fallbackIconBackground,
}: ReceiptImageProps) {
const styles = useThemeStyles();

Expand Down Expand Up @@ -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}
/>
);
Expand Down
58 changes: 58 additions & 0 deletions src/components/Search/SearchContext.tsx
Original file line number Diff line number Diff line change
@@ -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<SearchContext>(defaultSearchContext);

function SearchContextProvider({children}: ChildrenProps) {
const [searchContextData, setSearchContextData] = useState<Pick<SearchContext, 'currentSearchHash' | 'selectedTransactionIDs'>>({
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<SearchContext>(
() => ({
...searchContextData,
setCurrentSearchHash,
setSelectedTransactionIds,
}),
[searchContextData, setCurrentSearchHash, setSelectedTransactionIds],
);

return <Context.Provider value={searchContext}>{children}</Context.Provider>;
}

function useSearchContext() {
return useContext(Context);
}

SearchContextProvider.displayName = 'SearchContextProvider';

export {SearchContextProvider, useSearchContext};
5 changes: 4 additions & 1 deletion src/components/Search/index.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -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';
Expand All @@ -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;
Expand All @@ -47,6 +48,7 @@ function Search({query, policyIDs, sortBy, sortOrder}: SearchProps) {
const {isLargeScreenWidth} = useWindowDimensions();
const navigation = useNavigation<StackNavigationProp<AuthScreensParamList>>();
const lastSearchResultsRef = useRef<OnyxEntry<SearchResults>>();
const {setCurrentSearchHash} = useSearchContext();

const getItemHeight = useCallback(
(item: TransactionListItemType | ReportListItemType) => {
Expand Down Expand Up @@ -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]);
Expand Down
16 changes: 14 additions & 2 deletions src/components/Search/types.ts
Original file line number Diff line number Diff line change
@@ -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 */
Expand All @@ -13,5 +16,14 @@ type SelectedTransactionInfo = {
/** Model of selected results */
type SelectedTransactions = Record<string, SelectedTransactionInfo>;

// eslint-disable-next-line import/prefer-default-export
export type {SelectedTransactionInfo, SelectedTransactions};
type SortOrder = ValueOf<typeof CONST.SEARCH.SORT_ORDER>;
type SearchColumnType = ValueOf<typeof CONST.SEARCH.TABLE_COLUMNS>;

type SearchContext = {
currentSearchHash: number;
selectedTransactionIDs: string[];
setCurrentSearchHash: (hash: number) => void;
setSelectedTransactionIds: (selectedTransactionIds: string[]) => void;
};

export type {SelectedTransactionInfo, SelectedTransactions, SearchColumnType, SortOrder, SearchContext};
67 changes: 57 additions & 10 deletions src/components/SelectionList/Search/ActionCell.tsx
Original file line number Diff line number Diff line change
@@ -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<SearchTransactionAction, TranslationPaths> = {
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 (
<View style={[StyleUtils.getHeight(variables.h28), styles.justifyContentCenter]}>
<Badge
text={translate(buttonTextKey)}
text={text}
icon={Expensicons.Checkmark}
badgeStyles={[
styles.ml0,
styles.ph2,
styles.gap1,
isLargeScreenWidth ? styles.alignSelfCenter : styles.alignSelfEnd,
StyleUtils.getBorderColorStyle(theme.border),
StyleUtils.getHeight(variables.h20),
StyleUtils.getMinimumHeight(variables.h20),
isSelected ? StyleUtils.getBorderColorStyle(theme.buttonHoveredBG) : StyleUtils.getBorderColorStyle(theme.border),
]}
textStyles={StyleUtils.getFontSizeStyle(variables.fontSizeExtraSmall)}
iconStyles={styles.mr0}
Expand All @@ -50,14 +82,29 @@ function ActionCell({onButtonPress, action = CONST.SEARCH.ACTION_TYPES.VIEW, isL
);
}

const buttonInnerStyles = isSelected ? styles.buttonDefaultHovered : {};

if (action === CONST.SEARCH.ACTION_TYPES.VIEW || action === CONST.SEARCH.ACTION_TYPES.REVIEW) {
return (
<Button
text={text}
onPress={goToItem}
small
pressOnEnter
style={[styles.w100]}
innerStyles={buttonInnerStyles}
/>
);
}

return (
<Button
text={translate('common.view')}
text={text}
onPress={onButtonPress}
small
pressOnEnter
style={[styles.w100]}
innerStyles={isSelected ? styles.buttonDefaultHovered : {}}
innerStyles={buttonInnerStyles}
/>
);
}
Expand Down
11 changes: 6 additions & 5 deletions src/components/SelectionList/Search/ExpenseItemHeaderNarrow.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -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';

Expand All @@ -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();
Expand Down Expand Up @@ -48,9 +49,9 @@ function ExpenseItemHeaderNarrow({participantFrom, participantFromDisplayName, p
</View>
<View style={[StyleUtils.getWidthStyle(variables.w80)]}>
<ActionCell
onButtonPress={onButtonPress}
action={action}
isLargeScreenWidth={false}
transactionID={transactionID}
goToItem={onButtonPress}
/>
</View>
</View>
Expand Down
5 changes: 2 additions & 3 deletions src/components/SelectionList/Search/ReportListItem.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -145,7 +145,7 @@ function ReportListItem<TItem extends ListItem>({
onButtonPress={handleOnButtonPress}
/>
)}
<View style={[styles.flex1, styles.flexRow, styles.alignItemsCenter, styles.gap3, isLargeScreenWidth && styles.mr4]}>
<View style={[styles.flex1, styles.flexRow, styles.alignItemsCenter, styles.gap3]}>
<View style={[styles.flexRow, styles.flex1, styles.alignItemsCenter, styles.justifyContentBetween]}>
<View style={[styles.flexRow, styles.alignItemsCenter, styles.flex2]}>
{canSelectMultiple && (
Expand Down Expand Up @@ -177,9 +177,8 @@ function ReportListItem<TItem extends ListItem>({
<View style={StyleUtils.getSearchTableColumnStyles(CONST.SEARCH.TABLE_COLUMNS.TYPE)} />
<View style={StyleUtils.getSearchTableColumnStyles(CONST.SEARCH.TABLE_COLUMNS.ACTION)}>
<ActionCell
isLargeScreenWidth={isLargeScreenWidth}
onButtonPress={handleOnButtonPress}
action={reportItem.action}
goToItem={handleOnButtonPress}
isSelected={item.isSelected}
/>
</View>
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -22,7 +22,7 @@ function TransactionListItem<TItem extends ListItem>({

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,
Expand Down
Loading

0 comments on commit 5499988

Please sign in to comment.