Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
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
12 changes: 12 additions & 0 deletions src/CONST/index.ts
Original file line number Diff line number Diff line change
Expand Up @@ -7763,6 +7763,7 @@ const CONST = {
WEEK: 'week',
YEAR: 'year',
QUARTER: 'quarter',
APPROVER: 'approver',
},
/**
* Single source of truth for which transaction-level columns are available in each column-picker surface:
Expand Down Expand Up @@ -8085,6 +8086,12 @@ const CONST = {
EXPENSES: this.TABLE_COLUMNS.GROUP_EXPENSES,
TOTAL: this.TABLE_COLUMNS.GROUP_TOTAL,
},
APPROVER: {
AVATAR: this.TABLE_COLUMNS.AVATAR,
APPROVER: this.TABLE_COLUMNS.GROUP_APPROVER,
EXPENSES: this.TABLE_COLUMNS.GROUP_EXPENSES,
TOTAL: this.TABLE_COLUMNS.GROUP_TOTAL,
},
};
},
get TYPE_DEFAULT_COLUMNS() {
Expand Down Expand Up @@ -8138,6 +8145,7 @@ const CONST = {
WEEK: [this.TABLE_COLUMNS.GROUP_WEEK, this.TABLE_COLUMNS.GROUP_EXPENSES, this.TABLE_COLUMNS.GROUP_TOTAL],
YEAR: [this.TABLE_COLUMNS.GROUP_YEAR, this.TABLE_COLUMNS.GROUP_EXPENSES, this.TABLE_COLUMNS.GROUP_TOTAL],
QUARTER: [this.TABLE_COLUMNS.GROUP_QUARTER, this.TABLE_COLUMNS.GROUP_EXPENSES, this.TABLE_COLUMNS.GROUP_TOTAL],
APPROVER: [this.TABLE_COLUMNS.AVATAR, this.TABLE_COLUMNS.GROUP_APPROVER, this.TABLE_COLUMNS.GROUP_EXPENSES, this.TABLE_COLUMNS.GROUP_TOTAL],
};
},
BOOLEAN: {
Expand Down Expand Up @@ -8257,6 +8265,7 @@ const CONST = {
GROUP_WITHDRAWAL_STATUS: 'groupWithdrawalStatus',
GROUP_AMOUNT_DEBITED: 'groupAmountDebited',
GROUP_AMOUNT_REIMBURSED: 'groupAmountReimbursed',
GROUP_APPROVER: 'groupApprover',
},
SYNTAX_OPERATORS: {
AND: 'and',
Expand Down Expand Up @@ -8362,6 +8371,7 @@ const CONST = {
SUBMITTER_USER_ID: 'submitterUserID',
SUBMITTER_PAYROLL_ID: 'submitterPayrollID',
ORDER_DEAL_NUMBERS: 'orderDealNumbers',
APPROVER: 'approver',
// The Spend footer's display selections. They travel as filters because that is where the backend reads
// them from, but they never narrow the result set: only `footerTotal` changes the response, by swapping
// which aggregate comes back as the search total.
Expand Down Expand Up @@ -8451,6 +8461,7 @@ const CONST = {
SUBMITTER_USER_ID: 'submitter-user-id',
SUBMITTER_PAYROLL_ID: 'submitter-payroll-id',
ORDER_DEAL_NUMBERS: 'order-deal-numbers',
APPROVER: 'approver',
COLUMNS: 'columns',
LIMIT: 'limit',
FOOTER_COUNT: 'footer-count',
Expand Down Expand Up @@ -8527,6 +8538,7 @@ const CONST = {
[this.TABLE_COLUMNS.GROUP_WITHDRAWAL_STATUS]: 'group-withdrawal-status',
[this.TABLE_COLUMNS.GROUP_AMOUNT_DEBITED]: 'group-amount-debited',
[this.TABLE_COLUMNS.GROUP_AMOUNT_REIMBURSED]: 'group-amount-reimbursed',
[this.TABLE_COLUMNS.GROUP_APPROVER]: 'group-approver',
[this.TABLE_COLUMNS.AMOUNT_DEBITED]: 'amount-debited',
[this.TABLE_COLUMNS.AMOUNT_REIMBURSED]: 'amount-reimbursed',
};
Expand Down
192 changes: 192 additions & 0 deletions src/components/Search/SearchList/ListItem/ApproverListItemHeader.tsx
Original file line number Diff line number Diff line change
@@ -0,0 +1,192 @@
import UserAvatar from '@components/Avatar/UserAvatar';
import Checkbox from '@components/Checkbox';
import type {SearchColumnType} from '@components/Search/types';
import type {ListItem} from '@components/SelectionList/types';
import TextWithTooltip from '@components/TextWithTooltip';
import UserDetailsTooltip from '@components/UserDetailsTooltip';

import useLocalize from '@hooks/useLocalize';
import useStyleUtils from '@hooks/useStyleUtils';
import useThemeStyles from '@hooks/useThemeStyles';

import {temporaryGetDisplayNameOrDefault} from '@libs/PersonalDetailsUtils';

import CONST from '@src/CONST';

import type {ReactNode} from 'react';

import React from 'react';
import {View} from 'react-native';

import type {TransactionApproverGroupListItemType} from './types';

import ExpandCollapseArrowButton from './ExpandCollapseArrowButton';
import TextCell from './TextCell';
import TotalCell from './TotalCell';

type ApproverListItemHeaderProps = {
approver: TransactionApproverGroupListItemType;
onCheckboxPress?: (item: ListItem) => void;

/** Whether this section items disabled for selection */
isDisabled?: boolean | null;

/** Whether selecting multiple transactions at once is allowed */
canSelectMultiple: boolean | undefined;

/** Whether all transactions are selected */
isSelectAllChecked?: boolean;

/** Whether only some transactions are selected */
isIndeterminate?: boolean;

onDownArrowClick?: () => void;

/** Whether the down arrow is expanded */
isExpanded?: boolean;

/** The visible columns for the header */
columns?: SearchColumnType[];

isLargeScreenWidth?: boolean;
};

/**
* Kept non-generic so OXC's React Compiler can memoize the component.
* OXC bails on type params inside components ("Unsupported declaration type for hoisting").
*/
function ApproverListItemHeader({
approver: approverItem,
onCheckboxPress,
isDisabled,
canSelectMultiple,
isSelectAllChecked,
isIndeterminate,
isExpanded,
onDownArrowClick,
columns,
isLargeScreenWidth,
}: ApproverListItemHeaderProps) {
const styles = useThemeStyles();
const StyleUtils = useStyleUtils();
const {translate, formatPhoneNumber} = useLocalize();
const formattedDisplayName = temporaryGetDisplayNameOrDefault({
passedPersonalDetails: approverItem,
translate,
formatPhoneNumber,
});
const formattedLogin = formatPhoneNumber(approverItem.login ?? '');

const columnComponents: Partial<Record<SearchColumnType, ReactNode>> = {
[CONST.SEARCH.TABLE_COLUMNS.AVATAR]: (
<View
key={CONST.SEARCH.TABLE_COLUMNS.AVATAR}
style={StyleUtils.getReportTableColumnStyles(CONST.SEARCH.TABLE_COLUMNS.AVATAR)}
>
<UserDetailsTooltip accountID={approverItem.accountID}>
<View>
<UserAvatar
source={approverItem.avatar}
accountID={approverItem.accountID}
size={CONST.AVATAR_SIZE.SMALL}
/>
</View>
</UserDetailsTooltip>
</View>
),
[CONST.SEARCH.TABLE_COLUMNS.GROUP_APPROVER]: (
<View
key={CONST.SEARCH.TABLE_COLUMNS.GROUP_APPROVER}
style={StyleUtils.getReportTableColumnStyles(CONST.SEARCH.TABLE_COLUMNS.GROUP_APPROVER)}
>
<View style={styles.flexShrink1}>
<TextWithTooltip
text={formattedDisplayName}
style={[styles.optionDisplayName, styles.sidebarLinkTextBold, styles.pre, styles.fontWeightNormal]}
/>
<TextWithTooltip
text={formattedLogin || formattedDisplayName}
style={[styles.textLabelSupporting, styles.lh16, styles.pre]}
/>
</View>
</View>
),
[CONST.SEARCH.TABLE_COLUMNS.GROUP_EXPENSES]: (
<View
key={CONST.SEARCH.TABLE_COLUMNS.GROUP_EXPENSES}
style={StyleUtils.getReportTableColumnStyles(CONST.SEARCH.TABLE_COLUMNS.EXPENSES)}
>
<TextCell text={String(approverItem.count)} />
</View>
),
[CONST.SEARCH.TABLE_COLUMNS.GROUP_TOTAL]: (
<View
key={CONST.SEARCH.TABLE_COLUMNS.GROUP_TOTAL}
style={StyleUtils.getReportTableColumnStyles(CONST.SEARCH.TABLE_COLUMNS.TOTAL, {shouldRemoveTotalColumnFlex: true})}
>
<TotalCell
total={approverItem.total}
currency={approverItem.currency}
/>
</View>
),
};

return (
<View>
<View style={[styles.flexRow, styles.alignItemsCenter, isLargeScreenWidth ? [styles.pl3, styles.pv1, styles.gap3] : [styles.p4, styles.gap3]]}>
<View style={[styles.flexRow, styles.alignItemsCenter, styles.mnh40, styles.flex1, styles.gap3]}>
{!!canSelectMultiple && (
<Checkbox
onPress={() => onCheckboxPress?.(approverItem)}
isChecked={isSelectAllChecked}
isIndeterminate={isIndeterminate}
disabled={!!isDisabled || approverItem.isDisabledCheckbox}
accessibilityLabel={translate('common.select')}
containerStyle={styles.m0}
/>
)}
{!isLargeScreenWidth && (
<View style={[styles.flexRow, styles.flex1, styles.gap3]}>
<UserDetailsTooltip accountID={approverItem.accountID}>
<View>
<UserAvatar
source={approverItem.avatar}
accountID={approverItem.accountID}
/>
</View>
</UserDetailsTooltip>
<View style={[styles.gap1, styles.flexShrink1]}>
<TextWithTooltip
text={formattedDisplayName}
style={[styles.optionDisplayName, styles.sidebarLinkTextBold, styles.pre, styles.fontWeightNormal]}
/>
<TextWithTooltip
text={formattedLogin || formattedDisplayName}
style={[styles.textLabelSupporting, styles.lh16, styles.pre]}
/>
</View>
</View>
)}
{!!isLargeScreenWidth && columns?.map((column) => columnComponents[column])}
</View>
{!isLargeScreenWidth && (
<View style={[styles.flexShrink0, styles.flexRow, styles.alignItemsCenter]}>
<TotalCell
total={approverItem.total}
currency={approverItem.currency}
/>
{!!onDownArrowClick && (
<ExpandCollapseArrowButton
isExpanded={isExpanded}
onPress={onDownArrowClick}
/>
)}
</View>
)}
</View>
</View>
);
}

export default ApproverListItemHeader;
9 changes: 9 additions & 0 deletions src/components/Search/SearchList/ListItem/GroupHeader.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -46,6 +46,7 @@ import Animated from 'react-native-reanimated';

import type {GroupHeaderItemType, SearchListActionProps, SearchListItem, TransactionListItemType} from './types';

import ApproverListItemHeader from './ApproverListItemHeader';
import CardListItemHeader from './CardListItemHeader';
import CategoryListItemHeader from './CategoryListItemHeader';
import DayListItemHeader from './DayListItemHeader';
Expand Down Expand Up @@ -322,6 +323,14 @@ function GroupHeader({
{...commonProps}
/>
);
case CONST.SEARCH.GROUP_BY.APPROVER:
return (
<ApproverListItemHeader
approver={groupItem}
{...commonProps}
isLargeScreenWidth={isLargeScreenWidth}
/>
);
default:
return null;
}
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -22,7 +22,7 @@ import type {TransactionPreviewData} from '@libs/actions/Search';
import getNonEmptyStringOnyxID from '@libs/getNonEmptyStringOnyxID';
import type {ModifiedMouseEvent} from '@libs/Navigation/helpers/openInternalRouteInNewTab';
import {getLoginByAccountID} from '@libs/PersonalDetailsUtils';
import {isCashBackWithdrawalGroup, isTransactionDayGroupListItemType} from '@libs/SearchUIUtils';
import {isCashBackWithdrawalGroup, isTransactionApproverGroupListItemType, isTransactionDayGroupListItemType} from '@libs/SearchUIUtils';
import {COPYABLE_ROW_DATA_SET} from '@libs/SelectionScraper';
import {getVisibleTransactionViolations, isTransactionPendingDelete} from '@libs/TransactionUtils';

Expand Down Expand Up @@ -59,6 +59,7 @@ import type {
TransactionYearGroupListItemType,
} from './types';

import ApproverListItemHeader from './ApproverListItemHeader';
import CardListItemHeader from './CardListItemHeader';
import CategoryListItemHeader from './CategoryListItemHeader';
import DayListItemHeader from './DayListItemHeader';
Expand Down Expand Up @@ -482,6 +483,25 @@ function TransactionGroupListItemImpl({
isExpanded={isExpanded}
/>
),
[CONST.SEARCH.GROUP_BY.APPROVER]: (() => {
if (!isTransactionApproverGroupListItemType(groupItem)) {
return null;
}
return (
<ApproverListItemHeader
approver={groupItem}
onCheckboxPress={handleSelectionButtonPress}
isDisabled={isDisabledOrEmpty}
columns={columns}
canSelectMultiple={canSelectMultiple}
isSelectAllChecked={isSelectAllChecked}
isIndeterminate={isIndeterminate}
onDownArrowClick={onExpandIconPress}
isExpanded={isExpanded}
isLargeScreenWidth={isLargeScreenWidth}
/>
);
})(),
};

if (searchType === CONST.SEARCH.DATA_TYPES.EXPENSE_REPORT) {
Expand Down
9 changes: 9 additions & 0 deletions src/components/Search/SearchList/ListItem/types.ts
Original file line number Diff line number Diff line change
Expand Up @@ -23,6 +23,7 @@ import type {
import type {Attendee} from '@src/types/onyx/IOU';
import type {Errors, Icon} from '@src/types/onyx/OnyxCommon';
import type {
SearchApproverGroup,
SearchCardGroup,
SearchCategoryGroup,
SearchDataTypes,
Expand Down Expand Up @@ -494,6 +495,12 @@ type TransactionQuarterGroupListItemType = TransactionGroupListItemType & {group
sortKey: number;
};

type TransactionApproverGroupListItemType = TransactionGroupListItemType & {groupedBy: typeof CONST.SEARCH.GROUP_BY.APPROVER} & PersonalDetails &
SearchApproverGroup & {
/** Final and formatted approver name used for displaying and sorting */
formattedApprover?: string;
};

type TransactionListItemProps<TItem extends ListItem> = ListItemProps<TItem> &
SearchListActionProps & {
/** Whether the item's action is loading */
Expand Down Expand Up @@ -566,6 +573,7 @@ type GroupHeaderItemType =
| (TransactionWeekGroupListItemType & GroupHeaderListItemType)
| (TransactionYearGroupListItemType & GroupHeaderListItemType)
| (TransactionQuarterGroupListItemType & GroupHeaderListItemType)
| (TransactionApproverGroupListItemType & GroupHeaderListItemType)
| (TransactionGroupListItemType & GroupHeaderListItemType);

type GroupChildrenContainerItemType = TransactionGroupListItemType & {
Expand Down Expand Up @@ -623,6 +631,7 @@ export type {
TransactionWeekGroupListItemType,
TransactionYearGroupListItemType,
TransactionQuarterGroupListItemType,
TransactionApproverGroupListItemType,
TransactionWithdrawalIDGroupListItemType,
TransactionGroupListItemProps,
TransactionGroupListExpandedProps,
Expand Down
15 changes: 15 additions & 0 deletions src/components/Search/SearchTableHeader.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -513,6 +513,21 @@ const getTransactionGroupHeaders = (groupBy: SearchGroupBy, icons: SearchHeaderI
},
...commonGroupHeaders,
];
case CONST.SEARCH.GROUP_BY.APPROVER:
return [
{
columnName: CONST.SEARCH.TABLE_COLUMNS.AVATAR,
translationKey: undefined,
icon: icons.Profile,
isColumnSortable: false,
},
{
columnName: CONST.SEARCH.TABLE_COLUMNS.GROUP_APPROVER,
translationKey: 'search.filters.groupBy.approver',
isColumnSortable: true,
},
...commonGroupHeaders,
];
default:
return [];
}
Expand Down
5 changes: 5 additions & 0 deletions src/components/Search/chartGroupByConfig.ts
Original file line number Diff line number Diff line change
Expand Up @@ -132,6 +132,11 @@ const CHART_GROUP_BY_CONFIG: Record<SearchGroupBy, ChartGroupByConfig> = {
},
getDateRange: (item: GroupedItem) => (item.groupedBy === CONST.SEARCH.GROUP_BY.QUARTER ? DateUtils.getQuarterDateRange(item.year, item.quarter) : undefined),
},
[CONST.SEARCH.GROUP_BY.APPROVER]: {
titleIconName: 'Users',
getLabel: (item: GroupedItem) => (item.groupedBy === CONST.SEARCH.GROUP_BY.APPROVER ? (item.formattedApprover ?? '') : ''),
getFilterQuery: (item: GroupedItem) => (item.groupedBy === CONST.SEARCH.GROUP_BY.APPROVER ? `${CONST.SEARCH.GROUP_BY.APPROVER}:${item.accountID}` : ''),
},
};

export default CHART_GROUP_BY_CONFIG;
Loading
Loading