mirror of
https://github.com/mauriceboe/TREK.git
synced 2026-10-23 07:26:30 +00:00
fix(weather): day-local named anchor, cache ttl and past-days forecast window (#2167)
This commit is contained in:
@@ -157,7 +157,7 @@ export default function MJourneyEntrySheet({
|
||||
|
||||
const [placeResult, weatherResult] = await Promise.allSettled([
|
||||
mapsApi.reverse(pos.lat, pos.lng, language),
|
||||
weatherApi.getCurrent(pos.lat, pos.lng, 'en'),
|
||||
weatherApi.getCurrent(pos.lat, pos.lng, language),
|
||||
])
|
||||
if (!active) return
|
||||
if (placeResult.status === 'fulfilled') {
|
||||
|
||||
@@ -153,6 +153,11 @@ export default function MPlanTimeline({ planner, shell }: MPlanTimelineProps) {
|
||||
// the pill drops to icon-only there rather than saying it twice.
|
||||
dayLabel={editing ? '' : dayLabel}
|
||||
openLabel={t('day.overview')}
|
||||
// The chip still opens the day sheet, but its accessible name says
|
||||
// WHERE the forecast is for — a roadtrip day is ambiguous otherwise (#2167).
|
||||
weatherLabel={tl.weatherPlaceName
|
||||
? `${t('day.overview')} · ${t('day.weatherFor', { name: tl.weatherPlaceName })}`
|
||||
: undefined}
|
||||
onOpenDay={() => shell.openSheet('day', { dayId: day.id })}
|
||||
/>
|
||||
)}
|
||||
@@ -394,10 +399,12 @@ function EditHeader({ tl, planner, shell }: {
|
||||
* 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).
|
||||
*/
|
||||
function TimelineHeader({ tl, dayLabel, openLabel, onOpenDay }: {
|
||||
function TimelineHeader({ tl, dayLabel, openLabel, weatherLabel, onOpenDay }: {
|
||||
tl: MPlanTimelineController
|
||||
dayLabel: string
|
||||
openLabel: string
|
||||
/** Weather-chip label naming the forecast's anchor place (#2167); falls back to openLabel. */
|
||||
weatherLabel?: string
|
||||
onOpenDay: () => void
|
||||
}) {
|
||||
const WeatherIcon = weatherIconFor(tl.weather?.main)
|
||||
@@ -434,7 +441,8 @@ function TimelineHeader({ tl, dayLabel, openLabel, onOpenDay }: {
|
||||
<button
|
||||
type="button"
|
||||
onClick={onOpenDay}
|
||||
aria-label={openLabel}
|
||||
aria-label={weatherLabel || openLabel}
|
||||
title={tl.weatherPlaceName || undefined}
|
||||
className="ml-auto flex flex-none items-center gap-1 whitespace-nowrap px-1.5 py-1 text-[0.71875rem] font-semibold"
|
||||
>
|
||||
<WeatherIcon size={13} strokeWidth={2} />
|
||||
|
||||
@@ -99,25 +99,25 @@ export function useMPlanTimeline(planner: TripPlanner) {
|
||||
const upNext = useMemo(() => findUpNext(day, dayAssignments, now), [day, dayAssignments, now])
|
||||
|
||||
// ── Weather chip — anchored to the day's first located stop, else its hotel ──
|
||||
const weatherCoords = useMemo<{ lat: number; lng: number } | null>(() => {
|
||||
const weatherAnchor = useMemo<{ lat: number; lng: number; name: string | null } | null>(() => {
|
||||
const located = dayAssignments.find(a => a.place?.lat != null && a.place?.lng != null)
|
||||
if (located) return { lat: located.place!.lat!, lng: located.place!.lng! }
|
||||
if (located) return { lat: located.place!.lat!, lng: located.place!.lng!, name: located.place!.name ?? null }
|
||||
const hotel = day ? getDayBookendHotels(day, days, tripAccommodations).morning : undefined
|
||||
if (hotel && hotel.place_lat != null && hotel.place_lng != null) {
|
||||
return { lat: hotel.place_lat, lng: hotel.place_lng }
|
||||
return { lat: hotel.place_lat, lng: hotel.place_lng, name: hotel.place_name ?? null }
|
||||
}
|
||||
return null
|
||||
}, [day, dayAssignments, days, tripAccommodations])
|
||||
|
||||
const [weather, setWeather] = useState<WeatherResult | null>(null)
|
||||
useEffect(() => {
|
||||
if (!day?.date || !weatherCoords) { setWeather(null); return }
|
||||
if (!day?.date || !weatherAnchor) { setWeather(null); return }
|
||||
let cancelled = false
|
||||
weatherApi.get(weatherCoords.lat, weatherCoords.lng, day.date.slice(0, 10))
|
||||
weatherApi.get(weatherAnchor.lat, weatherAnchor.lng, day.date.slice(0, 10), language)
|
||||
.then(data => { if (!cancelled) setWeather(data.error ? null : data) })
|
||||
.catch(() => { if (!cancelled) setWeather(null) })
|
||||
return () => { cancelled = true }
|
||||
}, [day?.date, weatherCoords])
|
||||
}, [day?.date, weatherAnchor, language])
|
||||
|
||||
// ── Expanded auto-transit rows ──
|
||||
const [openTransitKeys, setOpenTransitKeys] = useState<Set<string>>(new Set())
|
||||
@@ -378,6 +378,7 @@ export function useMPlanTimeline(planner: TripPlanner) {
|
||||
|
||||
return {
|
||||
day, rows, hotelLegs, merged, hotelChips, weather, weatherTemp, upNext,
|
||||
weatherPlaceName: weatherAnchor?.name ?? null,
|
||||
language, timeFormat: settings.time_format,
|
||||
openTransitKeys, toggleTransit,
|
||||
moveRow, removeAssignment, editAssignment, editTransport, openTransitJourney,
|
||||
|
||||
@@ -68,11 +68,17 @@ export default function MDaySheet({ planner, shell }: MTripSheetsProps) {
|
||||
return [...(planner.assignments[String(day.id)] || [])].sort((a, b) => a.order_index - b.order_index)
|
||||
}, [day, planner.assignments])
|
||||
|
||||
// Weather anchor: the first assigned place with coordinates, else any trip place.
|
||||
const geoPlace = dayAssignments.find(a => a.place?.lat && a.place?.lng)?.place
|
||||
|| planner.places.find(p => p.lat && p.lng)
|
||||
const lat = geoPlace?.lat ?? null
|
||||
const lng = geoPlace?.lng ?? null
|
||||
// Weather anchor, day-local only (#2167): the day's first located stop, else the
|
||||
// hotel you wake up in (unconditional bookend lookup, mirroring useMPlanTimeline).
|
||||
// Never a place from another day — on a roadtrip that silently showed another
|
||||
// city's weather with nothing naming the place.
|
||||
const locatedPlace = dayAssignments.find(a => a.place?.lat && a.place?.lng)?.place
|
||||
const weatherHotel = day && !locatedPlace
|
||||
? getDayBookendHotels(day, planner.days, planner.tripAccommodations).morning
|
||||
: undefined
|
||||
const lat = locatedPlace?.lat ?? weatherHotel?.place_lat ?? null
|
||||
const lng = locatedPlace?.lng ?? weatherHotel?.place_lng ?? null
|
||||
const weatherPlaceName = locatedPlace?.name ?? weatherHotel?.place_name ?? null
|
||||
|
||||
const [weather, setWeather] = useState<WeatherResult | null>(null)
|
||||
const [weatherLoading, setWeatherLoading] = useState(false)
|
||||
@@ -304,6 +310,13 @@ export default function MDaySheet({ planner, shell }: MTripSheetsProps) {
|
||||
<span className="capitalize">{weather.description}</span>
|
||||
</span>
|
||||
</div>
|
||||
{/* Which place the forecast is for — on a roadtrip "the day's
|
||||
weather" is ambiguous without it (#2167). */}
|
||||
{weatherPlaceName && (
|
||||
<div className="mt-[6px] font-geist text-[0.625rem] text-m-faint">
|
||||
{t('day.weatherFor', { name: weatherPlaceName })}
|
||||
</div>
|
||||
)}
|
||||
{weather.type === 'climate' && (
|
||||
<div className="mt-[6px] font-geist text-[0.625rem] italic text-m-faint">{t('day.climateHint')}</div>
|
||||
)}
|
||||
|
||||
Reference in New Issue
Block a user