fix(planner): a leg's time zone sits behind a globe instead of beside the route

A departure or arrival row wrote its zone out ("Europe/Berlin") next to the
phase label, and on the narrow day plan that ate the width the route needed,
so "BER → HAM" ended in an ellipsis. The zone is now a small globe after the
time that names it on hover, the time pill carries only the time, and the
route gets its room back.
This commit is contained in:
Maurice
2026-09-29 21:16:11 +02:00
parent 5f700615b9
commit 91be785fbc
2 changed files with 35 additions and 4 deletions
@@ -1040,6 +1040,28 @@ describe('DayPlanSidebar', () => {
// ── Multi-day transport span phases ────────────────────────────────────
it('FE-PLANNER-DAYPLAN-233: a leg names its time zone behind a globe, not as text beside the route', async () => {
const day1 = buildDay({ id: 10, date: '2025-06-01', title: 'Out' })
const day2 = buildDay({ id: 11, date: '2025-06-02', title: 'In' })
const flight = buildReservation({
id: 202, type: 'flight', title: 'Overnight',
reservation_time: '2025-06-01T22:00:00',
reservation_end_time: '2025-06-02T06:00:00',
day_id: 10,
end_day_id: 11,
metadata: JSON.stringify({ departure_timezone: 'Europe/Berlin', arrival_timezone: 'America/New_York' }),
} as any)
const { container } = render(<DayPlanSidebar {...makeDefaultProps({ days: [day1, day2], reservations: [flight] })} />)
// Written out, the zone took the width the route needed.
expect(screen.queryByText(/Europe\/Berlin|America\/New_York/)).not.toBeInTheDocument()
const globes = container.querySelectorAll('[data-dp="transport-timezone"]')
expect([...globes].map(g => g.getAttribute('aria-label'))).toEqual(['Europe/Berlin', 'America/New_York'])
fireEvent.mouseEnter(globes[0])
expect(await screen.findByText('Europe/Berlin')).toBeInTheDocument()
})
it('FE-PLANNER-DAYPLAN-045: multi-day flight shows departure label on first day', () => {
const day1 = buildDay({ id: 10, date: '2025-06-01', title: 'Departure' })
const day2 = buildDay({ id: 11, date: '2025-06-02', title: 'Arrival' })
@@ -7,7 +7,7 @@ declare global { interface Window { __dragData: DragDataPayload | null } }
import React, { useState, useEffect, useLayoutEffect, useRef, useMemo, useCallback } from 'react'
import { avatarSrc } from '../../utils/avatarSrc'
import { safeHttpUrl } from '../../utils/safeUrl'
import { ChevronDown, ChevronRight, ChevronUp, Compass, Navigation, RotateCcw, ExternalLink, Pencil, GripVertical, Ticket, Plus, FileText, Trash2, Car, Lock, Hotel, Footprints, Route as RouteIcon, Bookmark, StickyNote, TramFront, Zap, MapPin, Wallet } from 'lucide-react'
import { ChevronDown, ChevronRight, ChevronUp, Compass, Navigation, RotateCcw, ExternalLink, Pencil, GripVertical, Ticket, Plus, FileText, Trash2, Car, Lock, Hotel, Footprints, Route as RouteIcon, Bookmark, StickyNote, TramFront, Zap, MapPin, Wallet, Globe } from 'lucide-react'
import { type PickedPlace } from './TransitSearchPanel'
import { buildTransitLeg, buildTransitNameIndex } from './transitLeg'
import { assignmentsApi, reservationsApi, daysApi } from '../../api/client'
@@ -2559,15 +2559,24 @@ const DayPlanSidebar = React.memo(function DayPlanSidebar(props: DayPlanSidebarP
const timeText = [
dispTime ? formatTime(dispTime, locale, timeFormat) : '',
spanPhase === 'single' && endTime ? `– ${formatTime(endTime, locale, timeFormat)}` : '',
meta.departure_timezone && spanPhase === 'start' ? meta.departure_timezone : '',
meta.arrival_timezone && spanPhase === 'end' ? meta.arrival_timezone : '',
].filter(Boolean).join(' ')
if (!timeText && !spanLabel && !transitMeta && !subtitle) return null
// The zone a leg's time is read in. Written out it took the width
// the route needs, so it is a globe that names it on hover.
const timeZone = (spanPhase === 'start' && meta.departure_timezone) || (spanPhase === 'end' && meta.arrival_timezone) || ''
if (!timeText && !timeZone && !spanLabel && !transitMeta && !subtitle) return null
return (
<div className="mt-0.5 flex min-w-0 items-center gap-1.5">
{/* Where in a multi-day span this day falls, said quietly beside its time. */}
{spanLabel && <SoftPill caps>{spanLabel}</SoftPill>}
{timeText && <TimePill>{timeText}</TimePill>}
{timeZone && (
<Tooltip label={timeZone} placement="top">
<span role="img" aria-label={timeZone} data-dp="transport-timezone"
className="grid h-[19px] w-[19px] flex-none place-items-center rounded-full bg-surface-card text-content-faint shadow-sm">
<Globe size={10} strokeWidth={2.2} aria-hidden="true" />
</span>
</Tooltip>
)}
{transitMeta ? (
<span className="flex min-w-0 items-center"><TransitLegChips legs={transitMeta.legs} size="sm" t={t} /></span>
) : subtitle && (