diff --git a/src/components/ReportActionItem/TripDetailsView.tsx b/src/components/ReportActionItem/TripDetailsView.tsx
index 9216928033ba..235a37d7f52d 100644
--- a/src/components/ReportActionItem/TripDetailsView.tsx
+++ b/src/components/ReportActionItem/TripDetailsView.tsx
@@ -22,7 +22,7 @@ import StringUtils from '@libs/StringUtils';
import variables from '@styles/variables';
import CONST from '@src/CONST';
import type {ReservationData} from '@src/libs/TripReservationUtils';
-import {formatAirportInfo, formatCancelledDescription, getPNRReservationDataFromTripReport, getTripReservationCode, getTripReservationIcon} from '@src/libs/TripReservationUtils';
+import {formatCancelledDescription, formatTransitLocationLabel, getPNRReservationDataFromTripReport, getTripReservationCode, getTripReservationIcon} from '@src/libs/TripReservationUtils';
import ROUTES from '@src/ROUTES';
import type {Report} from '@src/types/onyx';
import type {Reservation} from '@src/types/onyx/Transaction';
@@ -102,18 +102,18 @@ function ReservationView({reservation, transactionID, tripRoomReportID, sequence
{shouldShowArrowIcon ? (
<>
- {formatAirportInfo(reservation.start)}
+ {formatTransitLocationLabel(reservation.start)}
- {formatAirportInfo(reservation.end)}
+ {formatTransitLocationLabel(reservation.end)}
>
) : (
- {formatAirportInfo(reservation.start)} {translate('common.to').toLowerCase()} {formatAirportInfo(reservation.end)}
+ {formatTransitLocationLabel(reservation.start)} {translate('common.to').toLowerCase()} {formatTransitLocationLabel(reservation.end)}
)}
@@ -217,7 +217,7 @@ function TripDetailsView({tripRoomReport, shouldShowHorizontalRule, tripTransact
if (!destinationReservation) {
return '';
}
- return `${translate('travel.flightTo')} ${formatAirportInfo(destinationReservation.reservation.end, true)}`;
+ return `${translate('travel.flightTo')} ${formatTransitLocationLabel(destinationReservation.reservation.end, true)}`;
}
case CONST.RESERVATION_TYPE.TRAIN:
if (reservations.length === 2 && firstReservation.start.shortName === lastReservation.end.shortName) {
diff --git a/src/libs/TripReservationUtils.ts b/src/libs/TripReservationUtils.ts
index e4badc79e01e..5c18405ed428 100644
--- a/src/libs/TripReservationUtils.ts
+++ b/src/libs/TripReservationUtils.ts
@@ -336,6 +336,9 @@ function getCarReservations(pnr: Pnr, travelers: PnrTraveler[]): ReservationItem
return reservationList;
}
+// Drop Trainline URN-style codes so the trip UI doesn't render them as station labels.
+const getRailStationShortName = (code: string | undefined) => (code && !code.startsWith('urn:') ? code : '');
+
function getRailReservations(pnr: Pnr, travelers: PnrTraveler[]): ReservationItem[] {
const reservationList: ReservationItem[] = [];
@@ -362,13 +365,13 @@ function getRailReservations(pnr: Pnr, travelers: PnrTraveler[]): ReservationIte
start: {
date: leg.departAt.iso8601,
longName: leg.originInfo.name,
- shortName: leg.originInfo.code,
+ shortName: getRailStationShortName(leg.originInfo.code),
cityName: leg.originInfo.cityName,
},
end: {
date: leg.arriveAt.iso8601,
longName: leg.destinationInfo.name,
- shortName: leg.destinationInfo.code,
+ shortName: getRailStationShortName(leg.destinationInfo.code),
cityName: leg.destinationInfo.cityName,
},
route: {
@@ -471,13 +474,13 @@ function getReservationsFromTripReport(tripReport?: Report, transactions?: Trans
return [];
}
-function formatAirportInfo(reservationTimeDetails: ReservationTimeDetails, hideAirportCode = false): string {
- const longName = reservationTimeDetails?.longName ? `${reservationTimeDetails?.longName} ` : '';
- let shortName = reservationTimeDetails?.shortName ? `${reservationTimeDetails?.shortName}` : '';
-
- shortName = longName && shortName ? `(${shortName})` : shortName;
-
- return !hideAirportCode ? `${longName}${shortName}` : longName;
+function formatTransitLocationLabel(reservationTimeDetails: ReservationTimeDetails, hideShortCode = false): string {
+ const longName = reservationTimeDetails?.longName ?? '';
+ const shortName = reservationTimeDetails?.shortName ?? '';
+ if (hideShortCode || !shortName) {
+ return longName;
+ }
+ return longName ? `${longName} (${shortName})` : `(${shortName})`;
}
function getPNRReservationDataFromTripReport(tripReport?: Report, transactions?: Transaction[]): ReservationPNRData[] {
@@ -560,7 +563,7 @@ export {
getReservationsFromTripReport,
getTripTotal,
getReservationDetailsFromSequence,
- formatAirportInfo,
+ formatTransitLocationLabel,
getPNRReservationDataFromTripReport,
getAirReservations,
isPnrCancelled,
diff --git a/src/pages/Travel/TrainTripDetails.tsx b/src/pages/Travel/TrainTripDetails.tsx
index aae615a0c757..2c1aaeb0b27a 100644
--- a/src/pages/Travel/TrainTripDetails.tsx
+++ b/src/pages/Travel/TrainTripDetails.tsx
@@ -7,6 +7,7 @@ import UserPills from '@components/UserPills';
import useLocalize from '@hooks/useLocalize';
import useThemeStyles from '@hooks/useThemeStyles';
import DateUtils from '@libs/DateUtils';
+import {formatTransitLocationLabel} from '@libs/TripReservationUtils';
import CONST from '@src/CONST';
import type {PersonalDetails} from '@src/types/onyx';
import type {Reservation} from '@src/types/onyx/Transaction';
@@ -22,9 +23,7 @@ function TrainTripDetails({reservation, personalDetails}: TrainTripDetailsProps)
const startDate = DateUtils.getFormattedTransportDateAndHour(new Date(reservation.start.date));
const endDate = DateUtils.getFormattedTransportDateAndHour(new Date(reservation.end.date));
- const trainRouteDescription = `${reservation.start.longName} (${reservation.start.shortName}) ${translate('common.conjunctionTo')} ${reservation.end.longName} (${
- reservation.end.shortName
- })`;
+ const trainRouteDescription = `${formatTransitLocationLabel(reservation.start)} ${translate('common.conjunctionTo')} ${formatTransitLocationLabel(reservation.end)}`;
const trainDuration = DateUtils.getFormattedDurationBetweenDates(translate, new Date(reservation.start.date), new Date(reservation.end.date));
const displayName = personalDetails?.displayName ?? reservation.travelerPersonalInfo?.name;
@@ -50,7 +49,7 @@ function TrainTripDetails({reservation, personalDetails}: TrainTripDetailsProps)
description={translate('travel.trainDetails.departs')}
descriptionTextStyle={[styles.textLabelSupporting, styles.mb1]}
titleComponent={{startDate.hour}}
- helperText={`${reservation.start.longName} (${reservation.start.shortName})`}
+ helperText={formatTransitLocationLabel(reservation.start)}
helperTextStyle={[styles.pb3, styles.mtn2]}
interactive={false}
/>
@@ -58,7 +57,7 @@ function TrainTripDetails({reservation, personalDetails}: TrainTripDetailsProps)
description={translate('travel.trainDetails.arrives')}
descriptionTextStyle={[styles.textLabelSupporting, styles.mb1]}
titleComponent={{endDate.hour}}
- helperText={`${reservation.end.longName} (${reservation.end.shortName})`}
+ helperText={formatTransitLocationLabel(reservation.end)}
helperTextStyle={[styles.pb3, styles.mtn2]}
interactive={false}
/>
diff --git a/tests/unit/TripReservationUtilsTest.ts b/tests/unit/TripReservationUtilsTest.ts
index 4addabcc12a8..5af0a2f5b272 100644
--- a/tests/unit/TripReservationUtilsTest.ts
+++ b/tests/unit/TripReservationUtilsTest.ts
@@ -1,5 +1,5 @@
/* cspell:disable */
-import {getAirReservations, getPNRReservationDataFromTripReport, getReservationsFromTripReport, isPnrCancelled} from '@libs/TripReservationUtils';
+import {formatTransitLocationLabel, getAirReservations, getPNRReservationDataFromTripReport, getReservationsFromTripReport, isPnrCancelled} from '@libs/TripReservationUtils';
import CONST from '@src/CONST';
import type {Pnr, TripData} from '@src/types/onyx/TripData';
import {airReservationPnrData, airReservationTravelers} from '../data/TripAirReservationData';
@@ -2692,6 +2692,61 @@ describe('TripReservationUtils', () => {
});
});
+ describe('rail shortName sanitization', () => {
+ it('should drop a URN-formatted code from rail shortName', () => {
+ const railPnrWithUrnCodes = JSON.parse(JSON.stringify(railPnr)) as typeof railPnr;
+ const leg = railPnrWithUrnCodes.data.railPnr?.legInfos.at(0);
+ if (leg) {
+ leg.originInfo.code = 'urn:trainline:public:nloc:at000408';
+ leg.destinationInfo.code = 'urn:trainline:public:nloc:at001685';
+ }
+
+ const report = createRandomReport(1, undefined);
+ report.tripData = {
+ tripID: 'trip123',
+ payload: {
+ ...basicTripData,
+ pnrs: [railPnrWithUrnCodes],
+ },
+ };
+
+ const result = getReservationsFromTripReport(report, []);
+ expect(result).toHaveLength(1);
+
+ const trainReservation = result.at(0)?.reservation;
+ expect(trainReservation?.type).toEqual(CONST.RESERVATION_TYPE.TRAIN);
+ expect(trainReservation?.start?.shortName).toEqual('');
+ expect(trainReservation?.end?.shortName).toEqual('');
+ });
+
+ it('should render only longName when shortName is empty (no trailing space, no empty parens)', () => {
+ expect(formatTransitLocationLabel({date: '', longName: 'Brockenhurst', shortName: '', cityName: ''})).toEqual('Brockenhurst');
+ });
+
+ it('should render longName with parenthesized shortName when both are present', () => {
+ expect(formatTransitLocationLabel({date: '', longName: 'Solana Beach station', shortName: 'SOL', cityName: ''})).toEqual('Solana Beach station (SOL)');
+ });
+
+ it('should preserve a clean station code in rail shortName', () => {
+ const report = createRandomReport(1, undefined);
+ report.tripData = {
+ tripID: 'trip123',
+ payload: {
+ ...basicTripData,
+ pnrs: [railPnr],
+ },
+ };
+
+ const result = getReservationsFromTripReport(report, []);
+ expect(result).toHaveLength(1);
+
+ const trainReservation = result.at(0)?.reservation;
+ expect(trainReservation?.type).toEqual(CONST.RESERVATION_TYPE.TRAIN);
+ expect(trainReservation?.start?.shortName).toEqual('STX');
+ expect(trainReservation?.end?.shortName).toEqual('STY');
+ });
+ });
+
describe('getPNRReservationDataFromTripReport', () => {
it('should return an empty array when there are no transactions and trip payload', () => {
const report = createRandomReport(1, undefined);