Skip to content

Commit

Permalink
Merge pull request #55095 from parasharrajat/parasharrajat/viewtransa…
Browse files Browse the repository at this point in the history
…ctions

Add view transactions cta to Expensify card and company cards page
  • Loading branch information
mountiny authored Jan 22, 2025
2 parents fb5ace6 + 3238b9b commit 4dc7c7a
Show file tree
Hide file tree
Showing 8 changed files with 219 additions and 77 deletions.
1 change: 1 addition & 0 deletions src/languages/en.ts
Original file line number Diff line number Diff line change
Expand Up @@ -2659,6 +2659,7 @@ const translations = {
planType: 'Plan type',
submitExpense: 'Submit expenses using your workspace chat below:',
defaultCategory: 'Default category',
viewTransactions: 'View transactions',
},
perDiem: {
subtitle: 'Set per diem rates to control daily employee spend. ',
Expand Down
1 change: 1 addition & 0 deletions src/languages/es.ts
Original file line number Diff line number Diff line change
Expand Up @@ -2683,6 +2683,7 @@ const translations = {
planType: 'Tipo de plan',
submitExpense: 'Envíe los gastos utilizando el chat de su espacio de trabajo:',
defaultCategory: 'Categoría predeterminada',
viewTransactions: 'Ver transacciones',
},
perDiem: {
subtitle: 'Establece las tasas per diem para controlar los gastos diarios de los empleados. ',
Expand Down
14 changes: 11 additions & 3 deletions src/libs/SearchQueryUtils.ts
Original file line number Diff line number Diff line change
Expand Up @@ -624,14 +624,22 @@ function buildCannedSearchQuery({
type = CONST.SEARCH.DATA_TYPES.EXPENSE,
status = CONST.SEARCH.STATUS.EXPENSE.ALL,
policyID,
cardID,
}: {
type?: SearchDataTypes;
status?: SearchStatus;
policyID?: string;
cardID?: string;
} = {}): SearchQueryString {
const queryString = policyID
? `type:${type} status:${Array.isArray(status) ? status.join(',') : status} policyID:${policyID}`
: `type:${type} status:${Array.isArray(status) ? status.join(',') : status}`;
let queryString = `type:${type} status:${Array.isArray(status) ? status.join(',') : status}`;

if (policyID) {
queryString = `type:${type} status:${Array.isArray(status) ? status.join(',') : status} policyID:${policyID}`;
}

if (cardID) {
queryString = `type:${type} status:${Array.isArray(status) ? status.join(',') : status} expense-type:card card:${cardID}`;
}

// Parse the query to fill all default query fields with values
const normalizedQueryJSON = buildSearchQueryJSON(queryString);
Expand Down
45 changes: 29 additions & 16 deletions src/pages/settings/Wallet/ExpensifyCardPage.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -16,17 +16,18 @@ import useBeforeRemove from '@hooks/useBeforeRemove';
import useLocalize from '@hooks/useLocalize';
import useNetwork from '@hooks/useNetwork';
import useThemeStyles from '@hooks/useThemeStyles';
import * as FormActions from '@libs/actions/FormActions';
import {setDraftValues} from '@libs/actions/FormActions';
import {requestValidateCodeAction} from '@libs/actions/User';
import * as CardUtils from '@libs/CardUtils';
import * as CurrencyUtils from '@libs/CurrencyUtils';
import * as GetPhysicalCardUtils from '@libs/GetPhysicalCardUtils';
import {formatCardExpiration, getDomainCards, maskCard} from '@libs/CardUtils';
import {convertToDisplayString} from '@libs/CurrencyUtils';
import {getUpdatedDraftValues, getUpdatedPrivatePersonalDetails, goToNextPhysicalCardRoute} from '@libs/GetPhysicalCardUtils';
import Navigation from '@libs/Navigation/Navigation';
import type {PlatformStackScreenProps} from '@libs/Navigation/PlatformStackNavigation/types';
import type {SettingsNavigatorParamList} from '@libs/Navigation/types';
import {buildCannedSearchQuery} from '@libs/SearchQueryUtils';
import NotFoundPage from '@pages/ErrorPage/NotFoundPage';
import * as Card from '@userActions/Card';
import * as Link from '@userActions/Link';
import {revealVirtualCardDetails} from '@userActions/Card';
import {openOldDotLink} from '@userActions/Link';
import CONST from '@src/CONST';
import type {TranslationPaths} from '@src/languages/types';
import ONYXKEYS from '@src/ONYXKEYS';
Expand Down Expand Up @@ -81,7 +82,7 @@ function ExpensifyCardPage({
const [isNotFound, setIsNotFound] = useState(false);
const cardsToShow = useMemo(() => {
if (shouldDisplayCardDomain) {
return CardUtils.getDomainCards(cardList)[domain]?.filter((card) => !card?.nameValuePairs?.issuedBy || !card?.nameValuePairs?.isVirtual) ?? [];
return getDomainCards(cardList)[domain]?.filter((card) => !card?.nameValuePairs?.issuedBy || !card?.nameValuePairs?.isVirtual) ?? [];
}
return [cardList?.[cardID]];
}, [shouldDisplayCardDomain, cardList, cardID, domain]);
Expand Down Expand Up @@ -112,7 +113,7 @@ function ExpensifyCardPage({
// That is why this action is handled manually and the response is stored in a local state
// Hence eslint disable here.
// eslint-disable-next-line rulesdir/no-thenable-actions-in-views
Card.revealVirtualCardDetails(currentCardID, validateCode)
revealVirtualCardDetails(currentCardID, validateCode)
.then((value) => {
setCardsDetails((prevState: Record<number, ExpensifyCardDetails | null>) => ({...prevState, [currentCardID]: value}));
setCardsDetailsErrors((prevState) => ({
Expand All @@ -135,7 +136,7 @@ function ExpensifyCardPage({
const hasDetectedDomainFraud = cardsToShow?.some((card) => card?.fraud === CONST.EXPENSIFY_CARD.FRAUD_TYPES.DOMAIN);
const hasDetectedIndividualFraud = cardsToShow?.some((card) => card?.fraud === CONST.EXPENSIFY_CARD.FRAUD_TYPES.INDIVIDUAL);

const formattedAvailableSpendAmount = CurrencyUtils.convertToDisplayString(cardsToShow?.at(0)?.availableSpend);
const formattedAvailableSpendAmount = convertToDisplayString(cardsToShow?.at(0)?.availableSpend);
const {limitNameKey, limitTitleKey} = getLimitTypeTranslationKeys(cardsToShow?.at(0)?.nameValuePairs?.limitType);

const primaryLogin = account?.primaryLogin ?? '';
Expand All @@ -144,13 +145,13 @@ function ExpensifyCardPage({
const goToGetPhysicalCardFlow = () => {
let updatedDraftValues = draftValues;
if (!draftValues) {
updatedDraftValues = GetPhysicalCardUtils.getUpdatedDraftValues(undefined, privatePersonalDetails, loginList);
updatedDraftValues = getUpdatedDraftValues(undefined, privatePersonalDetails, loginList);
// Form draft data needs to be initialized with the private personal details
// If no draft data exists
FormActions.setDraftValues(ONYXKEYS.FORMS.GET_PHYSICAL_CARD_FORM, updatedDraftValues);
setDraftValues(ONYXKEYS.FORMS.GET_PHYSICAL_CARD_FORM, updatedDraftValues);
}

GetPhysicalCardUtils.goToNextPhysicalCardRoute(domain, GetPhysicalCardUtils.getUpdatedPrivatePersonalDetails(updatedDraftValues, privatePersonalDetails));
goToNextPhysicalCardRoute(domain, getUpdatedPrivatePersonalDetails(updatedDraftValues, privatePersonalDetails));
};

if (isNotFound) {
Expand Down Expand Up @@ -187,7 +188,7 @@ function ExpensifyCardPage({
<Button
style={[styles.mh5, styles.mb5]}
text={translate('cardPage.reviewTransaction')}
onPress={() => Link.openOldDotLink(CONST.OLDDOT_URLS.INBOX)}
onPress={() => openOldDotLink(CONST.OLDDOT_URLS.INBOX)}
/>
</>
)}
Expand All @@ -214,15 +215,15 @@ function ExpensifyCardPage({
{!!cardsDetails[card.cardID] && cardsDetails[card.cardID]?.pan ? (
<CardDetails
pan={cardsDetails[card.cardID]?.pan}
expiration={CardUtils.formatCardExpiration(cardsDetails[card.cardID]?.expiration ?? '')}
expiration={formatCardExpiration(cardsDetails[card.cardID]?.expiration ?? '')}
cvv={cardsDetails[card.cardID]?.cvv}
domain={domain}
/>
) : (
<>
<MenuItemWithTopDescription
description={translate('cardPage.virtualCardNumber')}
title={CardUtils.maskCard('')}
title={maskCard('')}
interactive={false}
titleStyle={styles.walletCardNumber}
shouldShowRightComponent
Expand Down Expand Up @@ -259,7 +260,7 @@ function ExpensifyCardPage({
<>
<MenuItemWithTopDescription
description={translate('cardPage.physicalCardNumber')}
title={CardUtils.maskCard(card?.lastFourPAN)}
title={maskCard(card?.lastFourPAN)}
interactive={false}
titleStyle={styles.walletCardNumber}
/>
Expand All @@ -272,6 +273,18 @@ function ExpensifyCardPage({
</>
);
})}
<MenuItem
icon={Expensicons.MoneySearch}
title={translate('workspace.common.viewTransactions')}
style={styles.mt3}
onPress={() => {
Navigation.navigate(
ROUTES.SEARCH_CENTRAL_PANE.getRoute({
query: buildCannedSearchQuery({type: CONST.SEARCH.DATA_TYPES.EXPENSE, status: CONST.SEARCH.STATUS.EXPENSE.ALL, cardID}),
}),
);
}}
/>
</>
)}
</ScrollView>
Expand Down
46 changes: 33 additions & 13 deletions src/pages/settings/Wallet/PaymentMethodList.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -27,14 +27,26 @@ import variables from '@styles/variables';
import CONST from '@src/CONST';
import ONYXKEYS from '@src/ONYXKEYS';
import ROUTES from '@src/ROUTES';
import type {AccountData, CompanyCardFeed} from '@src/types/onyx';
import type {AccountData, Card, CompanyCardFeed} from '@src/types/onyx';
import type {BankIcon} from '@src/types/onyx/Bank';
import type {Errors} from '@src/types/onyx/OnyxCommon';
import type PaymentMethod from '@src/types/onyx/PaymentMethod';
import type {FilterMethodPaymentType} from '@src/types/onyx/WalletTransfer';
import {isEmptyObject} from '@src/types/utils/EmptyObject';
import isLoadingOnyxValue from '@src/types/utils/isLoadingOnyxValue';

type PaymentMethodPressHandler = (
event?: GestureResponderEvent | KeyboardEvent,
accountType?: string,
accountData?: AccountData,
icon?: FormattedSelectedPaymentMethodIcon,
isDefault?: boolean,
methodID?: number,
description?: string,
) => void;

type CardPressHandler = (event?: GestureResponderEvent | KeyboardEvent, cardData?: Card, icon?: FormattedSelectedPaymentMethodIcon, cardID?: number) => void;

type PaymentMethodListProps = {
/** Type of active/highlighted payment method */
actionPaymentMethodType?: string;
Expand Down Expand Up @@ -88,15 +100,7 @@ type PaymentMethodListProps = {
shouldShowRightIcon?: boolean;

/** What to do when a menu item is pressed */
onPress: (
event?: GestureResponderEvent | KeyboardEvent,
accountType?: string,
accountData?: AccountData,
icon?: FormattedSelectedPaymentMethodIcon,
isDefault?: boolean,
methodID?: number,
description?: string,
) => void;
onPress: PaymentMethodPressHandler | CardPressHandler;

/** The policy invoice's transfer bank accountID */
invoiceTransferBankAccountID?: number;
Expand Down Expand Up @@ -214,13 +218,14 @@ function PaymentMethodList({
const icon = getCardFeedIcon(card.bank as CompanyCardFeed);

if (!isExpensifyCard(card.cardID)) {
const pressHandler = onPress as CardPressHandler;
assignedCardsGrouped.push({
key: card.cardID.toString(),
title: maskCardNumber(card.cardName, card.bank),
description: getDescriptionForPolicyDomainCard(card.domainName),
shouldShowRightIcon: false,
interactive: false,
interactive: true,
canDismissError: false,
shouldShowRightIcon,
errors: card.errors,
pendingAction: card.pendingAction,
brickRoadIndicator:
Expand All @@ -231,6 +236,20 @@ function PaymentMethodList({
iconStyles: [styles.cardIcon],
iconWidth: variables.cardIconWidth,
iconHeight: variables.cardIconHeight,
iconRight: Expensicons.ThreeDots,
isMethodActive: activePaymentMethodID === card.cardID,
onPress: (e: GestureResponderEvent | KeyboardEvent | undefined) =>
pressHandler(
e,
card,
{
icon,
iconStyles: [styles.cardIcon],
iconWidth: variables.cardIconWidth,
iconHeight: variables.cardIconHeight,
},
card.cardID,
),
});
return;
}
Expand Down Expand Up @@ -294,11 +313,12 @@ function PaymentMethodList({
);
}
combinedPaymentMethods = combinedPaymentMethods.map((paymentMethod) => {
const pressHandler = onPress as PaymentMethodPressHandler;
const isMethodActive = isPaymentMethodActive(actionPaymentMethodType, activePaymentMethodID, paymentMethod);
return {
...paymentMethod,
onPress: (e: GestureResponderEvent) =>
onPress(
pressHandler(
e,
paymentMethod.accountType,
paymentMethod.accountData,
Expand Down
Loading

0 comments on commit 4dc7c7a

Please sign in to comment.