Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Show search action buttons v2 #52441

Merged
merged 16 commits into from
Nov 22, 2024
Merged
3 changes: 3 additions & 0 deletions src/CONST.ts
Original file line number Diff line number Diff line change
Expand Up @@ -5964,6 +5964,9 @@ const CONST = {
ACTION_TYPES: {
VIEW: 'view',
REVIEW: 'review',
SUBMIT: 'submit',
APPROVE: 'approve',
PAY: 'pay',
DONE: 'done',
PAID: 'paid',
},
Expand Down
2 changes: 1 addition & 1 deletion src/components/Search/SearchPageHeader.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -182,7 +182,7 @@ function SearchPageHeader({queryJSON, hash}: SearchPageHeaderProps) {
return;
}

const reportIDList = (selectedReports?.filter((report) => !!report) as string[]) ?? [];
const reportIDList = selectedReports?.filter((report) => !!report) ?? [];
SearchActions.exportSearchItemsToCSV(
{query: status, jsonQuery: JSON.stringify(queryJSON), reportIDList, transactionIDList: selectedTransactionsKeys, policyIDs: [activeWorkspaceID ?? '']},
() => {
Expand Down
8 changes: 8 additions & 0 deletions src/components/Search/types.ts
Original file line number Diff line number Diff line change
Expand Up @@ -24,6 +24,13 @@ type SelectedTransactionInfo = {
/** Model of selected results */
type SelectedTransactions = Record<string, SelectedTransactionInfo>;

/** Model of payment data used by Search bulk actions */
type PaymentData = {
reportID: string;
amount: number;
paymentType: ValueOf<typeof CONST.IOU.PAYMENT_TYPE>;
};

type SortOrder = ValueOf<typeof CONST.SEARCH.SORT_ORDER>;
type SearchColumnType = ValueOf<typeof CONST.SEARCH.TABLE_COLUMNS>;
type ExpenseSearchStatus = ValueOf<typeof CONST.SEARCH.STATUS.EXPENSE>;
Expand Down Expand Up @@ -117,5 +124,6 @@ export type {
TripSearchStatus,
ChatSearchStatus,
SearchAutocompleteResult,
PaymentData,
SearchAutocompleteQueryRange,
};
43 changes: 29 additions & 14 deletions src/components/SelectionList/Search/ActionCell.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -4,6 +4,7 @@ import Badge from '@components/Badge';
import Button from '@components/Button';
import * as Expensicons from '@components/Icon/Expensicons';
import useLocalize from '@hooks/useLocalize';
import useNetwork from '@hooks/useNetwork';
import useStyleUtils from '@hooks/useStyleUtils';
import useTheme from '@hooks/useTheme';
import useThemeStyles from '@hooks/useThemeStyles';
Expand All @@ -15,6 +16,9 @@ import type {SearchTransactionAction} from '@src/types/onyx/SearchResults';
const actionTranslationsMap: Record<SearchTransactionAction, TranslationPaths> = {
view: 'common.view',
review: 'common.review',
submit: 'common.submit',
approve: 'iou.approve',
pay: 'iou.pay',
done: 'common.done',
paid: 'iou.settledExpensify',
};
Expand All @@ -26,13 +30,23 @@ type ActionCellProps = {
goToItem: () => void;
isChildListItem?: boolean;
parentAction?: string;
isLoading?: boolean;
};

function ActionCell({action = CONST.SEARCH.ACTION_TYPES.VIEW, isLargeScreenWidth = true, isSelected = false, goToItem, isChildListItem = false, parentAction = ''}: ActionCellProps) {
function ActionCell({
action = CONST.SEARCH.ACTION_TYPES.VIEW,
isLargeScreenWidth = true,
isSelected = false,
goToItem,
isChildListItem = false,
parentAction = '',
isLoading = false,
}: ActionCellProps) {
const {translate} = useLocalize();
const theme = useTheme();
const styles = useThemeStyles();
const StyleUtils = useStyleUtils();
const {isOffline} = useNetwork();

const text = translate(actionTranslationsMap[action]);

Expand Down Expand Up @@ -61,9 +75,8 @@ function ActionCell({action = CONST.SEARCH.ACTION_TYPES.VIEW, isLargeScreenWidth
);
}

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

if (action === CONST.SEARCH.ACTION_TYPES.VIEW || shouldUseViewAction) {
const buttonInnerStyles = isSelected ? styles.buttonDefaultHovered : {};
return isLargeScreenWidth ? (
<Button
text={translate(actionTranslationsMap[CONST.SEARCH.ACTION_TYPES.VIEW])}
Expand All @@ -77,17 +90,19 @@ function ActionCell({action = CONST.SEARCH.ACTION_TYPES.VIEW, isLargeScreenWidth
) : null;
}

if (action === CONST.SEARCH.ACTION_TYPES.REVIEW) {
return (
<Button
text={text}
onPress={goToItem}
small
style={[styles.w100]}
innerStyles={buttonInnerStyles}
/>
);
}
const buttonInnerStyles = isSelected ? styles.buttonSuccessHovered : {};
return (
<Button
text={text}
onPress={goToItem}
small
style={[styles.w100]}
innerStyles={buttonInnerStyles}
isLoading={isLoading}
success
isDisabled={isOffline}
/>
);
}

ActionCell.displayName = 'ActionCell';
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -28,6 +28,7 @@ type ExpenseItemHeaderNarrowProps = {
isDisabled?: boolean | null;
isDisabledCheckbox?: boolean;
handleCheckboxPress?: () => void;
isLoading?: boolean;
};

function ExpenseItemHeaderNarrow({
Expand All @@ -44,6 +45,7 @@ function ExpenseItemHeaderNarrow({
isDisabled,
handleCheckboxPress,
text,
isLoading = false,
}: ExpenseItemHeaderNarrowProps) {
const styles = useThemeStyles();
const StyleUtils = useStyleUtils();
Expand Down Expand Up @@ -102,6 +104,7 @@ function ExpenseItemHeaderNarrow({
goToItem={onButtonPress}
isLargeScreenWidth={false}
isSelected={isSelected}
isLoading={isLoading}
/>
</View>
</View>
Expand Down
7 changes: 6 additions & 1 deletion src/components/SelectionList/Search/ReportListItem.tsx
Original file line number Diff line number Diff line change
@@ -1,6 +1,7 @@
import React from 'react';
import {View} from 'react-native';
import Checkbox from '@components/Checkbox';
import {useSearchContext} from '@components/Search/SearchContext';
import BaseListItem from '@components/SelectionList/BaseListItem';
import type {ListItem, ReportListItemProps, ReportListItemType, TransactionListItemType} from '@components/SelectionList/types';
import Text from '@components/Text';
Expand All @@ -10,6 +11,7 @@ import useResponsiveLayout from '@hooks/useResponsiveLayout';
import useStyleUtils from '@hooks/useStyleUtils';
import useTheme from '@hooks/useTheme';
import useThemeStyles from '@hooks/useThemeStyles';
import {handleActionButtonPress} from '@libs/actions/Search';
import * as CurrencyUtils from '@libs/CurrencyUtils';
import Navigation from '@libs/Navigation/Navigation';
import variables from '@styles/variables';
Expand Down Expand Up @@ -69,6 +71,7 @@ function ReportListItem<TItem extends ListItem>({
const styles = useThemeStyles();
const {isLargeScreenWidth} = useResponsiveLayout();
const StyleUtils = useStyleUtils();
const {currentSearchHash} = useSearchContext();

const animatedHighlightStyle = useAnimatedHighlightStyle({
borderRadius: variables.componentBorderRadius,
Expand All @@ -93,7 +96,7 @@ function ReportListItem<TItem extends ListItem>({
];

const handleOnButtonPress = () => {
onSelectRow(item);
handleActionButtonPress(currentSearchHash, reportItem, () => onSelectRow(item));
};

const openReportInRHP = (transactionItem: TransactionListItemType) => {
Expand Down Expand Up @@ -165,6 +168,7 @@ function ReportListItem<TItem extends ListItem>({
action={reportItem.action}
onButtonPress={handleOnButtonPress}
containerStyle={[styles.ph3, styles.pt1half, styles.mb1half]}
isLoading={reportItem.isActionLoading}
/>
)}
<View style={[styles.flex1, styles.flexRow, styles.alignItemsCenter, styles.gap3, styles.ph3, styles.pv1half]}>
Expand Down Expand Up @@ -199,6 +203,7 @@ function ReportListItem<TItem extends ListItem>({
action={reportItem.action}
goToItem={handleOnButtonPress}
isSelected={item.isSelected}
isLoading={reportItem.isActionLoading}
/>
</View>
)}
Expand Down
Original file line number Diff line number Diff line change
@@ -1,10 +1,12 @@
import React from 'react';
import {useSearchContext} from '@components/Search/SearchContext';
import BaseListItem from '@components/SelectionList/BaseListItem';
import type {ListItem, TransactionListItemProps, TransactionListItemType} from '@components/SelectionList/types';
import useAnimatedHighlightStyle from '@hooks/useAnimatedHighlightStyle';
import useResponsiveLayout from '@hooks/useResponsiveLayout';
import useTheme from '@hooks/useTheme';
import useThemeStyles from '@hooks/useThemeStyles';
import {handleActionButtonPress} from '@libs/actions/Search';
import variables from '@styles/variables';
import TransactionListItemRow from './TransactionListItemRow';

Expand All @@ -26,6 +28,7 @@ function TransactionListItem<TItem extends ListItem>({
const theme = useTheme();

const {isLargeScreenWidth} = useResponsiveLayout();
const {currentSearchHash} = useSearchContext();

const listItemPressableStyle = [
styles.selectionListPressableItemWrapper,
Expand Down Expand Up @@ -75,13 +78,14 @@ function TransactionListItem<TItem extends ListItem>({
item={transactionItem}
showTooltip={showTooltip}
onButtonPress={() => {
onSelectRow(item);
handleActionButtonPress(currentSearchHash, transactionItem, () => onSelectRow(item));
}}
onCheckboxPress={() => onCheckboxPress?.(item)}
isDisabled={!!isDisabled}
canSelectMultiple={!!canSelectMultiple}
isButtonSelected={item.isSelected}
shouldShowTransactionCheckbox={false}
isLoading={transactionItem.isActionLoading}
/>
</BaseListItem>
);
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -59,6 +59,7 @@ type TransactionListItemRowProps = {
isButtonSelected?: boolean;
parentAction?: string;
shouldShowTransactionCheckbox?: boolean;
isLoading?: boolean;
};

const getTypeIcon = (type?: SearchTransactionType) => {
Expand Down Expand Up @@ -257,6 +258,7 @@ function TransactionListItemRow({
isButtonSelected = false,
parentAction = '',
shouldShowTransactionCheckbox,
isLoading = false,
}: TransactionListItemRowProps) {
const styles = useThemeStyles();
const {isLargeScreenWidth} = useResponsiveLayout();
Expand All @@ -280,6 +282,7 @@ function TransactionListItemRow({
isDisabled={item.isDisabled}
isDisabledCheckbox={item.isDisabledCheckbox}
handleCheckboxPress={onCheckboxPress}
isLoading={isLoading}
/>
)}

Expand Down Expand Up @@ -445,6 +448,7 @@ function TransactionListItemRow({
isChildListItem={isChildListItem}
parentAction={parentAction}
goToItem={onButtonPress}
isLoading={isLoading}
/>
</View>
</View>
Expand Down
Empty file.
5 changes: 2 additions & 3 deletions src/libs/API/parameters/PayMoneyRequestOnSearchParams.ts
Original file line number Diff line number Diff line change
@@ -1,11 +1,10 @@
type PayMoneyRequestOnSearchParams = {
hash: number;
paymentType: string;
/**
* Stringified JSON object with type of following structure:
* Array<{reportID: string, amount: number}>
* Array<{reportID: string, amount: number, paymentType: string}>
*/
reportsAndAmounts: string;
paymentData: string;
};

export default PayMoneyRequestOnSearchParams;
11 changes: 6 additions & 5 deletions src/libs/PolicyUtils.ts
Original file line number Diff line number Diff line change
Expand Up @@ -29,6 +29,7 @@ import type {
Tenant,
} from '@src/types/onyx/Policy';
import type PolicyEmployee from '@src/types/onyx/PolicyEmployee';
import type {SearchPolicy} from '@src/types/onyx/SearchResults';
import {isEmptyObject} from '@src/types/utils/EmptyObject';
import {hasSynchronizationErrorMessage} from './actions/connections';
import {getCategoryApproverRule} from './CategoryUtils';
Expand Down Expand Up @@ -184,7 +185,7 @@ function getPolicyBrickRoadIndicatorStatus(policy: OnyxEntry<Policy>, isConnecti
return undefined;
}

function getPolicyRole(policy: OnyxInputOrEntry<Policy>, currentUserLogin: string | undefined) {
function getPolicyRole(policy: OnyxInputOrEntry<Policy> | SearchPolicy, currentUserLogin: string | undefined) {
return policy?.role ?? policy?.employeeList?.[currentUserLogin ?? '-1']?.role;
}

Expand Down Expand Up @@ -218,7 +219,7 @@ const isUserPolicyAdmin = (policy: OnyxInputOrEntry<Policy>, login?: string) =>
/**
* Checks if the current user is an admin of the policy.
*/
const isPolicyAdmin = (policy: OnyxInputOrEntry<Policy>, currentUserLogin?: string): boolean => getPolicyRole(policy, currentUserLogin) === CONST.POLICY.ROLE.ADMIN;
const isPolicyAdmin = (policy: OnyxInputOrEntry<Policy> | SearchPolicy, currentUserLogin?: string): boolean => getPolicyRole(policy, currentUserLogin) === CONST.POLICY.ROLE.ADMIN;

/**
* Checks if the current user is of the role "user" on the policy.
Expand Down Expand Up @@ -379,7 +380,7 @@ function isPendingDeletePolicy(policy: OnyxEntry<Policy>): boolean {
return policy?.pendingAction === CONST.RED_BRICK_ROAD_PENDING_ACTION.DELETE;
}

function isPaidGroupPolicy(policy: OnyxEntry<Policy>): boolean {
function isPaidGroupPolicy(policy: OnyxEntry<Policy> | SearchPolicy): boolean {
return policy?.type === CONST.POLICY.TYPE.TEAM || policy?.type === CONST.POLICY.TYPE.CORPORATE;
}

Expand All @@ -404,7 +405,7 @@ function isTaxTrackingEnabled(isPolicyExpenseChat: boolean, policy: OnyxEntry<Po
* Checks if policy's scheduled submit / auto reporting frequency is "instant".
* Note: Free policies have "instant" submit always enabled.
*/
function isInstantSubmitEnabled(policy: OnyxInputOrEntry<Policy>): boolean {
function isInstantSubmitEnabled(policy: OnyxInputOrEntry<Policy> | SearchPolicy): boolean {
return policy?.autoReporting === true && policy?.autoReportingFrequency === CONST.POLICY.AUTO_REPORTING_FREQUENCIES.INSTANT;
}

Expand Down Expand Up @@ -434,7 +435,7 @@ function getCorrectedAutoReportingFrequency(policy: OnyxInputOrEntry<Policy>): V
/**
* Checks if policy's approval mode is "optional", a.k.a. "Submit & Close"
*/
function isSubmitAndClose(policy: OnyxInputOrEntry<Policy>): boolean {
function isSubmitAndClose(policy: OnyxInputOrEntry<Policy> | SearchPolicy): boolean {
return policy?.approvalMode === CONST.POLICY.APPROVAL_MODE.OPTIONAL;
}

Expand Down
Loading
Loading