diff --git a/client/src/mobile/screens/trip/MTripShell.tsx b/client/src/mobile/screens/trip/MTripShell.tsx index 2cdda4137..d940921ce 100644 --- a/client/src/mobile/screens/trip/MTripShell.tsx +++ b/client/src/mobile/screens/trip/MTripShell.tsx @@ -40,7 +40,8 @@ export type MTripCollabTab = 'chat' | 'notes' | 'polls' /** * Currently open bottom/floating sheet. Well-known ids (owned by the sheets * screen): 'day' (payload { dayId }), 'days', 'mehr', 'note' (payload - * { dayId, note? }), 'transport' (payload { reservationId }), 'bract' + * { dayId, note? }), 'accommodation' (payload { dayId?, accId?, from? }), + * 'transport' (payload { reservationId }), 'bract' * (payload { placeId, dayPicker? }), 'import', 'export', 'members', * 'tripedit', 'bags', 'task'. The place inspector keys off the planner's * place selection instead of a sheet id, and planner-backed editors (place diff --git a/client/src/mobile/screens/trip/plan/MPlanTimeline.tsx b/client/src/mobile/screens/trip/plan/MPlanTimeline.tsx index cb0c030e1..a3f1cb59e 100644 --- a/client/src/mobile/screens/trip/plan/MPlanTimeline.tsx +++ b/client/src/mobile/screens/trip/plan/MPlanTimeline.tsx @@ -10,7 +10,7 @@ import { fmtTransitDuration } from '../../../../components/Planner/transitDispla import { formatTime } from '../../../../utils/formatters' import { useMPlanTimeline, type MPlanTimelineController } from './useMPlanTimeline' import { cityPillsForDay, weatherIconFor } from './planTimelineModel' -import type { PlanRow } from './planTimelineModel' +import type { HotelChip, PlanRow } from './planTimelineModel' import { useMPlanDragReorder } from './useMPlanDragReorder' import { useTouchDragBridge } from '../../../../hooks/useTouchDragBridge' import { useIsTouch } from '../../../../hooks/useIsTouch' @@ -56,6 +56,21 @@ export default function MPlanTimeline({ planner, shell }: MPlanTimelineProps) { const daySchedule = usePluginDaySchedule(planner.tripId) const day = tl.day const dayId = day?.id + // A stay chip opens the stay, the same way the stay card in the day sheet + // does: the editor for members who may edit days, otherwise the hotel's + // place. A stay with neither still leads to the day sheet, so the chip + // never goes dead (#2210). + const openStay = (chip: HotelChip) => { + if (!day) return + if (canEdit) shell.openSheet('accommodation', { dayId: day.id, accId: chip.accId, from: 'timeline' }) + else if (chip.placeId != null) planner.handlePlaceClick(chip.placeId) + else shell.openSheet('day', { dayId: day.id }) + } + // The icon alone tells check-out from check-in; the accessible name says it. + const stayLabel = (chip: HotelChip) => { + const kind = chip.variant === 'checkin' ? t('day.checkIn') : chip.variant === 'checkout' ? t('day.checkOut') : t('mobileTrip.stay') + return `${kind} · ${chip.name}${chip.time ? ` · ${chip.time.slice(0, 5)}` : ''}` + } // Mirrors MDaySheet's own label so the pill and the sheet it opens agree. const dayLabel = day ? day.title || t('planner.dayN', { n: day.day_number || planner.days.indexOf(day) + 1 }) @@ -159,6 +174,8 @@ export default function MPlanTimeline({ planner, shell }: MPlanTimelineProps) { ? `${t('day.overview')} · ${t('day.weatherFor', { name: tl.weatherPlaceName })}` : undefined} onOpenDay={() => shell.openSheet('day', { dayId: day.id })} + onOpenStay={openStay} + stayLabel={stayLabel} /> )} @@ -396,16 +413,20 @@ function EditHeader({ tl, planner, shell }: { * (check-out / check-in / stay) and the weather chip. * * The day pill leads unconditionally. It used to be the accommodation chip that - * carried this, which left a day without a stay — and, with no weather either, - * the whole header — with no way into the day sheet at all (#2004). + * carried this, which left a day without a stay (and, with no weather either, + * the whole header) with no way into the day sheet at all (#2004). Since then + * the stay chips have a target of their own: tapping a hotel opens that stay, + * not the day (#2210). Only the weather chip still shares the day pill's target. */ -function TimelineHeader({ tl, dayLabel, openLabel, weatherLabel, onOpenDay }: { +function TimelineHeader({ tl, dayLabel, openLabel, weatherLabel, onOpenDay, onOpenStay, stayLabel }: { tl: MPlanTimelineController dayLabel: string openLabel: string /** Weather-chip label naming the forecast's anchor place (#2167); falls back to openLabel. */ weatherLabel?: string onOpenDay: () => void + onOpenStay: (chip: HotelChip) => void + stayLabel: (chip: HotelChip) => string }) { const WeatherIcon = weatherIconFor(tl.weather?.main) return ( @@ -428,7 +449,8 @@ function TimelineHeader({ tl, dayLabel, openLabel, weatherLabel, onOpenDay }: {