Skip to content

Commit

Permalink
Add autocomplete list to SearchPageHeader and refactor it
Browse files Browse the repository at this point in the history
  • Loading branch information
Kicu committed Nov 15, 2024
1 parent 5f60dcf commit 19058b3
Show file tree
Hide file tree
Showing 11 changed files with 350 additions and 145 deletions.
137 changes: 11 additions & 126 deletions src/components/Search/SearchPageHeader.tsx
Original file line number Diff line number Diff line change
@@ -1,120 +1,38 @@
import React, {useEffect, useMemo, useState} from 'react';
import React, {useMemo, useState} from 'react';
import {InteractionManager, View} from 'react-native';
import {useOnyx} from 'react-native-onyx';
import Button from '@components/Button';
import ButtonWithDropdownMenu from '@components/ButtonWithDropdownMenu';
import type {DropdownOption} from '@components/ButtonWithDropdownMenu/types';
import ConfirmModal from '@components/ConfirmModal';
import DecisionModal from '@components/DecisionModal';
import Header from '@components/Header';
import type HeaderWithBackButtonProps from '@components/HeaderWithBackButton/types';
import Icon from '@components/Icon';
import * as Expensicons from '@components/Icon/Expensicons';
import * as Illustrations from '@components/Icon/Illustrations';
import {usePersonalDetails} from '@components/OnyxProvider';
import Text from '@components/Text';
import useActiveWorkspace from '@hooks/useActiveWorkspace';
import useLocalize from '@hooks/useLocalize';
import useNetwork from '@hooks/useNetwork';
import useResponsiveLayout from '@hooks/useResponsiveLayout';
import useTheme from '@hooks/useTheme';
import useThemeStyles from '@hooks/useThemeStyles';
import * as SearchActions from '@libs/actions/Search';
import Log from '@libs/Log';
import Navigation from '@libs/Navigation/Navigation';
import {getAllTaxRates} from '@libs/PolicyUtils';
import * as SearchQueryUtils from '@libs/SearchQueryUtils';
import SearchSelectedNarrow from '@pages/Search/SearchSelectedNarrow';
import variables from '@styles/variables';
import CONST from '@src/CONST';
import type {TranslationPaths} from '@src/languages/types';
import ONYXKEYS from '@src/ONYXKEYS';
import ROUTES from '@src/ROUTES';
import type {SearchDataTypes} from '@src/types/onyx/SearchResults';
import type DeepValueOf from '@src/types/utils/DeepValueOf';
import type IconAsset from '@src/types/utils/IconAsset';
import {useSearchContext} from './SearchContext';
import SearchButton from './SearchRouter/SearchButton';
import SearchRouterInput from './SearchRouter/SearchRouterInput';
import SearchPageHeaderInput from './SearchPageHeaderInput';
import type {SearchQueryJSON} from './types';

type HeaderWrapperProps = Pick<HeaderWithBackButtonProps, 'icon' | 'children'> & {
text: string;
value: string;
isCannedQuery: boolean;
onSubmit: () => void;
setValue: (input: string) => void;
};

function HeaderWrapper({icon, children, text, value, isCannedQuery, onSubmit, setValue}: HeaderWrapperProps) {
const styles = useThemeStyles();
// If the icon is present, the header bar should be taller and use different font.
const isCentralPaneSettings = !!icon;

return (
<View
dataSet={{dragArea: false}}
style={[styles.headerBar, isCentralPaneSettings && styles.headerBarDesktopHeight]}
>
{isCannedQuery ? (
<View style={[styles.dFlex, styles.flexRow, styles.alignItemsCenter, styles.flexGrow1, styles.justifyContentBetween, styles.overflowHidden]}>
{!!icon && (
<Icon
src={icon}
width={variables.iconHeader}
height={variables.iconHeader}
additionalStyles={[styles.mr2]}
/>
)}
<Header subtitle={<Text style={[styles.textLarge, styles.textHeadlineH2]}>{text}</Text>} />
<View style={[styles.reportOptions, styles.flexRow, styles.pr5, styles.alignItemsCenter, styles.gap2]}>{children}</View>
</View>
) : (
<View style={styles.pr5}>
<SearchRouterInput
value={value}
onSubmit={onSubmit}
onSearchQueryChange={setValue}
autoFocus={false}
isFullWidth
wrapperStyle={[styles.searchRouterInputResults, styles.br2]}
wrapperFocusedStyle={styles.searchRouterInputResultsFocused}
rightComponent={children}
routerListRef={undefined}
/>
</View>
)}
</View>
);
}

type SearchPageHeaderProps = {
queryJSON: SearchQueryJSON;
hash: number;
};
type SearchPageHeaderProps = {queryJSON: SearchQueryJSON};

type SearchHeaderOptionValue = DeepValueOf<typeof CONST.SEARCH.BULK_ACTION_TYPES> | undefined;

type HeaderContent = {
icon: IconAsset;
titleText: TranslationPaths;
};

function getHeaderContent(type: SearchDataTypes): HeaderContent {
switch (type) {
case CONST.SEARCH.DATA_TYPES.INVOICE:
return {icon: Illustrations.EnvelopeReceipt, titleText: 'workspace.common.invoices'};
case CONST.SEARCH.DATA_TYPES.TRIP:
return {icon: Illustrations.Luggage, titleText: 'travel.trips'};
case CONST.SEARCH.DATA_TYPES.CHAT:
return {icon: Illustrations.CommentBubblesBlue, titleText: 'common.chats'};
case CONST.SEARCH.DATA_TYPES.EXPENSE:
default:
return {icon: Illustrations.MoneyReceipts, titleText: 'common.expenses'};
}
}

function SearchPageHeader({queryJSON, hash}: SearchPageHeaderProps) {
function SearchPageHeader({queryJSON}: SearchPageHeaderProps) {
const {translate} = useLocalize();
const theme = useTheme();
const styles = useThemeStyles();
Expand All @@ -136,19 +54,10 @@ function SearchPageHeader({queryJSON, hash}: SearchPageHeaderProps) {
const [isOfflineModalVisible, setIsOfflineModalVisible] = useState(false);
const [isDownloadErrorModalVisible, setIsDownloadErrorModalVisible] = useState(false);

const {status, type} = queryJSON;
const isCannedQuery = SearchQueryUtils.isCannedSearchQuery(queryJSON);
const headerText = isCannedQuery ? translate(getHeaderContent(type).titleText) : SearchQueryUtils.buildUserReadableQueryString(queryJSON, personalDetails, cardList, reports, taxRates);
const [inputValue, setInputValue] = useState(headerText);

useEffect(() => {
setInputValue(headerText);
}, [headerText]);
const {status, hash} = queryJSON;

const selectedTransactionsKeys = Object.keys(selectedTransactions ?? {});

const headerIcon = getHeaderContent(type).icon;

const handleDeleteExpenses = () => {
if (selectedTransactionsKeys.length === 0) {
return;
Expand Down Expand Up @@ -182,7 +91,7 @@ function SearchPageHeader({queryJSON, hash}: SearchPageHeaderProps) {
return;
}

const reportIDList = (selectedReports?.filter((report) => !!report) as string[]) ?? [];
const reportIDList = selectedReports.filter((report): report is string => !!report) ?? [];
SearchActions.exportSearchItemsToCSV(
{query: status, jsonQuery: JSON.stringify(queryJSON), reportIDList, transactionIDList: selectedTransactionsKeys, policyIDs: [activeWorkspaceID ?? '']},
() => {
Expand Down Expand Up @@ -327,41 +236,18 @@ function SearchPageHeader({queryJSON, hash}: SearchPageHeaderProps) {
return null;
}

const onPress = () => {
const onFiltersButtonPress = () => {
const filterFormValues = SearchQueryUtils.buildFilterFormValuesFromQuery(queryJSON, policyCategories, policyTagsLists, currencyList, personalDetails, cardList, reports, taxRates);
SearchActions.updateAdvancedFilters(filterFormValues);

Navigation.navigate(ROUTES.SEARCH_ADVANCED_FILTERS);
};

const onSubmit = () => {
if (!inputValue) {
return;
}
const inputQueryJSON = SearchQueryUtils.buildSearchQueryJSON(inputValue);
if (inputQueryJSON) {
// Todo traverse the tree to update all the display values into id values; this is only temporary until autocomplete code from SearchRouter is implement here
// After https://github.com/Expensify/App/pull/51633 is merged, autocomplete functionality will be included into this component, and `getFindIDFromDisplayValue` can be removed
const computeNodeValueFn = SearchQueryUtils.getFindIDFromDisplayValue(cardList, taxRates);
const standardizedQuery = SearchQueryUtils.traverseAndUpdatedQuery(inputQueryJSON, computeNodeValueFn);
const query = SearchQueryUtils.buildSearchQueryString(standardizedQuery);
SearchActions.clearAllFilters();
Navigation.navigate(ROUTES.SEARCH_CENTRAL_PANE.getRoute({query}));
} else {
Log.alert(`${CONST.ERROR.ENSURE_BUGBOT} user query failed to parse`, inputValue, false);
}
};
const isCannedQuery = SearchQueryUtils.isCannedSearchQuery(queryJSON);

return (
<>
<HeaderWrapper
icon={headerIcon}
text={headerText}
value={inputValue}
isCannedQuery={isCannedQuery}
onSubmit={onSubmit}
setValue={setInputValue}
>
<SearchPageHeaderInput queryJSON={queryJSON}>
{headerButtonsOptions.length > 0 ? (
<ButtonWithDropdownMenu
onPress={() => null}
Expand All @@ -377,11 +263,10 @@ function SearchPageHeader({queryJSON, hash}: SearchPageHeaderProps) {
innerStyles={!isCannedQuery && [styles.searchRouterInputResults, styles.borderNone]}
text={translate('search.filtersHeader')}
icon={Expensicons.Filters}
onPress={onPress}
onPress={onFiltersButtonPress}
/>
)}
{isCannedQuery && <SearchButton />}
</HeaderWrapper>
</SearchPageHeaderInput>
<ConfirmModal
isVisible={isDeleteExpensesConfirmModalVisible}
onConfirm={handleDeleteExpenses}
Expand Down
Loading

0 comments on commit 19058b3

Please sign in to comment.