diff --git a/src/components/Tables/WorkspaceExpensifyCardsTable/WorkspaceExpensifyCardsTableRow.tsx b/src/components/Tables/WorkspaceExpensifyCardsTable/WorkspaceExpensifyCardsTableRow.tsx index 18a1eb1ce2d4..09cac8d37aa0 100644 --- a/src/components/Tables/WorkspaceExpensifyCardsTable/WorkspaceExpensifyCardsTableRow.tsx +++ b/src/components/Tables/WorkspaceExpensifyCardsTable/WorkspaceExpensifyCardsTableRow.tsx @@ -10,7 +10,7 @@ import useLocalize from '@hooks/useLocalize'; import useTheme from '@hooks/useTheme'; import useThemeStyles from '@hooks/useThemeStyles'; -import {getTranslationKeyForLimitType} from '@libs/CardUtils'; +import {getTranslationKeyForCardStatus, getTranslationKeyForLimitType} from '@libs/CardUtils'; import {convertToShortDisplayString} from '@libs/CurrencyUtils'; import DateUtils from '@libs/DateUtils'; import {temporaryGetDisplayNameOrDefault} from '@libs/PersonalDetailsUtils'; @@ -46,6 +46,8 @@ export default function WorkspaceExpensifyCardsTableRow({item, rowIndex, shouldU const narrowLayoutSubtitle = [item.lastFourPAN, item.name].filter(Boolean).join(` ${CONST.DOT_SEPARATOR} `); const cardType = item.isVirtual ? translate('workspace.expensifyCard.virtual') : translate('workspace.expensifyCard.physical'); const limitTypeLabel = translate(getTranslationKeyForLimitType(item.limitType)); + const statusTranslationKey = getTranslationKeyForCardStatus(item.card.state, item.isVirtual); + const statusLabel = statusTranslationKey ? translate(statusTranslationKey) : ''; const formattedLimit = convertToShortDisplayString(item.limit, item.currency); const formattedFrozenDate = item.frozenDate ? DateUtils.formatWithUTCTimeZone(item.frozenDate, CONST.DATE.MONTH_DAY_YEAR_ABBR_FORMAT) : ''; let frozenByText: string | undefined; @@ -58,7 +60,7 @@ export default function WorkspaceExpensifyCardsTableRow({item, rowIndex, shouldU } } - const accessibilityLabel = [cardholderName, item.name, cardType, limitTypeLabel, item.lastFourPAN, formattedLimit, frozenByText].filter(Boolean).join(', '); + const accessibilityLabel = [cardholderName, item.name, cardType, limitTypeLabel, item.lastFourPAN, statusLabel, formattedLimit, frozenByText].filter(Boolean).join(', '); const frozenByRowFooter = !!frozenByText && ( @@ -137,7 +139,7 @@ export default function WorkspaceExpensifyCardsTableRow({item, rowIndex, shouldU )} {!shouldUseNarrowTableLayout && ( - + )} + {!shouldUseNarrowTableLayout && ( + + + + )} + | undefined, isVirtual: boolean): TranslationPaths | undefined { + switch (state) { + // Pending order and Shipped are physical-only states, so a virtual card in one of them has no meaningful status to show. + case CONST.EXPENSIFY_CARD.STATE.STATE_NOT_ISSUED: + return isVirtual ? undefined : 'workspace.expensifyCard.statusPendingOrder'; + case CONST.EXPENSIFY_CARD.STATE.NOT_ACTIVATED: + return isVirtual ? undefined : 'workspace.expensifyCard.statusShipped'; + case CONST.EXPENSIFY_CARD.STATE.OPEN: + return 'workspace.expensifyCard.statusActive'; + case CONST.EXPENSIFY_CARD.STATE.STATE_SUSPENDED: + return 'workspace.expensifyCard.statusInactive'; + // Any other state (e.g. bad data) has no status to show, so it's left blank rather than defaulting to Active. + default: + return undefined; + } +} + function maskPin(pin: string | undefined): string { if (pin === undefined) { return '••••'; @@ -1958,6 +1981,7 @@ export { getCardDescription, getMCardNumberString, getTranslationKeyForLimitType, + getTranslationKeyForCardStatus, maskPin, getEligibleBankAccountsForCard, sortCardsByCardholderName, diff --git a/tests/unit/CardUtilsTest.ts b/tests/unit/CardUtilsTest.ts index aea31aa90e99..5a90c318bec4 100644 --- a/tests/unit/CardUtilsTest.ts +++ b/tests/unit/CardUtilsTest.ts @@ -50,6 +50,7 @@ import { getPlaidInstitutionIconUrl, getPlaidInstitutionId, getSelectedFeed, + getTranslationKeyForCardStatus, getYearFromExpirationDateString, hasAssignedCardMatching, hasIssuedExpensifyCard, @@ -2081,6 +2082,33 @@ describe('CardUtils', () => { }); }); + describe('getTranslationKeyForCardStatus', () => { + it('maps STATE_NOT_ISSUED to pending order for physical cards', () => { + expect(getTranslationKeyForCardStatus(CONST.EXPENSIFY_CARD.STATE.STATE_NOT_ISSUED, false)).toBe('workspace.expensifyCard.statusPendingOrder'); + }); + + it('maps NOT_ACTIVATED to shipped for physical cards', () => { + expect(getTranslationKeyForCardStatus(CONST.EXPENSIFY_CARD.STATE.NOT_ACTIVATED, false)).toBe('workspace.expensifyCard.statusShipped'); + }); + + it('maps OPEN to active', () => { + expect(getTranslationKeyForCardStatus(CONST.EXPENSIFY_CARD.STATE.OPEN, false)).toBe('workspace.expensifyCard.statusActive'); + }); + + it('maps STATE_SUSPENDED to inactive', () => { + expect(getTranslationKeyForCardStatus(CONST.EXPENSIFY_CARD.STATE.STATE_SUSPENDED, false)).toBe('workspace.expensifyCard.statusInactive'); + }); + + it('reports no status for a virtual card in a physical-only state', () => { + expect(getTranslationKeyForCardStatus(CONST.EXPENSIFY_CARD.STATE.STATE_NOT_ISSUED, true)).toBeUndefined(); + expect(getTranslationKeyForCardStatus(CONST.EXPENSIFY_CARD.STATE.NOT_ACTIVATED, true)).toBeUndefined(); + }); + + it('reports no status for an undefined or unrecognized state', () => { + expect(getTranslationKeyForCardStatus(undefined, false)).toBeUndefined(); + }); + }); + describe('getDefaultExpensifyCardLimitType', () => { it('returns SMART when policy has approvals configured (approvalMode is ADVANCED)', () => { const policy = createMock({