diff --git a/client/src/components/Planner/DayPlanSidebarTransportDetailModal.tsx b/client/src/components/Planner/DayPlanSidebarTransportDetailModal.tsx index ddd614c2b..6c580622b 100644 --- a/client/src/components/Planner/DayPlanSidebarTransportDetailModal.tsx +++ b/client/src/components/Planner/DayPlanSidebarTransportDetailModal.tsx @@ -12,6 +12,7 @@ import { parseReservationMetadata } from '../../utils/flightLegs' import { BookingDialogHeader } from './bookings/BookingDialogShell' import { BOX, Eyebrow, Field, fs, toneColor, toneOf } from './bookings/bookingParts' import { filesFor } from '../../utils/reservationFiles' +import { bookingDayLabel, segmentCodeLabel } from './transportDetailModel' import { TransitLegs, transitSummary } from './bookings/transitParts' import type { Reservation } from '../../types' @@ -185,8 +186,7 @@ function detailCells(res: Reservation, meta: Record, t: Transla if (legs.length > 1) { for (const leg of legs) { if (!leg?.confirmation_number) continue - const segment = [leg.from, leg.to].filter(Boolean).join(' → ') - add(segment || t('reservations.confirmationCode'), leg.confirmation_number, true) + add(segmentCodeLabel(leg, t), leg.confirmation_number, true) } } return cells @@ -196,9 +196,7 @@ function detailCells(res: Reservation, meta: Record, t: Transla function whenOf(res: Reservation, locale: string, timeFormat: string): { day: string; time: string } { const { date, time } = splitReservationDateTime(res.reservation_time) const { time: endTime } = splitReservationDateTime(res.reservation_end_time) - const day = date - ? new Date(date + 'T00:00:00Z').toLocaleDateString(locale, { weekday: 'short', day: 'numeric', month: 'short', timeZone: 'UTC' }) - : '' + const day = date ? bookingDayLabel(date, locale) : '' const start = time ? formatTime(time, locale, timeFormat) : '' const end = endTime ? formatTime(endTime, locale, timeFormat) : '' return { day, time: start && end ? `${start} → ${end}` : start } diff --git a/client/src/components/Planner/transportDetailModel.test.ts b/client/src/components/Planner/transportDetailModel.test.ts new file mode 100644 index 000000000..94728a6de --- /dev/null +++ b/client/src/components/Planner/transportDetailModel.test.ts @@ -0,0 +1,21 @@ +// FE-PLANNER-TRANSPORTDETAIL-001 to -002: the labels the desktop transport detail +// and the phone transport sheet both read off a booking. +import { describe, expect, it } from 'vitest'; + +import { bookingDayLabel, segmentCodeLabel } from './transportDetailModel'; + +const t = (key: string) => `t:${key}`; + +describe('transportDetailModel', () => { + it('FE-PLANNER-TRANSPORTDETAIL-001: the day reads as weekday, day and month, on the calendar date itself', () => { + expect(bookingDayLabel('2026-07-01', 'en-US')).toBe('Wed, Jul 1'); + expect(bookingDayLabel('2026-12-31', 'en-US')).toBe('Thu, Dec 31'); + }); + + it('FE-PLANNER-TRANSPORTDETAIL-002: a segment code is listed under its route, or the plain code label', () => { + expect(segmentCodeLabel({ from: 'MUC', to: 'FRA' }, t)).toBe('MUC → FRA'); + expect(segmentCodeLabel({ from: 'MUC', to: null }, t)).toBe('MUC'); + expect(segmentCodeLabel({ to: 'FRA' }, t)).toBe('FRA'); + expect(segmentCodeLabel({ from: null, to: '' }, t)).toBe('t:reservations.confirmationCode'); + }); +}); diff --git a/client/src/components/Planner/transportDetailModel.ts b/client/src/components/Planner/transportDetailModel.ts new file mode 100644 index 000000000..431962a0a --- /dev/null +++ b/client/src/components/Planner/transportDetailModel.ts @@ -0,0 +1,25 @@ +type Translate = (key: string, params?: Record) => string; + +/** + * What the desktop transport detail dialog and the phone transport sheet both read + * off a booking. The booking's files come from utils/reservationFiles. + */ + +/** The day a booking runs on, as the head of either detail names it, in the locale's order ("Wed, Jul 1" in en-US). */ +export function bookingDayLabel(date: string, locale: string): string { + return new Date(`${date}T00:00:00Z`).toLocaleDateString(locale, { + weekday: 'short', + day: 'numeric', + month: 'short', + timeZone: 'UTC', + }); +} + +/** + * The name a segment's own booking code is listed under (#1943): where it leaves + * from and where it goes, or the plain booking code label when the segment names + * neither. + */ +export function segmentCodeLabel(leg: { from?: string | null; to?: string | null }, t: Translate): string { + return [leg.from, leg.to].filter(Boolean).join(' → ') || t('reservations.confirmationCode'); +} diff --git a/client/src/mobile/screens/trip/sheets/MTransportSheet.tsx b/client/src/mobile/screens/trip/sheets/MTransportSheet.tsx index 22c31294e..e7a40ecac 100644 --- a/client/src/mobile/screens/trip/sheets/MTransportSheet.tsx +++ b/client/src/mobile/screens/trip/sheets/MTransportSheet.tsx @@ -10,6 +10,7 @@ import { getFlightLegs, getTrainLegs, usesStationRoute } from '../../../../utils import { openFile } from '../../../../utils/fileDownload' import { runsOnDay } from '../../../../utils/reservationRoutes' import { filesFor } from '../../../../utils/reservationFiles' +import { bookingDayLabel, segmentCodeLabel } from '../../../../components/Planner/transportDetailModel' import type { Reservation } from '../../../../types' import { Eyebrow, INNER_CLS, StatBox, TileHeader, displayTime } from './MTripSheetUi' @@ -88,11 +89,7 @@ export default function MTransportSheet({ planner, shell }: MTripSheetsProps) { if (meta.flight_number) subParts.push(meta.flight_number) if (meta.train_number) subParts.push(meta.train_number) if (from?.name && to?.name) subParts.push(`${from.name} → ${to.name}`) - else if (date) { - subParts.push(new Date(`${date}T00:00:00Z`).toLocaleDateString(locale, { - weekday: 'short', day: 'numeric', month: 'short', timeZone: 'UTC', - })) - } + else if (date) subParts.push(bookingDayLabel(date, locale)) const seat = meta.seat const platform = meta.platform @@ -247,7 +244,7 @@ export default function MTransportSheet({ planner, shell }: MTripSheetsProps) { {legCodes.map((leg, i) => (
- {[leg.from, leg.to].filter(Boolean).join(' → ') || t('reservations.confirmationCode')} + {segmentCodeLabel(leg, t)}