Skip to content

Commit 4dc7c7a

Browse files
authored
Merge pull request #55095 from parasharrajat/parasharrajat/viewtransactions
Add view transactions cta to Expensify card and company cards page
2 parents fb5ace6 + 3238b9b commit 4dc7c7a

File tree

8 files changed

+219
-77
lines changed

8 files changed

+219
-77
lines changed

src/languages/en.ts

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -2659,6 +2659,7 @@ const translations = {
26592659
planType: 'Plan type',
26602660
submitExpense: 'Submit expenses using your workspace chat below:',
26612661
defaultCategory: 'Default category',
2662+
viewTransactions: 'View transactions',
26622663
},
26632664
perDiem: {
26642665
subtitle: 'Set per diem rates to control daily employee spend. ',

src/languages/es.ts

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -2683,6 +2683,7 @@ const translations = {
26832683
planType: 'Tipo de plan',
26842684
submitExpense: 'Envíe los gastos utilizando el chat de su espacio de trabajo:',
26852685
defaultCategory: 'Categoría predeterminada',
2686+
viewTransactions: 'Ver transacciones',
26862687
},
26872688
perDiem: {
26882689
subtitle: 'Establece las tasas per diem para controlar los gastos diarios de los empleados. ',

src/libs/SearchQueryUtils.ts

Lines changed: 11 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -624,14 +624,22 @@ function buildCannedSearchQuery({
624624
type = CONST.SEARCH.DATA_TYPES.EXPENSE,
625625
status = CONST.SEARCH.STATUS.EXPENSE.ALL,
626626
policyID,
627+
cardID,
627628
}: {
628629
type?: SearchDataTypes;
629630
status?: SearchStatus;
630631
policyID?: string;
632+
cardID?: string;
631633
} = {}): SearchQueryString {
632-
const queryString = policyID
633-
? `type:${type} status:${Array.isArray(status) ? status.join(',') : status} policyID:${policyID}`
634-
: `type:${type} status:${Array.isArray(status) ? status.join(',') : status}`;
634+
let queryString = `type:${type} status:${Array.isArray(status) ? status.join(',') : status}`;
635+
636+
if (policyID) {
637+
queryString = `type:${type} status:${Array.isArray(status) ? status.join(',') : status} policyID:${policyID}`;
638+
}
639+
640+
if (cardID) {
641+
queryString = `type:${type} status:${Array.isArray(status) ? status.join(',') : status} expense-type:card card:${cardID}`;
642+
}
635643

636644
// Parse the query to fill all default query fields with values
637645
const normalizedQueryJSON = buildSearchQueryJSON(queryString);

src/pages/settings/Wallet/ExpensifyCardPage.tsx

Lines changed: 29 additions & 16 deletions
Original file line numberDiff line numberDiff line change
@@ -16,17 +16,18 @@ import useBeforeRemove from '@hooks/useBeforeRemove';
1616
import useLocalize from '@hooks/useLocalize';
1717
import useNetwork from '@hooks/useNetwork';
1818
import useThemeStyles from '@hooks/useThemeStyles';
19-
import * as FormActions from '@libs/actions/FormActions';
19+
import {setDraftValues} from '@libs/actions/FormActions';
2020
import {requestValidateCodeAction} from '@libs/actions/User';
21-
import * as CardUtils from '@libs/CardUtils';
22-
import * as CurrencyUtils from '@libs/CurrencyUtils';
23-
import * as GetPhysicalCardUtils from '@libs/GetPhysicalCardUtils';
21+
import {formatCardExpiration, getDomainCards, maskCard} from '@libs/CardUtils';
22+
import {convertToDisplayString} from '@libs/CurrencyUtils';
23+
import {getUpdatedDraftValues, getUpdatedPrivatePersonalDetails, goToNextPhysicalCardRoute} from '@libs/GetPhysicalCardUtils';
2424
import Navigation from '@libs/Navigation/Navigation';
2525
import type {PlatformStackScreenProps} from '@libs/Navigation/PlatformStackNavigation/types';
2626
import type {SettingsNavigatorParamList} from '@libs/Navigation/types';
27+
import {buildCannedSearchQuery} from '@libs/SearchQueryUtils';
2728
import NotFoundPage from '@pages/ErrorPage/NotFoundPage';
28-
import * as Card from '@userActions/Card';
29-
import * as Link from '@userActions/Link';
29+
import {revealVirtualCardDetails} from '@userActions/Card';
30+
import {openOldDotLink} from '@userActions/Link';
3031
import CONST from '@src/CONST';
3132
import type {TranslationPaths} from '@src/languages/types';
3233
import ONYXKEYS from '@src/ONYXKEYS';
@@ -81,7 +82,7 @@ function ExpensifyCardPage({
8182
const [isNotFound, setIsNotFound] = useState(false);
8283
const cardsToShow = useMemo(() => {
8384
if (shouldDisplayCardDomain) {
84-
return CardUtils.getDomainCards(cardList)[domain]?.filter((card) => !card?.nameValuePairs?.issuedBy || !card?.nameValuePairs?.isVirtual) ?? [];
85+
return getDomainCards(cardList)[domain]?.filter((card) => !card?.nameValuePairs?.issuedBy || !card?.nameValuePairs?.isVirtual) ?? [];
8586
}
8687
return [cardList?.[cardID]];
8788
}, [shouldDisplayCardDomain, cardList, cardID, domain]);
@@ -112,7 +113,7 @@ function ExpensifyCardPage({
112113
// That is why this action is handled manually and the response is stored in a local state
113114
// Hence eslint disable here.
114115
// eslint-disable-next-line rulesdir/no-thenable-actions-in-views
115-
Card.revealVirtualCardDetails(currentCardID, validateCode)
116+
revealVirtualCardDetails(currentCardID, validateCode)
116117
.then((value) => {
117118
setCardsDetails((prevState: Record<number, ExpensifyCardDetails | null>) => ({...prevState, [currentCardID]: value}));
118119
setCardsDetailsErrors((prevState) => ({
@@ -135,7 +136,7 @@ function ExpensifyCardPage({
135136
const hasDetectedDomainFraud = cardsToShow?.some((card) => card?.fraud === CONST.EXPENSIFY_CARD.FRAUD_TYPES.DOMAIN);
136137
const hasDetectedIndividualFraud = cardsToShow?.some((card) => card?.fraud === CONST.EXPENSIFY_CARD.FRAUD_TYPES.INDIVIDUAL);
137138

138-
const formattedAvailableSpendAmount = CurrencyUtils.convertToDisplayString(cardsToShow?.at(0)?.availableSpend);
139+
const formattedAvailableSpendAmount = convertToDisplayString(cardsToShow?.at(0)?.availableSpend);
139140
const {limitNameKey, limitTitleKey} = getLimitTypeTranslationKeys(cardsToShow?.at(0)?.nameValuePairs?.limitType);
140141

141142
const primaryLogin = account?.primaryLogin ?? '';
@@ -144,13 +145,13 @@ function ExpensifyCardPage({
144145
const goToGetPhysicalCardFlow = () => {
145146
let updatedDraftValues = draftValues;
146147
if (!draftValues) {
147-
updatedDraftValues = GetPhysicalCardUtils.getUpdatedDraftValues(undefined, privatePersonalDetails, loginList);
148+
updatedDraftValues = getUpdatedDraftValues(undefined, privatePersonalDetails, loginList);
148149
// Form draft data needs to be initialized with the private personal details
149150
// If no draft data exists
150-
FormActions.setDraftValues(ONYXKEYS.FORMS.GET_PHYSICAL_CARD_FORM, updatedDraftValues);
151+
setDraftValues(ONYXKEYS.FORMS.GET_PHYSICAL_CARD_FORM, updatedDraftValues);
151152
}
152153

153-
GetPhysicalCardUtils.goToNextPhysicalCardRoute(domain, GetPhysicalCardUtils.getUpdatedPrivatePersonalDetails(updatedDraftValues, privatePersonalDetails));
154+
goToNextPhysicalCardRoute(domain, getUpdatedPrivatePersonalDetails(updatedDraftValues, privatePersonalDetails));
154155
};
155156

156157
if (isNotFound) {
@@ -187,7 +188,7 @@ function ExpensifyCardPage({
187188
<Button
188189
style={[styles.mh5, styles.mb5]}
189190
text={translate('cardPage.reviewTransaction')}
190-
onPress={() => Link.openOldDotLink(CONST.OLDDOT_URLS.INBOX)}
191+
onPress={() => openOldDotLink(CONST.OLDDOT_URLS.INBOX)}
191192
/>
192193
</>
193194
)}
@@ -214,15 +215,15 @@ function ExpensifyCardPage({
214215
{!!cardsDetails[card.cardID] && cardsDetails[card.cardID]?.pan ? (
215216
<CardDetails
216217
pan={cardsDetails[card.cardID]?.pan}
217-
expiration={CardUtils.formatCardExpiration(cardsDetails[card.cardID]?.expiration ?? '')}
218+
expiration={formatCardExpiration(cardsDetails[card.cardID]?.expiration ?? '')}
218219
cvv={cardsDetails[card.cardID]?.cvv}
219220
domain={domain}
220221
/>
221222
) : (
222223
<>
223224
<MenuItemWithTopDescription
224225
description={translate('cardPage.virtualCardNumber')}
225-
title={CardUtils.maskCard('')}
226+
title={maskCard('')}
226227
interactive={false}
227228
titleStyle={styles.walletCardNumber}
228229
shouldShowRightComponent
@@ -259,7 +260,7 @@ function ExpensifyCardPage({
259260
<>
260261
<MenuItemWithTopDescription
261262
description={translate('cardPage.physicalCardNumber')}
262-
title={CardUtils.maskCard(card?.lastFourPAN)}
263+
title={maskCard(card?.lastFourPAN)}
263264
interactive={false}
264265
titleStyle={styles.walletCardNumber}
265266
/>
@@ -272,6 +273,18 @@ function ExpensifyCardPage({
272273
</>
273274
);
274275
})}
276+
<MenuItem
277+
icon={Expensicons.MoneySearch}
278+
title={translate('workspace.common.viewTransactions')}
279+
style={styles.mt3}
280+
onPress={() => {
281+
Navigation.navigate(
282+
ROUTES.SEARCH_CENTRAL_PANE.getRoute({
283+
query: buildCannedSearchQuery({type: CONST.SEARCH.DATA_TYPES.EXPENSE, status: CONST.SEARCH.STATUS.EXPENSE.ALL, cardID}),
284+
}),
285+
);
286+
}}
287+
/>
275288
</>
276289
)}
277290
</ScrollView>

src/pages/settings/Wallet/PaymentMethodList.tsx

Lines changed: 33 additions & 13 deletions
Original file line numberDiff line numberDiff line change
@@ -27,14 +27,26 @@ import variables from '@styles/variables';
2727
import CONST from '@src/CONST';
2828
import ONYXKEYS from '@src/ONYXKEYS';
2929
import ROUTES from '@src/ROUTES';
30-
import type {AccountData, CompanyCardFeed} from '@src/types/onyx';
30+
import type {AccountData, Card, CompanyCardFeed} from '@src/types/onyx';
3131
import type {BankIcon} from '@src/types/onyx/Bank';
3232
import type {Errors} from '@src/types/onyx/OnyxCommon';
3333
import type PaymentMethod from '@src/types/onyx/PaymentMethod';
3434
import type {FilterMethodPaymentType} from '@src/types/onyx/WalletTransfer';
3535
import {isEmptyObject} from '@src/types/utils/EmptyObject';
3636
import isLoadingOnyxValue from '@src/types/utils/isLoadingOnyxValue';
3737

38+
type PaymentMethodPressHandler = (
39+
event?: GestureResponderEvent | KeyboardEvent,
40+
accountType?: string,
41+
accountData?: AccountData,
42+
icon?: FormattedSelectedPaymentMethodIcon,
43+
isDefault?: boolean,
44+
methodID?: number,
45+
description?: string,
46+
) => void;
47+
48+
type CardPressHandler = (event?: GestureResponderEvent | KeyboardEvent, cardData?: Card, icon?: FormattedSelectedPaymentMethodIcon, cardID?: number) => void;
49+
3850
type PaymentMethodListProps = {
3951
/** Type of active/highlighted payment method */
4052
actionPaymentMethodType?: string;
@@ -88,15 +100,7 @@ type PaymentMethodListProps = {
88100
shouldShowRightIcon?: boolean;
89101

90102
/** What to do when a menu item is pressed */
91-
onPress: (
92-
event?: GestureResponderEvent | KeyboardEvent,
93-
accountType?: string,
94-
accountData?: AccountData,
95-
icon?: FormattedSelectedPaymentMethodIcon,
96-
isDefault?: boolean,
97-
methodID?: number,
98-
description?: string,
99-
) => void;
103+
onPress: PaymentMethodPressHandler | CardPressHandler;
100104

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

216220
if (!isExpensifyCard(card.cardID)) {
221+
const pressHandler = onPress as CardPressHandler;
217222
assignedCardsGrouped.push({
218223
key: card.cardID.toString(),
219224
title: maskCardNumber(card.cardName, card.bank),
220225
description: getDescriptionForPolicyDomainCard(card.domainName),
221-
shouldShowRightIcon: false,
222-
interactive: false,
226+
interactive: true,
223227
canDismissError: false,
228+
shouldShowRightIcon,
224229
errors: card.errors,
225230
pendingAction: card.pendingAction,
226231
brickRoadIndicator:
@@ -231,6 +236,20 @@ function PaymentMethodList({
231236
iconStyles: [styles.cardIcon],
232237
iconWidth: variables.cardIconWidth,
233238
iconHeight: variables.cardIconHeight,
239+
iconRight: Expensicons.ThreeDots,
240+
isMethodActive: activePaymentMethodID === card.cardID,
241+
onPress: (e: GestureResponderEvent | KeyboardEvent | undefined) =>
242+
pressHandler(
243+
e,
244+
card,
245+
{
246+
icon,
247+
iconStyles: [styles.cardIcon],
248+
iconWidth: variables.cardIconWidth,
249+
iconHeight: variables.cardIconHeight,
250+
},
251+
card.cardID,
252+
),
234253
});
235254
return;
236255
}
@@ -294,11 +313,12 @@ function PaymentMethodList({
294313
);
295314
}
296315
combinedPaymentMethods = combinedPaymentMethods.map((paymentMethod) => {
316+
const pressHandler = onPress as PaymentMethodPressHandler;
297317
const isMethodActive = isPaymentMethodActive(actionPaymentMethodType, activePaymentMethodID, paymentMethod);
298318
return {
299319
...paymentMethod,
300320
onPress: (e: GestureResponderEvent) =>
301-
onPress(
321+
pressHandler(
302322
e,
303323
paymentMethod.accountType,
304324
paymentMethod.accountData,

0 commit comments

Comments
 (0)