From 9a823c0bc2fd32a37b3a8f1aaf9d489b2e34b2c5 Mon Sep 17 00:00:00 2001 From: Maurice Date: Thu, 2 Jul 2026 23:05:19 +0200 Subject: [PATCH] feat(planner): walk legs as centred dividers + journey-card note line MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Walk segments in the journey modal and the day-plan inline itinerary collapse from two lines into a single centred divider โ€” dashed rules left and right, the walk in the middle (foot icon, destination, minutes). Leg meta badges sit tighter under their titles. The Transports-tab journey card shows a dimmed first-line note preview, and the journey modal now reads the reservation from the live store, so an update is visible the moment the entry reopens. --- .../components/Planner/ReservationsPanel.tsx | 10 ++- .../Planner/TransitJourneyModal.tsx | 40 +++++------- .../src/components/Planner/transitDisplay.tsx | 63 ++++++++++++------- client/src/pages/TripPlannerPage.tsx | 2 +- 4 files changed, 64 insertions(+), 51 deletions(-) diff --git a/client/src/components/Planner/ReservationsPanel.tsx b/client/src/components/Planner/ReservationsPanel.tsx index bb975504c..9ad2eecb6 100644 --- a/client/src/components/Planner/ReservationsPanel.tsx +++ b/client/src/components/Planner/ReservationsPanel.tsx @@ -9,7 +9,7 @@ import { Plane, Hotel, Utensils, Train, Car, Ship, Bus, Sailboat, Bike, CarTaxiFront, Route, Ticket, FileText, MapPin, Calendar, Hash, CheckCircle2, Circle, Pencil, Trash2, Plus, ChevronDown, ChevronRight, Users, ExternalLink, BookMarked, Lightbulb, Link2, Clock, ArrowRight, AlertCircle, Download, - TramFront, Footprints, + TramFront, Footprints, StickyNote, } from 'lucide-react' import { openFile } from '../../utils/fileDownload' import { TransitTitle, TransitLegChips, TransitMetaBadges, fmtTransitDuration } from './transitDisplay' @@ -531,7 +531,7 @@ function TransitJourneyCard({ r, days, onOpen, onDelete, canEdit }: {
-
+
)} + {r.notes && ( +
+ + {r.notes.split('\n')[0]} +
+ )} {confirmOpen && ReactDOM.createPortal(
{ e.stopPropagation(); setConfirmOpen(false) }}>
e.stopPropagation()}> diff --git a/client/src/components/Planner/TransitJourneyModal.tsx b/client/src/components/Planner/TransitJourneyModal.tsx index d71736411..e7399cbc5 100644 --- a/client/src/components/Planner/TransitJourneyModal.tsx +++ b/client/src/components/Planner/TransitJourneyModal.tsx @@ -8,7 +8,7 @@ import ConfirmDialog from '../shared/ConfirmDialog' import { useTranslation } from '../../i18n' import { useSettingsStore } from '../../store/settingsStore' import { splitReservationDateTime, formatTime } from '../../utils/formatters' -import { TransitTitle, TransitMetaBadges, fmtTransitDuration } from './transitDisplay' +import { TransitTitle, TransitMetaBadges, TransitWalkDivider, fmtTransitDuration } from './transitDisplay' import type { Reservation } from '../../types' /** @@ -231,40 +231,30 @@ export default function TransitJourneyModal({ reservation, onClose, onSave, onDe
{(transit.legs as TransitLegMeta[]).map((leg, i) => { - const isWalk = leg.mode === 'WALK' + if (leg.mode === 'WALK') return const mins = leg.duration ? Math.round(leg.duration / 60) : null return (
- {!isWalk && leg.from?.time ? leg.from.time : ''} + {leg.from?.time || ''}
- {isWalk ? ( - - - - ) : ( - - {leg.line || leg.mode} - - )} + + {leg.line || leg.mode} +
- {isWalk - ? {t('transit.walkTo', { name: leg.to?.name || '' })} - : <> - {leg.from?.name} - {leg.from?.track && ({t('transit.platform', { track: leg.from.track })})} - - {leg.to?.name} - } + {leg.from?.name} + {leg.from?.track && ({t('transit.platform', { track: leg.from.track })})} + + {leg.to?.name}
-
+
diff --git a/client/src/components/Planner/transitDisplay.tsx b/client/src/components/Planner/transitDisplay.tsx index 219df3828..9a9cdb48e 100644 --- a/client/src/components/Planner/transitDisplay.tsx +++ b/client/src/components/Planner/transitDisplay.tsx @@ -20,6 +20,31 @@ export interface TransitLegDisplay { to?: { name?: string; time?: string | null; track?: string | null } } + +/** + * A walk leg as a one-line centred divider โ€” dashed rules left and right, + * the walk itself in the middle (๐Ÿšถ Walk to X ยท 4 min). Used by the journey + * modal and the day-plan inline itinerary. + */ +export function TransitWalkDivider({ leg, t, size = 'md' }: { + leg: TransitLegDisplay + t: (k: string, p?: Record) => string + size?: 'sm' | 'md' +}) { + const mins = leg.duration ? Math.round(leg.duration / 60) : null + const ruleStyle: React.CSSProperties = { flex: 1, borderTop: '1px dashed var(--border-primary)', opacity: 0.6, minWidth: 12 } + return ( +
+ + + + {t('transit.walkTo', { name: leg.to?.name || '' })}{mins ? ` ยท ${t('transit.min', { count: mins })}` : ''} + + +
+ ) +} + /** * 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. @@ -31,40 +56,32 @@ export function TransitItineraryInline({ legs, t }: { return (
{legs.map((leg, i) => { - const isWalk = leg.mode === 'WALK' + if (leg.mode === 'WALK') return const mins = leg.duration ? Math.round(leg.duration / 60) : null return (
- {!isWalk && leg.from?.time ? leg.from.time : ''} + {leg.from?.time || ''} + + + {leg.line || leg.mode} - {isWalk ? ( - - ) : ( - - {leg.line || leg.mode} - - )}
- {isWalk - ? {t('transit.walkTo', { name: leg.to?.name || '' })} - : <> - {leg.from?.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, + leg.stops ? t('transit.stops', { count: leg.stops }) : null, + leg.from?.track ? t('transit.platform', { track: leg.from.track }) : null, ].filter(Boolean).join(' ยท ')}
diff --git a/client/src/pages/TripPlannerPage.tsx b/client/src/pages/TripPlannerPage.tsx index 5bd0c3b73..6cd08866a 100644 --- a/client/src/pages/TripPlannerPage.tsx +++ b/client/src/pages/TripPlannerPage.tsx @@ -724,7 +724,7 @@ export default function TripPlannerPage(): React.ReactElement | null { {/* Journey view for a saved public-transit entry (#1065) */} {transitJourney && ( r.id === transitJourney.id) ?? transitJourney} canEdit={can('day_edit', trip)} onClose={() => setTransitJourney(null)} onSave={async (fields) => { await tripActions.updateReservation(tripId, transitJourney.id, fields); setTransitJourney(null) }}