Skip to content

Commit

Permalink
Merge pull request Expensify#42410 from software-mansion-labs/search-…
Browse files Browse the repository at this point in the history
…v1-p2/report-list-item

[No QA][Search v1] Create ReportListItem
  • Loading branch information
luacmartins authored May 28, 2024
2 parents 8be7abe + 4e2942b commit f5d28de
Show file tree
Hide file tree
Showing 17 changed files with 861 additions and 486 deletions.
1 change: 1 addition & 0 deletions src/CONST.ts
Original file line number Diff line number Diff line change
Expand Up @@ -4782,6 +4782,7 @@ const CONST = {

SEARCH_DATA_TYPES: {
TRANSACTION: 'transaction',
REPORT: 'report',
},

REFERRER: {
Expand Down
13 changes: 11 additions & 2 deletions src/components/Search.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -15,13 +15,19 @@ import {isEmptyObject} from '@src/types/utils/EmptyObject';
import isLoadingOnyxValue from '@src/types/utils/isLoadingOnyxValue';
import SelectionList from './SelectionList';
import SearchTableHeader from './SelectionList/SearchTableHeader';
import type {ReportListItemType, TransactionListItemType} from './SelectionList/types';
import TableListItemSkeleton from './Skeletons/TableListItemSkeleton';

type SearchProps = {
query: string;
policyIDs?: string;
};

function isReportListItemType(item: TransactionListItemType | ReportListItemType): item is ReportListItemType {
const reportListItem = item as ReportListItemType;
return reportListItem.transactions !== undefined;
}

function Search({query, policyIDs}: SearchProps) {
const {isOffline} = useNetwork();
const styles = useThemeStyles();
Expand Down Expand Up @@ -74,10 +80,11 @@ function Search({query, policyIDs}: SearchProps) {
}

const ListItem = SearchUtils.getListItem(type);

const data = SearchUtils.getSections(searchResults?.data ?? {}, type);

return (
<SelectionList
<SelectionList<ReportListItemType | TransactionListItemType>
customListHeader={<SearchTableHeader data={searchResults?.data} />}
// To enhance the smoothness of scrolling and minimize the risk of encountering blank spaces during scrolling,
// we have configured a larger windowSize and a longer delay between batch renders.
Expand All @@ -93,7 +100,9 @@ function Search({query, policyIDs}: SearchProps) {
ListItem={ListItem}
sections={[{data, isDisabled: false}]}
onSelectRow={(item) => {
openReport(item.transactionThreadReportID);
const reportID = isReportListItemType(item) ? item.reportID : item.transactionThreadReportID;

openReport(reportID);
}}
shouldPreventDefaultFocusOnSelectRow={!DeviceCapabilities.canUseTouchScreen()}
listHeaderWrapperStyle={[styles.ph9, styles.pv3, styles.pb5]}
Expand Down
54 changes: 54 additions & 0 deletions src/components/SelectionList/Search/ExpenseItemHeaderNarrow.tsx
Original file line number Diff line number Diff line change
@@ -0,0 +1,54 @@
import React, {memo} from 'react';
import {View} from 'react-native';
import Button from '@components/Button';
import Icon from '@components/Icon';
import * as Expensicons from '@components/Icon/Expensicons';
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 UserInfoCell from './UserInfoCell';

type ExpenseItemHeaderNarrowProps = {
participantFrom: SearchAccountDetails;
participantTo: SearchAccountDetails;
buttonText: string;
onButtonPress: () => void;
};

function ExpenseItemHeaderNarrow({participantFrom, participantTo, buttonText, onButtonPress}: ExpenseItemHeaderNarrowProps) {
const styles = useThemeStyles();
const StyleUtils = useStyleUtils();
const theme = useTheme();

return (
<View style={[styles.flex1, styles.flexRow, styles.alignItemsCenter, styles.justifyContentBetween, styles.mb2, styles.gap2]}>
<View style={[styles.flexRow, styles.alignItemsCenter, styles.gap1, styles.flex1]}>
<View style={[styles.mw50]}>
<UserInfoCell participant={participantFrom} />
</View>
<Icon
src={Expensicons.ArrowRightLong}
width={variables.iconSizeXXSmall}
height={variables.iconSizeXXSmall}
fill={theme.icon}
/>
<View style={[styles.flex1, styles.mw50]}>
<UserInfoCell participant={participantTo} />
</View>
</View>
<View style={[StyleUtils.getWidthStyle(variables.w80)]}>
<Button
text={buttonText}
onPress={onButtonPress}
small
pressOnEnter
style={[styles.p0]}
/>
</View>
</View>
);
}

export default memo(ExpenseItemHeaderNarrow);
159 changes: 159 additions & 0 deletions src/components/SelectionList/Search/ReportListItem.tsx
Original file line number Diff line number Diff line change
@@ -0,0 +1,159 @@
import React from 'react';
import {View} from 'react-native';
import Button from '@components/Button';
import BaseListItem from '@components/SelectionList/BaseListItem';
import type {ListItem, ReportListItemProps, ReportListItemType, TransactionListItemType} from '@components/SelectionList/types';
import Text from '@components/Text';
import TextWithTooltip from '@components/TextWithTooltip';
import useLocalize from '@hooks/useLocalize';
import useStyleUtils from '@hooks/useStyleUtils';
import useThemeStyles from '@hooks/useThemeStyles';
import useWindowDimensions from '@hooks/useWindowDimensions';
import * as CurrencyUtils from '@libs/CurrencyUtils';
import Navigation from '@libs/Navigation/Navigation';
import {getSearchParams} from '@libs/SearchUtils';
import CONST from '@src/CONST';
import ROUTES from '@src/ROUTES';
import ExpenseItemHeaderNarrow from './ExpenseItemHeaderNarrow';
import TransactionListItem from './TransactionListItem';
import TransactionListItemRow from './TransactionListItemRow';

function ReportListItem<TItem extends ListItem>({
item,
isFocused,
showTooltip,
isDisabled,
canSelectMultiple,
onSelectRow,
onDismissError,
shouldPreventDefaultFocusOnSelectRow,
onFocus,
shouldSyncFocus,
}: ReportListItemProps<TItem>) {
const reportItem = item as unknown as ReportListItemType;

const styles = useThemeStyles();
const {translate} = useLocalize();
const {isLargeScreenWidth} = useWindowDimensions();
const StyleUtils = useStyleUtils();

const listItemPressableStyle = [styles.selectionListPressableItemWrapper, styles.pv3, item.isSelected && styles.activeComponentBG, isFocused && styles.sidebarLinkActive];

const handleOnButtonPress = () => {
onSelectRow(item);
};

const openReportInRHP = (transactionItem: TransactionListItemType) => {
const searchParams = getSearchParams();
const currentQuery = searchParams && `query` in searchParams ? (searchParams?.query as string) : CONST.TAB_SEARCH.ALL;
Navigation.navigate(ROUTES.SEARCH_REPORT.getRoute(currentQuery, transactionItem.transactionThreadReportID));
};

const totalCell = (
<TextWithTooltip
shouldShowTooltip={showTooltip}
text={CurrencyUtils.convertToDisplayString(reportItem?.total, reportItem?.currency)}
style={[styles.optionDisplayName, styles.textNewKansasNormal, styles.pre, styles.justifyContentCenter, isLargeScreenWidth ? undefined : styles.textAlignRight]}
/>
);

const actionCell = (
<Button
text={translate('common.view')}
onPress={handleOnButtonPress}
small
pressOnEnter
style={[styles.p0]}
/>
);

if (!reportItem?.reportName && reportItem.transactions.length > 1) {
return null;
}

const participantFrom = reportItem.transactions[0].from;
const participantTo = reportItem.transactions[0].to;

if (reportItem.transactions.length === 1) {
const transactionItem = reportItem.transactions[0];

return (
<TransactionListItem
item={transactionItem as unknown as TItem}
isFocused={isFocused}
showTooltip={showTooltip}
isDisabled={isDisabled}
canSelectMultiple={canSelectMultiple}
onSelectRow={() => openReportInRHP(transactionItem)}
onDismissError={onDismissError}
shouldPreventDefaultFocusOnSelectRow={shouldPreventDefaultFocusOnSelectRow}
onFocus={onFocus}
shouldSyncFocus={shouldSyncFocus}
/>
);
}

return (
<BaseListItem
item={item}
pressableStyle={listItemPressableStyle}
wrapperStyle={[styles.flexRow, styles.flex1, styles.justifyContentBetween, styles.userSelectNone, styles.alignItemsCenter]}
containerStyle={[styles.mb3]}
isFocused={isFocused}
isDisabled={isDisabled}
showTooltip={showTooltip}
canSelectMultiple={canSelectMultiple}
onSelectRow={onSelectRow}
onDismissError={onDismissError}
shouldPreventDefaultFocusOnSelectRow={shouldPreventDefaultFocusOnSelectRow}
errors={item.errors}
pendingAction={item.pendingAction}
keyForList={item.keyForList}
onFocus={onFocus}
shouldSyncFocus={shouldSyncFocus}
hoverStyle={item.isSelected && styles.activeComponentBG}
>
<View style={styles.flex1}>
{!isLargeScreenWidth && (
<ExpenseItemHeaderNarrow
participantFrom={participantFrom}
participantTo={participantTo}
buttonText={translate('common.view')}
onButtonPress={handleOnButtonPress}
/>
)}
<View style={[styles.flex1, styles.flexRow, styles.alignItemsCenter]}>
<View style={[styles.flexRow, styles.flex1, styles.alignItemsCenter, styles.justifyContentBetween]}>
<View style={[styles.flexRow, styles.alignItemsCenter, styles.flex2]}>
<View style={[styles.flexShrink1]}>
<Text style={[styles.reportListItemTitle]}>{reportItem?.reportName}</Text>
<Text style={[styles.textMicroSupporting]}>{`${reportItem.transactions.length} ${translate('search.groupedExpenses')}`}</Text>
</View>
</View>
<View style={[styles.flexRow, styles.flex1, styles.justifyContentEnd]}>{totalCell}</View>
</View>
{/** styles.reportListItemActionButtonMargin added here to move the action button by the type column distance */}
{isLargeScreenWidth && (
<View style={[StyleUtils.getSearchTableColumnStyles(CONST.SEARCH_TABLE_COLUMNS.ACTION), styles.reportListItemActionButtonMargin]}>{actionCell}</View>
)}
</View>
<View style={[styles.mt3, styles.reportListItemSeparator]} />
{reportItem.transactions.map((transaction) => (
<TransactionListItemRow
item={transaction}
showTooltip={showTooltip}
onButtonPress={() => {
openReportInRHP(transaction);
}}
showItemHeaderOnNarrowLayout={false}
containerStyle={styles.mt3}
/>
))}
</View>
</BaseListItem>
);
}

ReportListItem.displayName = 'ReportListItem';

export default ReportListItem;
68 changes: 68 additions & 0 deletions src/components/SelectionList/Search/TransactionListItem.tsx
Original file line number Diff line number Diff line change
@@ -0,0 +1,68 @@
import React from 'react';
import BaseListItem from '@components/SelectionList/BaseListItem';
import type {ListItem, TransactionListItemProps, TransactionListItemType} from '@components/SelectionList/types';
import useThemeStyles from '@hooks/useThemeStyles';
import useWindowDimensions from '@hooks/useWindowDimensions';
import TransactionListItemRow from './TransactionListItemRow';

function TransactionListItem<TItem extends ListItem>({
item,
isFocused,
showTooltip,
isDisabled,
canSelectMultiple,
onSelectRow,
onDismissError,
shouldPreventDefaultFocusOnSelectRow,
onFocus,
shouldSyncFocus,
}: TransactionListItemProps<TItem>) {
const transactionItem = item as unknown as TransactionListItemType;
const styles = useThemeStyles();

const {isLargeScreenWidth} = useWindowDimensions();

const listItemPressableStyle = [styles.selectionListPressableItemWrapper, styles.pv3, item.isSelected && styles.activeComponentBG, isFocused && styles.sidebarLinkActive];

const listItemWrapperStyle = [
styles.flex1,
styles.userSelectNone,
isLargeScreenWidth ? {...styles.flexRow, ...styles.justifyContentBetween, ...styles.alignItemsCenter} : {...styles.flexColumn, ...styles.alignItemsStretch},
];

return (
<BaseListItem
item={item}
pressableStyle={listItemPressableStyle}
wrapperStyle={listItemWrapperStyle}
containerStyle={[styles.mb3]}
isFocused={isFocused}
isDisabled={isDisabled}
showTooltip={showTooltip}
canSelectMultiple={canSelectMultiple}
onSelectRow={onSelectRow}
onDismissError={onDismissError}
shouldPreventDefaultFocusOnSelectRow={shouldPreventDefaultFocusOnSelectRow}
errors={item.errors}
pendingAction={item.pendingAction}
keyForList={item.keyForList}
onFocus={onFocus}
shouldSyncFocus={shouldSyncFocus}
hoverStyle={item.isSelected && styles.activeComponentBG}
>
{() => (
<TransactionListItemRow
item={transactionItem}
showTooltip={showTooltip}
onButtonPress={() => {
onSelectRow(item);
}}
/>
)}
</BaseListItem>
);
}

TransactionListItem.displayName = 'TransactionListItem';

export default TransactionListItem;
Loading

0 comments on commit f5d28de

Please sign in to comment.