- {[
- day ? (day.title || t('dayplan.dayN', { n: day.day_number })) : null,
- dateStr,
- time ? `${formatTime(time, locale, timeFormat)}${endTime ? ` – ${formatTime(endTime, locale, timeFormat)}` : ''}` : null,
- mins ? t('transit.min', { count: mins }) : null,
- ].filter(Boolean).join(' · ')}
+
+
{canEdit && (
@@ -555,7 +555,7 @@ function TransitJourneyCard({ r, days, onOpen, onDelete, canEdit }: {
{transit && (
- {[
- !isWalk && leg.from?.time ? `${leg.from.time}${leg.to?.time ? ` – ${leg.to.time}` : ''}` : null,
- mins ? t('transit.min', { count: mins }) : null,
- !isWalk && leg.stops ? t('transit.stops', { count: leg.stops }) : null,
- !isWalk && leg.headsign ? `→ ${leg.headsign}` : null,
- !isWalk ? leg.agency : null,
- ].filter(Boolean).join(' · ')}
+
+
diff --git a/client/src/components/Planner/TransitSearchPanel.tsx b/client/src/components/Planner/TransitSearchPanel.tsx
index cf91986d9..fe6acad7b 100644
--- a/client/src/components/Planner/TransitSearchPanel.tsx
+++ b/client/src/components/Planner/TransitSearchPanel.tsx
@@ -2,6 +2,7 @@ import React, { useEffect, useMemo, useRef, useState } from 'react'
import tzlookup from 'tz-lookup'
import { ArrowLeftRight, ArrowRight, Bus, CableCar, ChevronDown, ChevronUp, Clock, Footprints, MapPin, Sailboat, Search, Train, TramFront, TrainFront } from 'lucide-react'
import CustomTimePicker from '../shared/CustomTimePicker'
+import { TransitMetaBadges } from './transitDisplay'
import { transitApi } from '../../api/client'
import { useSettingsStore } from '../../store/settingsStore'
import { useToast } from '../shared/Toast'
@@ -246,14 +247,20 @@ function ItineraryCard({ it, tzFrom, tzTo, is12h, expanded, onToggle, onAdd, add
{leg.mode === 'WALK' ? t('transit.walkTo', { name: leg.to.name }) : leg.from.name}
{leg.from.track && leg.mode !== 'WALK' &&
+
{leg.mode === 'WALK' ? (
-
{fmtDuration(leg.duration, t)}{leg.distance ? ` · ${leg.distance >= 1000 ? `${(leg.distance / 1000).toFixed(1)} km` : `${leg.distance} m`}` : ''}
+
= 1000 ? `${(leg.distance / 1000).toFixed(1)} km` : `${leg.distance} m`) : '' },
+ ]} />
) : (
<>
- {leg.headsign && {leg.headsign}}
- {fmtDuration(leg.duration, t)}{leg.intermediateStops > 0 ? ` · ${t('transit.stops', { count: leg.intermediateStops })}` : ''}
+ 0 ? t('transit.stops', { count: leg.intermediateStops }) : '' },
+ ]} />
>
)}
diff --git a/client/src/components/Planner/transitDisplay.tsx b/client/src/components/Planner/transitDisplay.tsx
index dea4f92e4..219df3828 100644
--- a/client/src/components/Planner/transitDisplay.tsx
+++ b/client/src/components/Planner/transitDisplay.tsx
@@ -1,5 +1,5 @@
import React from 'react'
-import { Footprints, MoveRight } from 'lucide-react'
+import { Footprints, MoveRight, type LucideIcon } from 'lucide-react'
/**
* Shared display bits for public-transit entries (#1065) — the timeline row,
@@ -14,6 +14,109 @@ export interface TransitLegDisplay {
line_color?: string | null
line_text_color?: string | null
duration?: number
+ headsign?: string | null
+ stops?: number
+ from?: { name?: string; time?: string | null; track?: string | null }
+ to?: { name?: string; time?: string | null; track?: string | null }
+}
+
+/**
+ * The itinerary folded out right inside the day-plan row (#1065): one compact
+ * line per leg — time, badge or foot icon, stations — sized for the sidebar.
+ */
+export function TransitItineraryInline({ legs, t }: {
+ legs: TransitLegDisplay[]
+ t: (k: string, p?: Record
) => string
+}) {
+ return (
+
+ {legs.map((leg, i) => {
+ const isWalk = leg.mode === 'WALK'
+ const mins = leg.duration ? Math.round(leg.duration / 60) : null
+ return (
+
+
+ {!isWalk && leg.from?.time ? leg.from.time : ''}
+
+ {isWalk ? (
+
+ ) : (
+
+ {leg.line || leg.mode}
+
+ )}
+
+
+ {isWalk
+ ? {t('transit.walkTo', { name: leg.to?.name || '' })}
+ : <>
+ {leg.from?.name}
+
+ {leg.to?.name}
+ >}
+
+
+ {[
+ mins ? t('transit.min', { count: mins }) : null,
+ !isWalk && leg.stops ? t('transit.stops', { count: leg.stops }) : null,
+ !isWalk && leg.from?.track ? t('transit.platform', { track: leg.from.track }) : null,
+ ].filter(Boolean).join(' · ')}
+
+
+
+ )
+ })}
+
+ )
+}
+
+
+/** "1 h 9 min" / "42 min" from seconds — shared by all transit surfaces. */
+export function fmtTransitDuration(seconds: number, t: (k: string, p?: Record) => string): string {
+ const mins = Math.round(seconds / 60)
+ if (mins < 60) return t('transit.min', { count: mins })
+ const h = Math.floor(mins / 60)
+ const m = mins % 60
+ return m > 0 ? `${h} h ${m} min` : `${h} h`
+}
+
+export interface TransitMetaItem {
+ icon?: LucideIcon
+ text: string
+ /** De-emphasised (operator names and the like). */
+ dim?: boolean
+}
+
+/**
+ * A quiet badge row replacing dot-joined meta text ("21:06 - 22:15 - 69 min -
+ * 14 stops ..."): each fact becomes a small chip, optional icon in front.
+ */
+export function TransitMetaBadges({ items, size = 'md' }: { items: TransitMetaItem[]; size?: 'sm' | 'md' }) {
+ const font = size === 'sm' ? 'calc(10px * var(--fs-scale-caption, 1))' : 'calc(10.5px * var(--fs-scale-caption, 1))'
+ return (
+
+ {items.filter(i => i.text).map(({ icon: Icon, text, dim }, i) => (
+
+ {Icon && }
+ {text}
+
+ ))}
+
+ )
}
/** Renders "From → To" titles with an arrow icon instead of the text glyph. */