refactor(planner): share the transport detail day and segment labels between desktop and phone

The transport detail dialog and the phone transport sheet name the booking's day and each segment's own booking code through transportDetailModel, next to the files filter they already share. The rest of the two views reads the booking differently on purpose and stays as it is.
This commit is contained in:
Maurice
2026-10-09 10:48:31 +02:00
parent fc3cc1bbbe
commit a4e3e83d3f
4 changed files with 52 additions and 11 deletions
@@ -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<string, unknown>, 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<string, unknown>, 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 }
@@ -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');
});
});
@@ -0,0 +1,25 @@
type Translate = (key: string, params?: Record<string, string | number>) => 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');
}
@@ -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) => (
<div key={i} className="flex items-center gap-2">
<span className="min-w-0 flex-1 truncate text-[0.71875rem] font-medium">
{[leg.from, leg.to].filter(Boolean).join(' → ') || t('reservations.confirmationCode')}
{segmentCodeLabel(leg, t)}
</span>
<button
type="button"