From e63cc9b0cdf7a08b44ab17d8e35d6a2a3c21503c Mon Sep 17 00:00:00 2001 From: Ishpaul Singh Date: Sat, 25 Jul 2026 03:59:01 +0530 Subject: [PATCH 1/3] Add View on Spend button to Travel Invoicing settings Links admins to the Spend page pre-filtered on the Consolidated Travel Billing feed so they can reconcile travel spend. Co-Authored-By: Claude Opus 4.8 (1M context) --- src/languages/de.ts | 1 + src/languages/en.ts | 1 + src/languages/es.ts | 1 + src/languages/fr.ts | 1 + src/languages/it.ts | 1 + src/languages/ja.ts | 1 + src/languages/nl.ts | 1 + src/languages/pl.ts | 1 + src/languages/pt-BR.ts | 1 + src/languages/zh-hans.ts | 1 + .../WorkspaceTravelInvoicingSection.tsx | 35 ++++++++++++++----- 11 files changed, 37 insertions(+), 8 deletions(-) diff --git a/src/languages/de.ts b/src/languages/de.ts index 1e5a4131ca3a..d71f4d60abdb 100644 --- a/src/languages/de.ts +++ b/src/languages/de.ts @@ -6074,6 +6074,7 @@ _Für ausführlichere Anweisungen [besuchen Sie unsere Hilfeseite](${CONST.NETSU currentTravelSpendLabel: 'Aktuelle Reisekosten', currentTravelSpendPaymentQueued: (amount: string) => `Die Zahlung über ${amount} ist in der Warteschlange und wird in Kürze bearbeitet.`, currentTravelSpendCta: 'Saldo bezahlen', + viewOnSpend: 'In Ausgaben anzeigen', currentTravelLimitLabel: 'Aktuelles Reisekontingent', settlementAccountLabel: 'Verrechnungskonto', settlementFrequencyLabel: 'Auszahlungsfrequenz', diff --git a/src/languages/en.ts b/src/languages/en.ts index 75bfadee2ca7..f71d2af00036 100644 --- a/src/languages/en.ts +++ b/src/languages/en.ts @@ -6163,6 +6163,7 @@ const translations = { currentTravelSpendLabel: 'Current travel spend', currentTravelSpendPaymentQueued: (amount: string) => `Payment of ${amount} is queued and will be processed soon.`, currentTravelSpendCta: 'Pay balance', + viewOnSpend: 'View on Spend', currentTravelLimitLabel: 'Current travel limit', settlementAccountLabel: 'Settlement account', settlementFrequencyLabel: 'Settlement frequency', diff --git a/src/languages/es.ts b/src/languages/es.ts index 5e173ef30b78..feb531d93cbb 100644 --- a/src/languages/es.ts +++ b/src/languages/es.ts @@ -5918,6 +5918,7 @@ ${amount} para ${merchant} - ${date}`, currentTravelSpendLabel: 'Gasto actual en viajes', currentTravelSpendPaymentQueued: (amount: string) => `El pago de ${amount} está en cola y se procesará pronto.`, currentTravelSpendCta: 'Pagar saldo', + viewOnSpend: 'Ver en Gastos', currentTravelLimitLabel: 'Límite actual de viajes', settlementAccountLabel: 'Cuenta de liquidación', settlementFrequencyLabel: 'Frecuencia de liquidación', diff --git a/src/languages/fr.ts b/src/languages/fr.ts index 664cee6663fb..37786cf8a72b 100644 --- a/src/languages/fr.ts +++ b/src/languages/fr.ts @@ -6094,6 +6094,7 @@ _Pour des instructions plus détaillées, [visitez notre site d’aide](${CONST. currentTravelSpendLabel: 'Dépenses de voyage actuelles', currentTravelSpendPaymentQueued: (amount: string) => `Le paiement de ${amount} est en file d’attente et sera traité bientôt.`, currentTravelSpendCta: 'Payer le solde', + viewOnSpend: 'Voir dans Dépenses', currentTravelLimitLabel: 'Plafond de déplacement actuel', settlementAccountLabel: 'Compte de règlement', settlementFrequencyLabel: 'Fréquence de règlement', diff --git a/src/languages/it.ts b/src/languages/it.ts index 6dc456a27c0c..75dd8211bf40 100644 --- a/src/languages/it.ts +++ b/src/languages/it.ts @@ -6056,6 +6056,7 @@ _Per istruzioni più dettagliate, [visita il nostro sito di assistenza](${CONST. currentTravelSpendLabel: 'Spesa di viaggio attuale', currentTravelSpendPaymentQueued: (amount: string) => `Il pagamento di ${amount} è in coda e verrà elaborato a breve.`, currentTravelSpendCta: 'Paga saldo', + viewOnSpend: 'Visualizza in Spese', currentTravelLimitLabel: 'Limite di viaggio attuale', settlementAccountLabel: 'Conto di regolamento', settlementFrequencyLabel: 'Frequenza di regolamento', diff --git a/src/languages/ja.ts b/src/languages/ja.ts index 11db3c3def10..dbca13f8bf8a 100644 --- a/src/languages/ja.ts +++ b/src/languages/ja.ts @@ -5985,6 +5985,7 @@ _詳しい手順については、[ヘルプサイトをご覧ください](${CO currentTravelSpendLabel: '現在の出張費支出', currentTravelSpendPaymentQueued: (amount: string) => `${amount} の支払いはキューに登録されており、まもなく処理されます。`, currentTravelSpendCta: '残高を支払う', + viewOnSpend: '支出で表示', currentTravelLimitLabel: '現在の出張上限', settlementAccountLabel: '決済口座', settlementFrequencyLabel: '清算頻度', diff --git a/src/languages/nl.ts b/src/languages/nl.ts index c774982e5ffe..0ec1a82d53a2 100644 --- a/src/languages/nl.ts +++ b/src/languages/nl.ts @@ -6045,6 +6045,7 @@ _Voor meer gedetailleerde instructies, [bezoek onze help-site](${CONST.NETSUITE_ currentTravelSpendLabel: 'Huidige reiskosten', currentTravelSpendPaymentQueued: (amount: string) => `Betaling van ${amount} staat in de wachtrij en wordt binnenkort verwerkt.`, currentTravelSpendCta: 'Saldo betalen', + viewOnSpend: 'Bekijken in Uitgaven', currentTravelLimitLabel: 'Huidige reisl imiet', settlementAccountLabel: 'Verrekeningsrekening', settlementFrequencyLabel: 'Uitbetalingsfrequentie', diff --git a/src/languages/pl.ts b/src/languages/pl.ts index ac30cc2c6efc..5f3ceae500c6 100644 --- a/src/languages/pl.ts +++ b/src/languages/pl.ts @@ -6024,6 +6024,7 @@ _Aby uzyskać bardziej szczegółowe instrukcje, [odwiedź naszą stronę pomocy currentTravelSpendLabel: 'Aktualne wydatki na podróże', currentTravelSpendPaymentQueued: (amount: string) => `Płatność w wysokości ${amount} jest w kolejce i wkrótce zostanie przetworzona.`, currentTravelSpendCta: 'Spłać saldo', + viewOnSpend: 'Zobacz w Wydatkach', currentTravelLimitLabel: 'Aktualny limit podróży', settlementAccountLabel: 'Konto rozliczeniowe', settlementFrequencyLabel: 'Częstotliwość rozliczeń', diff --git a/src/languages/pt-BR.ts b/src/languages/pt-BR.ts index fe6a996bfccf..e4cc743fd70d 100644 --- a/src/languages/pt-BR.ts +++ b/src/languages/pt-BR.ts @@ -6037,6 +6037,7 @@ _Para instruções mais detalhadas, [visite nossa central de ajuda](${CONST.NETS currentTravelSpendLabel: 'Gasto atual com viagens', currentTravelSpendPaymentQueued: (amount: string) => `O pagamento de ${amount} está na fila e será processado em breve.`, currentTravelSpendCta: 'Pagar saldo', + viewOnSpend: 'Ver em Gastos', currentTravelLimitLabel: 'Limite de viagem atual', settlementAccountLabel: 'Conta de liquidação', settlementFrequencyLabel: 'Frequência de liquidação', diff --git a/src/languages/zh-hans.ts b/src/languages/zh-hans.ts index 487d47fe7611..0d6f8a86a460 100644 --- a/src/languages/zh-hans.ts +++ b/src/languages/zh-hans.ts @@ -5857,6 +5857,7 @@ _如需更详细的说明,请[访问我们的帮助网站](${CONST.NETSUITE_IM currentTravelSpendLabel: '当前差旅支出', currentTravelSpendPaymentQueued: (amount: string) => `金额为 ${amount} 的付款已排队,稍后将被处理。`, currentTravelSpendCta: '支付余额', + viewOnSpend: '在支出中查看', currentTravelLimitLabel: '当前出行限额', settlementAccountLabel: '结算账户', settlementFrequencyLabel: '结算频率', diff --git a/src/pages/workspace/travel/WorkspaceTravelInvoicingSection.tsx b/src/pages/workspace/travel/WorkspaceTravelInvoicingSection.tsx index 74cc49069286..fe3c8240d0f2 100644 --- a/src/pages/workspace/travel/WorkspaceTravelInvoicingSection.tsx +++ b/src/pages/workspace/travel/WorkspaceTravelInvoicingSection.tsx @@ -32,6 +32,7 @@ import createDynamicRoute from '@libs/Navigation/helpers/dynamicRoutesUtils/crea import Navigation from '@libs/Navigation/Navigation'; import {areTravelPersonalDetailsMissing} from '@libs/PersonalDetailsUtils'; import {hasInProgressUSDVBBA} from '@libs/ReimbursementAccountUtils'; +import {buildQueryStringFromFilterFormValues} from '@libs/SearchQueryUtils'; import { getIsTravelInvoicingEnabled, getTravelInvoicingCardSettingsKey, @@ -163,6 +164,19 @@ function WorkspaceTravelInvoicingSection({policyID}: WorkspaceTravelInvoicingSec setIsPayBalanceModalVisible(true); }; + /** + * Navigates to the Spend page pre-filtered on the Consolidated Travel Billing feed so admins + * can reconcile their travel spend. The feed ID mirrors the one built in getExpensifyCardFeedsForDisplay. + */ + const handleViewOnSpend = () => { + const travelFeedID = `${workspaceAccountID}_${CONST.EXPENSIFY_CARD.BANK}_${CONST.TRAVEL.PROGRAM_TRAVEL_US}`; + const query = buildQueryStringFromFilterFormValues({ + type: CONST.SEARCH.DATA_TYPES.EXPENSE, + feed: [travelFeedID], + }); + Navigation.navigate(ROUTES.SEARCH_ROOT.getRoute({query})); + }; + /** * Handles the confirmed payment of the outstanding travel balance. * Closes the modal and triggers the API call with optimistic Onyx update. @@ -331,15 +345,20 @@ function WorkspaceTravelInvoicingSection({policyID}: WorkspaceTravelInvoicingSec )} - {shouldShowPayButton && canWriteMoreFeatures && ( - - )} + {shouldShowPayButton && canWriteMoreFeatures && ( + + )} + Date: Sat, 25 Jul 2026 04:04:19 +0530 Subject: [PATCH 2/3] Stack Travel Invoicing buttons on their own row on narrow layouts Co-Authored-By: Claude Opus 4.8 (1M context) --- .../travel/WorkspaceTravelInvoicingSection.tsx | 10 ++++++++-- 1 file changed, 8 insertions(+), 2 deletions(-) diff --git a/src/pages/workspace/travel/WorkspaceTravelInvoicingSection.tsx b/src/pages/workspace/travel/WorkspaceTravelInvoicingSection.tsx index fe3c8240d0f2..c1cd5ec3937b 100644 --- a/src/pages/workspace/travel/WorkspaceTravelInvoicingSection.tsx +++ b/src/pages/workspace/travel/WorkspaceTravelInvoicingSection.tsx @@ -13,6 +13,7 @@ import useLocalize from '@hooks/useLocalize'; import useNetwork from '@hooks/useNetwork'; import useOnyx from '@hooks/useOnyx'; import usePolicyFeatureWriteAccess from '@hooks/usePolicyFeatureWriteAccess'; +import useResponsiveLayout from '@hooks/useResponsiveLayout'; import useThemeStyles from '@hooks/useThemeStyles'; import useWorkspaceAccountID from '@hooks/useWorkspaceAccountID'; @@ -71,6 +72,7 @@ type WorkspaceTravelInvoicingSectionProps = { function WorkspaceTravelInvoicingSection({policyID}: WorkspaceTravelInvoicingSectionProps) { const styles = useThemeStyles(); const {isOffline} = useNetwork(); + const {shouldUseNarrowLayout} = useResponsiveLayout(); const {translate} = useLocalize(); const {convertToDisplayString} = useCurrencyListActions(); const workspaceAccountID = useWorkspaceAccountID(policyID); @@ -329,7 +331,7 @@ function WorkspaceTravelInvoicingSection({policyID}: WorkspaceTravelInvoicingSec /> )} - + - {shouldShowPayButton && canWriteMoreFeatures && ( @@ -354,6 +359,7 @@ function WorkspaceTravelInvoicingSection({policyID}: WorkspaceTravelInvoicingSec onPress={handlePayBalance} isDisabled={isOffline} variant={CONST.BUTTON_VARIANT.SUCCESS} + style={shouldUseNarrowLayout ? styles.flex1 : undefined} > {translate('workspace.moreFeatures.travel.travelInvoicing.travelInvoicingSection.subsections.currentTravelSpendCta')} From 8e5073180e1458997507ac4fc2cb65d667771b45 Mon Sep 17 00:00:00 2001 From: Ishpaul Singh Date: Sat, 25 Jul 2026 04:31:40 +0530 Subject: [PATCH 3/3] Extract getTravelInvoicingFeedID to dedupe the travel feed-ID format The `{fundID}_{BANK}_{PROGRAM_TRAVEL_US}` feed-ID string was built inline in WorkspaceTravelInvoicingSection and useGetExpensifyCardFromReportAction. Extract a getTravelInvoicingFeedID helper in TravelInvoicingUtils and reuse it in both, so the format lives in one place. Co-Authored-By: Claude Opus 4.8 (1M context) --- src/hooks/useGetExpensifyCardFromReportAction.ts | 3 ++- src/libs/TravelInvoicingUtils.ts | 8 ++++++++ .../workspace/travel/WorkspaceTravelInvoicingSection.tsx | 5 +++-- 3 files changed, 13 insertions(+), 3 deletions(-) diff --git a/src/hooks/useGetExpensifyCardFromReportAction.ts b/src/hooks/useGetExpensifyCardFromReportAction.ts index 9ae5ca383e6d..b2c79cf74c3a 100644 --- a/src/hooks/useGetExpensifyCardFromReportAction.ts +++ b/src/hooks/useGetExpensifyCardFromReportAction.ts @@ -2,6 +2,7 @@ import {useCardList, useWorkspaceCardList} from '@components/OnyxListItemProvide import {isPolicyAdmin} from '@libs/PolicyUtils'; import {getOriginalMessage, isCardIssuedAction} from '@libs/ReportActionsUtils'; +import {getTravelInvoicingFeedID} from '@libs/TravelInvoicingUtils'; import CONST from '@src/CONST'; import ONYXKEYS from '@src/ONYXKEYS'; @@ -25,7 +26,7 @@ function useGetExpensifyCardFromReportAction({reportAction, policyID}: {reportAc // Travel Invoicing cards on the `_TRAVEL_US` variant. Check both. return ( allExpensifyCards?.[`${ONYXKEYS.COLLECTION.WORKSPACE_CARDS_LIST}${workspaceAccountID}_${CONST.EXPENSIFY_CARD.BANK}`]?.[cardID] ?? - allExpensifyCards?.[`${ONYXKEYS.COLLECTION.WORKSPACE_CARDS_LIST}${workspaceAccountID}_${CONST.EXPENSIFY_CARD.BANK}_${CONST.TRAVEL.PROGRAM_TRAVEL_US}`]?.[cardID] ?? + allExpensifyCards?.[`${ONYXKEYS.COLLECTION.WORKSPACE_CARDS_LIST}${getTravelInvoicingFeedID(workspaceAccountID)}`]?.[cardID] ?? allUserCards?.[cardID] ); } diff --git a/src/libs/TravelInvoicingUtils.ts b/src/libs/TravelInvoicingUtils.ts index c29645794b98..5e9f25d09c3d 100644 --- a/src/libs/TravelInvoicingUtils.ts +++ b/src/libs/TravelInvoicingUtils.ts @@ -137,6 +137,13 @@ function getTravelInvoicingCardSettingsKey(workspaceAccountID: number): `${typeo return `${ONYXKEYS.COLLECTION.PRIVATE_EXPENSIFY_CARD_SETTINGS}${workspaceAccountID}`; } +/** + * Builds the card-feed ID for a Travel Invoicing card feed (the `_TRAVEL_US` variant). + */ +function getTravelInvoicingFeedID(fundID: number | string): `${string}_${typeof CONST.EXPENSIFY_CARD.BANK}_${typeof CONST.TRAVEL.PROGRAM_TRAVEL_US}` { + return `${fundID}_${CONST.EXPENSIFY_CARD.BANK}_${CONST.TRAVEL.PROGRAM_TRAVEL_US}`; +} + /** * Downloads a cached Travel Invoice Statement PDF. * Constructs a secure URL with encrypted auth token and triggers the download. @@ -185,6 +192,7 @@ export { getTravelSettlementAccount, getTravelSettlementFrequency, getTravelInvoicingCardSettingsKey, + getTravelInvoicingFeedID, downloadTravelInvoiceStatementPDF, getTravelInvoicingCard, isTravelCVVEligible, diff --git a/src/pages/workspace/travel/WorkspaceTravelInvoicingSection.tsx b/src/pages/workspace/travel/WorkspaceTravelInvoicingSection.tsx index c1cd5ec3937b..3a557c1aa82c 100644 --- a/src/pages/workspace/travel/WorkspaceTravelInvoicingSection.tsx +++ b/src/pages/workspace/travel/WorkspaceTravelInvoicingSection.tsx @@ -37,6 +37,7 @@ import {buildQueryStringFromFilterFormValues} from '@libs/SearchQueryUtils'; import { getIsTravelInvoicingEnabled, getTravelInvoicingCardSettingsKey, + getTravelInvoicingFeedID, getTravelLimit, getTravelSettlementAccount, getTravelSettlementFrequency, @@ -168,10 +169,10 @@ function WorkspaceTravelInvoicingSection({policyID}: WorkspaceTravelInvoicingSec /** * Navigates to the Spend page pre-filtered on the Consolidated Travel Billing feed so admins - * can reconcile their travel spend. The feed ID mirrors the one built in getExpensifyCardFeedsForDisplay. + * can reconcile their travel spend. */ const handleViewOnSpend = () => { - const travelFeedID = `${workspaceAccountID}_${CONST.EXPENSIFY_CARD.BANK}_${CONST.TRAVEL.PROGRAM_TRAVEL_US}`; + const travelFeedID = getTravelInvoicingFeedID(workspaceAccountID); const query = buildQueryStringFromFilterFormValues({ type: CONST.SEARCH.DATA_TYPES.EXPENSE, feed: [travelFeedID],