Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
Show all changes
47 commits
Select commit Hold shift + click to select a range
a6f60db
[CFI] Add group-by columns
mohammadjafarinejad Nov 26, 2025
e491685
Merge branch 'Expensify:main' into fix/75099
mohammadjafarinejad Nov 26, 2025
14a9be1
chore: fix prettier
mohammadjafarinejad Nov 26, 2025
faafa0a
feat: add isExpenseReportView prop to TransactionGroupListExpanded
mohammadjafarinejad Nov 26, 2025
e1f2188
fix ts error & spell check
mohammadjafarinejad Nov 27, 2025
7e70ea8
fix: update sorting logic
mohammadjafarinejad Nov 27, 2025
030130c
fix: add "americanexpress" to cspell dictionary
mohammadjafarinejad Nov 27, 2025
077043f
Merge branch 'main' into fix/75099
mohammadjafarinejad Nov 27, 2025
e257c24
fix: remove isExpenseReportView prop from Search component
mohammadjafarinejad Nov 27, 2025
26d1483
fix: update width handling for expenses
mohammadjafarinejad Nov 27, 2025
9ea500a
fix: hide Action column from nested rows
mohammadjafarinejad Nov 27, 2025
c582be9
fix: show checkbox for withdrawal group
mohammadjafarinejad Nov 28, 2025
85255c3
fix: group headers layout and styles issues
mohammadjafarinejad Nov 28, 2025
724860d
fix: default sorting
mohammadjafarinejad Nov 28, 2025
789fe65
fix: add formattedCardName to transaction card group list items
mohammadjafarinejad Nov 28, 2025
caab269
Merge branch 'main' into fix/75099
mohammadjafarinejad Nov 30, 2025
6a41ee4
fix: add cardFeeds to search component
mohammadjafarinejad Nov 30, 2025
e23705d
Merge branch 'main' into fix/75099
mohammadjafarinejad Dec 3, 2025
ab7c0e9
refactor: simplify checkbox rendering in CardListItemHeader and Membe…
mohammadjafarinejad Dec 3, 2025
b4bac30
fix eslint
mohammadjafarinejad Dec 3, 2025
85eb6cb
fix eslint
mohammadjafarinejad Dec 3, 2025
dd64ee8
Merge branch 'Expensify:main' into fix/75099
mohammadjafarinejad Dec 3, 2025
5cdc18a
Merge branch 'main' into fix/75099
mohammadjafarinejad Dec 6, 2025
dbfc6da
fix: update peggy to change default sortBy
mohammadjafarinejad Dec 11, 2025
3595511
revert: unintended change
mohammadjafarinejad Dec 12, 2025
91f8f3f
revert: unintended change
mohammadjafarinejad Dec 12, 2025
0eeed46
refactor: DRY list item header components
mohammadjafarinejad Dec 12, 2025
cd61b52
Merge branch 'main' into fix/75099
mohammadjafarinejad Dec 16, 2025
c53358f
Fix merge conflicts from main
mohammadjafarinejad Dec 16, 2025
c90547e
Merge remote-tracking branch 'origin/main' into fix/75099
mohammadjafarinejad Dec 16, 2025
1b99b01
Fix: Lazy icons hook as const assertion
mohammadjafarinejad Dec 16, 2025
1fdbfb3
Merge remote-tracking branch 'origin/main' into fix/75099
mohammadjafarinejad Dec 16, 2025
a153031
Remove manual memoization from new header components for React Compil…
mohammadjafarinejad Dec 16, 2025
5a4f9d9
Merge branch 'main' into fix/75099
mohammadjafarinejad Dec 17, 2025
0b4a237
Merge branch 'main' into fix/75099
mohammadjafarinejad Dec 17, 2025
b8f9cc4
Fix Expenses column not expanding properly in grouped search results
mohammadjafarinejad Dec 17, 2025
7c2dd43
fix: withdrawn sorting
mohammadjafarinejad Dec 17, 2025
4f66902
Revert SearchTableHeader to use profileIcon parameter
mohammadjafarinejad Dec 17, 2025
9c385fb
fix: crash on card sections
mohammadjafarinejad Dec 17, 2025
62d8893
fix: add cardFeeds to MoneyRequestReportNavigation
mohammadjafarinejad Dec 17, 2025
b5961ea
fix expenses column style
mohammadjafarinejad Dec 17, 2025
9a9878d
fix: misaligned child columns
mohammadjafarinejad Dec 17, 2025
514d2bf
fix: update sorting logic for member and card data
mohammadjafarinejad Dec 17, 2025
d45c585
fix: reset orderBy in peggy for groupViews
mohammadjafarinejad Dec 17, 2025
80cd7c5
Merge branch 'main' into fix/75099
mohammadjafarinejad Dec 17, 2025
ab3b209
Merge branch 'main' into fix/75099
mohammadjafarinejad Dec 18, 2025
49a6015
Adapt code to changes from main
mohammadjafarinejad Dec 18, 2025
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
1 change: 1 addition & 0 deletions cspell.json
Original file line number Diff line number Diff line change
Expand Up @@ -24,6 +24,7 @@
"alrt",
"Amal",
"Amal's",
"americanexpress",
"americanexpressfdx",
"Amina",
"androiddebugkey",
Expand Down
4 changes: 4 additions & 0 deletions src/CONST/index.ts
Original file line number Diff line number Diff line change
Expand Up @@ -6832,6 +6832,10 @@ const CONST = {
WITHDRAWAL_ID: 'withdrawalID',
AVATAR: 'avatar',
STATUS: 'status',
EXPENSES: 'expenses',
FEED: 'feed',
WITHDRAWN: 'withdrawn',
BANK_ACCOUNT: 'bankAccount',
REPORT_ID: 'reportID',
BASE_62_REPORT_ID: 'base62ReportID',
TAX: 'tax',
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -34,6 +34,8 @@ function MoneyRequestReportNavigation({reportID, shouldDisplayNarrowVersion}: Mo
selector: selectFilteredReportActions,
});

const [cardFeeds] = useOnyx(ONYXKEYS.COLLECTION.SHARED_NVP_PRIVATE_DOMAIN_MEMBER, {canBeMissing: true});

const archivedReportsIdSet = useArchivedReportsIdSet();

const {type, status, sortBy, sortOrder, groupBy} = lastSearchQuery?.queryJSON ?? {};
Expand All @@ -50,6 +52,7 @@ function MoneyRequestReportNavigation({reportID, shouldDisplayNarrowVersion}: Mo
currentSearch: lastSearchQuery?.searchKey,
archivedReportsIDList: archivedReportsIdSet,
isActionLoadingSet,
cardFeeds,
});
results = getSortedSections(type, status ?? '', searchData, localeCompare, translate, sortBy, sortOrder, groupBy).map((value) => value.reportID);
}
Expand Down
2 changes: 1 addition & 1 deletion src/components/Search/SearchList/index.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -9,7 +9,7 @@
import type {NativeSyntheticEvent, StyleProp, ViewStyle} from 'react-native';
import Animated, {Easing, FadeOutUp, LinearTransition} from 'react-native-reanimated';
import Checkbox from '@components/Checkbox';
import * as Expensicons from '@components/Icon/Expensicons';

Check warning on line 12 in src/components/Search/SearchList/index.tsx

View workflow job for this annotation

GitHub Actions / Changed files ESLint check

'@components/Icon/Expensicons' import is restricted from being used by a pattern. Direct imports from Icon/Expensicons are deprecated. Please use lazy loading hooks instead. Use `useMemoizedLazyExpensifyIcons` from @hooks/useLazyAsset. See docs/LAZY_ICONS_AND_ILLUSTRATIONS.md for details

Check warning on line 12 in src/components/Search/SearchList/index.tsx

View workflow job for this annotation

GitHub Actions / Changed files ESLint check

'@components/Icon/Expensicons' import is restricted from being used. Direct imports from @components/Icon/Expensicons are deprecated. Please use lazy loading hooks instead. Use `useMemoizedLazyExpensifyIcons` from @hooks/useLazyAsset. See docs/LAZY_ICONS_AND_ILLUSTRATIONS.md for details
import MenuItem from '@components/MenuItem';
import Modal from '@components/Modal';
import {usePersonalDetails} from '@components/OnyxListItemProvider';
Expand Down Expand Up @@ -378,7 +378,7 @@
],
);

const tableHeaderVisible = (canSelectMultiple || !!SearchTableHeader) && (!groupBy || type === CONST.SEARCH.DATA_TYPES.EXPENSE_REPORT);
const tableHeaderVisible = canSelectMultiple || !!SearchTableHeader;
const selectAllButtonVisible = canSelectMultiple && !SearchTableHeader;
const isSelectAllChecked = selectedItemsLength > 0 && selectedItemsLength === flattenedItemsWithoutPendingDelete.length;

Expand Down
32 changes: 24 additions & 8 deletions src/components/Search/index.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -335,6 +335,8 @@ function Search({
selector: selectFilteredReportActions,
});

const [cardFeeds] = useOnyx(ONYXKEYS.COLLECTION.SHARED_NVP_PRIVATE_DOMAIN_MEMBER, {canBeMissing: true});

const {defaultCardFeed} = useCardFeedsForDisplay();
const suggestedSearches = useMemo(() => getSuggestedSearches(accountID, defaultCardFeed?.id), [defaultCardFeed?.id, accountID]);

Expand Down Expand Up @@ -459,9 +461,24 @@ function Search({
archivedReportsIDList: archivedReportsIdSet,
queryJSON,
isActionLoadingSet,
cardFeeds,
});
return [filteredData1, filteredData1.length, allLength];
}, [searchKey, exportReportActions, validGroupBy, isDataLoaded, searchResults, type, archivedReportsIdSet, formatPhoneNumber, accountID, queryJSON, email, isActionLoadingSet]);
}, [
searchKey,
exportReportActions,
validGroupBy,
isDataLoaded,
searchResults,
type,
archivedReportsIdSet,
formatPhoneNumber,
accountID,
queryJSON,
email,
isActionLoadingSet,
cardFeeds,
]);

useEffect(() => {
/** We only want to display the skeleton for the status filters the first time we load them for a specific data type */
Expand Down Expand Up @@ -831,10 +848,10 @@ function Search({
if (!searchResults?.data) {
return [];
}
const columns = getColumnsToShow(accountID, searchResults?.data, visibleColumns, false, searchResults?.search?.type);
const columns = getColumnsToShow(accountID, searchResults?.data, visibleColumns, false, searchResults?.search?.type, validGroupBy);

return (Object.keys(columns) as SearchColumnType[]).filter((col) => columns[col]);
}, [accountID, searchResults?.data, searchResults?.search?.type, visibleColumns]);
}, [accountID, searchResults?.data, searchResults?.search?.type, visibleColumns, validGroupBy]);

const opacity = useSharedValue(1);
const animatedStyle = useAnimatedStyle(() => ({
Expand Down Expand Up @@ -862,7 +879,7 @@ function Search({

const isChat = type === CONST.SEARCH.DATA_TYPES.CHAT;
const isTask = type === CONST.SEARCH.DATA_TYPES.TASK;
const canSelectMultiple = !isChat && !isTask && (!isSmallScreenWidth || isMobileSelectionModeEnabled) && validGroupBy !== CONST.SEARCH.GROUP_BY.WITHDRAWAL_ID;
const canSelectMultiple = !isChat && !isTask && (!isSmallScreenWidth || isMobileSelectionModeEnabled);
const ListItem = getListItem(type, status, validGroupBy);

const sortedSelectedData = useMemo(
Expand Down Expand Up @@ -1046,9 +1063,8 @@ function Search({
isExpenseReportType ?? false,
);
const {shouldShowAmountInWideColumn, shouldShowTaxAmountInWideColumn} = getWideAmountIndicators(searchResults?.data);
const shouldShowSorting = !validGroupBy;
const shouldShowTableHeader = isLargeScreenWidth && !isChat && !validGroupBy;
const tableHeaderVisible = (canSelectMultiple || shouldShowTableHeader) && (!validGroupBy || isExpenseReportType);
const shouldShowTableHeader = isLargeScreenWidth && !isChat;
const tableHeaderVisible = canSelectMultiple || shouldShowTableHeader;

return (
<SearchScopeProvider>
Expand Down Expand Up @@ -1081,7 +1097,7 @@ function Search({
shouldShowYearExported={shouldShowYearExported}
isAmountColumnWide={shouldShowAmountInWideColumn}
isTaxAmountColumnWide={shouldShowTaxAmountInWideColumn}
shouldShowSorting={shouldShowSorting}
shouldShowSorting
areAllOptionalColumnsHidden={areAllOptionalColumnsHidden}
groupBy={validGroupBy}
/>
Expand Down
Original file line number Diff line number Diff line change
@@ -1,12 +1,10 @@
import React, {useMemo} from 'react';
import React from 'react';
import {View} from 'react-native';
import Checkbox from '@components/Checkbox';
import Icon from '@components/Icon';
import * as Expensicons from '@components/Icon/Expensicons';
import {PressableWithFeedback} from '@components/Pressable';
import ReportActionAvatars from '@components/ReportActionAvatars';
import type {ListItem, TransactionCardGroupListItemType} from '@components/SelectionListWithSections/types';
import TextWithTooltip from '@components/TextWithTooltip';
import UserDetailsTooltip from '@components/UserDetailsTooltip';
import useLocalize from '@hooks/useLocalize';
import useResponsiveLayout from '@hooks/useResponsiveLayout';
import useStyleUtils from '@hooks/useStyleUtils';
Expand All @@ -15,6 +13,8 @@ import useThemeStyles from '@hooks/useThemeStyles';
import {getDisplayNameOrDefault} from '@libs/PersonalDetailsUtils';
import CONST from '@src/CONST';
import type {CompanyCardFeed} from '@src/types/onyx/CardFeeds';
import ExpandCollapseArrowButton from './ExpandCollapseArrowButton';
import ExpensesCell from './ExpensesCell';
import TotalCell from './TotalCell';

type CardListItemHeaderProps<TItem extends ListItem> = {
Expand Down Expand Up @@ -62,13 +62,13 @@ function CardListItemHeader<TItem extends ListItem>({
const {isLargeScreenWidth} = useResponsiveLayout();
const StyleUtils = useStyleUtils();
const {translate, formatPhoneNumber} = useLocalize();
const formattedDisplayName = useMemo(() => formatPhoneNumber(getDisplayNameOrDefault(cardItem)), [cardItem, formatPhoneNumber]);
const formattedDisplayName = formatPhoneNumber(getDisplayNameOrDefault(cardItem));
const backgroundColor =
StyleUtils.getItemBackgroundColorStyle(!!cardItem.isSelected, !!isFocused, !!isDisabled, theme.activeComponentBG, theme.hoverComponentBG)?.backgroundColor ?? theme.highlightBG;

return (
<View>
<View style={[styles.pv1Half, styles.pl3, styles.flexRow, styles.alignItemsCenter, styles.justifyContentStart]}>
<View style={[styles.pv1Half, styles.pl3, styles.flexRow, styles.alignItemsCenter, isLargeScreenWidth ? styles.gap3 : styles.justifyContentStart]}>
<View style={[styles.flexRow, styles.alignItemsCenter, styles.mnh40, styles.flex1, styles.gap3]}>
{!!canSelectMultiple && (
<Checkbox
Expand All @@ -77,52 +77,80 @@ function CardListItemHeader<TItem extends ListItem>({
isIndeterminate={isIndeterminate}
disabled={!!isDisabled || cardItem.isDisabledCheckbox}
accessibilityLabel={translate('common.select')}
style={isLargeScreenWidth && styles.mr1}
/>
)}
<View style={[styles.flexRow, styles.flex1, styles.gap3]}>
<ReportActionAvatars
subscriptCardFeed={cardItem.bank as CompanyCardFeed}
subscriptAvatarBorderColor={backgroundColor}
noRightMarginOnSubscriptContainer
accountIDs={[cardItem.accountID]}
/>
<View style={[styles.gapHalf, styles.flexShrink1]}>
<TextWithTooltip
text={formattedDisplayName}
style={[styles.optionDisplayName, styles.sidebarLinkTextBold, styles.pre, styles.fontWeightNormal]}
/>
<TextWithTooltip
text={`${cardItem.cardName}${cardItem.lastFourPAN ? ` ${CONST.DOT_SEPARATOR} ` : ''}${cardItem.lastFourPAN}`}
style={[styles.textLabelSupporting, styles.lh16, styles.pre]}
{!isLargeScreenWidth && (
<View style={[styles.flexRow, styles.flex1, styles.gap3]}>
<ReportActionAvatars
subscriptCardFeed={cardItem.bank as CompanyCardFeed}
subscriptAvatarBorderColor={backgroundColor}
noRightMarginOnSubscriptContainer
accountIDs={[cardItem.accountID]}
/>
</View>
</View>
</View>
<View style={[styles.flexShrink0, styles.mr3, styles.gap1]}>
<TotalCell
total={cardItem.total}
currency={cardItem.currency}
/>
{!isLargeScreenWidth && !!onDownArrowClick && (
<View>
<PressableWithFeedback
onPress={onDownArrowClick}
style={[styles.pl3, styles.justifyContentCenter, styles.alignItemsEnd]}
accessibilityRole={CONST.ROLE.BUTTON}
accessibilityLabel={isExpanded ? CONST.ACCESSIBILITY_LABELS.COLLAPSE : CONST.ACCESSIBILITY_LABELS.EXPAND}
>
{({hovered}) => (
<Icon
src={isExpanded ? Expensicons.UpArrow : Expensicons.DownArrow}
fill={theme.icon}
additionalStyles={!hovered && styles.opacitySemiTransparent}
small
/>
)}
</PressableWithFeedback>
<View style={[styles.gapHalf, styles.flexShrink1]}>
<TextWithTooltip
text={formattedDisplayName}
style={[styles.optionDisplayName, styles.sidebarLinkTextBold, styles.pre, styles.fontWeightNormal]}
/>
<TextWithTooltip
text={`${cardItem.cardName}${cardItem.lastFourPAN ? ` ${CONST.DOT_SEPARATOR} ` : ''}${cardItem.lastFourPAN}`}
style={[styles.textLabelSupporting, styles.lh16, styles.pre]}
/>
</View>
</View>
)}
{isLargeScreenWidth && (
<>
<View style={StyleUtils.getReportTableColumnStyles(CONST.SEARCH.TABLE_COLUMNS.AVATAR)}>
<UserDetailsTooltip accountID={cardItem.accountID}>
<View>
<ReportActionAvatars
subscriptCardFeed={cardItem.bank as CompanyCardFeed}
subscriptAvatarBorderColor={backgroundColor}
noRightMarginOnSubscriptContainer
accountIDs={[cardItem.accountID]}
/>
</View>
</UserDetailsTooltip>
</View>
<View style={StyleUtils.getReportTableColumnStyles(CONST.SEARCH.TABLE_COLUMNS.CARD)}>
<View style={[styles.gapHalf, styles.flexShrink1]}>
<TextWithTooltip text={cardItem.formattedCardName ?? ''} />
</View>
</View>
<View style={StyleUtils.getReportTableColumnStyles(CONST.SEARCH.TABLE_COLUMNS.FEED)}>
<TextWithTooltip
text={cardItem.formattedFeedName ?? ''}
style={[styles.optionDisplayName, styles.lineHeightLarge, styles.pre]}
/>
</View>
<View style={StyleUtils.getReportTableColumnStyles(CONST.SEARCH.TABLE_COLUMNS.EXPENSES)}>
<ExpensesCell count={cardItem.count} />
</View>
<View style={StyleUtils.getReportTableColumnStyles(CONST.SEARCH.TABLE_COLUMNS.TOTAL, false, false, false, false, false, false, false, false, true)}>
<TotalCell
total={cardItem.total}
currency={cardItem.currency}
/>
</View>
</>
)}
</View>
{!isLargeScreenWidth && (
<View style={[[styles.flexShrink0, styles.mr3, styles.gap1]]}>
<TotalCell
total={cardItem.total}
currency={cardItem.currency}
/>
{!!onDownArrowClick && (
<ExpandCollapseArrowButton
isExpanded={isExpanded}
onPress={onDownArrowClick}
/>
)}
</View>
)}
</View>
</View>
);
Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,39 @@
import React from 'react';
import Icon from '@components/Icon';
import {PressableWithFeedback} from '@components/Pressable';
import {useMemoizedLazyExpensifyIcons} from '@hooks/useLazyAsset';
import useTheme from '@hooks/useTheme';
import useThemeStyles from '@hooks/useThemeStyles';
import CONST from '@src/CONST';

type ExpandCollapseArrowButtonProps = {
isExpanded?: boolean;

onPress: () => void;
};

function ExpandCollapseArrowButton({isExpanded, onPress}: ExpandCollapseArrowButtonProps) {
const theme = useTheme();
const styles = useThemeStyles();
const icons = useMemoizedLazyExpensifyIcons(['UpArrow', 'DownArrow']);

return (
<PressableWithFeedback
onPress={onPress}
style={[styles.pl3, styles.justifyContentCenter, styles.alignItemsEnd]}
accessibilityRole={CONST.ROLE.BUTTON}
accessibilityLabel={isExpanded ? CONST.ACCESSIBILITY_LABELS.COLLAPSE : CONST.ACCESSIBILITY_LABELS.EXPAND}
>
{({hovered}) => (
<Icon
src={isExpanded ? icons.UpArrow : icons.DownArrow}
fill={theme.icon}
additionalStyles={!hovered && styles.opacitySemiTransparent}
small
/>
)}
</PressableWithFeedback>
);
}

export default ExpandCollapseArrowButton;
Original file line number Diff line number Diff line change
@@ -0,0 +1,25 @@
import React from 'react';
import TextWithTooltip from '@components/TextWithTooltip';
import useThemeStyles from '@hooks/useThemeStyles';

type ExpensesDataCellProps = {
count: number;

shouldShowTooltip?: boolean;
};

function ExpensesDataCell({count, shouldShowTooltip = true}: ExpensesDataCellProps) {

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I think we should just reuse TextCell here, since this is just a string

Copy link
Copy Markdown
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

const styles = useThemeStyles();

return (
<TextWithTooltip
shouldShowTooltip={shouldShowTooltip}
text={String(count)}
style={[styles.optionDisplayName, styles.lineHeightLarge, styles.pre]}
/>
);
}

ExpensesDataCell.displayName = 'ExpensesDataCell';

export default ExpensesDataCell;
Loading
Loading