mirror of
https://github.com/mauriceboe/TREK.git
synced 2026-10-19 21:46:49 +00:00
495c5e95ec
The UP NEXT card kept pointing at this morning's first stop hours after it had started, and at day one of a trip that ended last month. A header promising what comes next, sitting above something that is over. It now stays away on a day that has passed and once today's timed plan has run out — a day whose stops carry no times at all still shows its first stop, there is nothing stale about that. The timeline card already collapses the reserved space when there is no up-next, so the layout closes cleanly. Seeding the plan day also lands on the next day still ahead when today falls into a gap in the dates, instead of dropping back to day one. A trip that is already over keeps opening on its first day. And a day that was selected before the shell mounted now counts as seeded, so clearing the selection is left alone instead of being re-seeded on the next render — which is what the guard was there for in the first place. Based on #2055.
54 lines
2.4 KiB
TypeScript
54 lines
2.4 KiB
TypeScript
/**
|
|
* Finding "today" inside a trip (#1567).
|
|
*
|
|
* Deliberately local, not UTC. `new Date().toISOString()` is the obvious way to
|
|
* get a YYYY-MM-DD and it is wrong for exactly the people this feature is for:
|
|
* someone in Tokyo opening the planner at 08:00 gets yesterday's date, and
|
|
* someone in Los Angeles at 17:00 gets tomorrow's. A trip day is a calendar day
|
|
* where the traveller is standing, so the comparison has to be made in their
|
|
* own clock.
|
|
*/
|
|
|
|
/** Today as YYYY-MM-DD in the viewer's own timezone. */
|
|
export function localToday(now: Date = new Date()): string {
|
|
const pad = (n: number) => String(n).padStart(2, '0')
|
|
return `${now.getFullYear()}-${pad(now.getMonth() + 1)}-${pad(now.getDate())}`
|
|
}
|
|
|
|
/**
|
|
* The id of the day that is today, or null when the trip is not running.
|
|
*
|
|
* Days without a date (a trip planned as "day 1..7" with no calendar attached)
|
|
* can never match, which is the intended outcome: there is nothing to jump to.
|
|
*/
|
|
export function findTodayDayId(days: Array<{ id: number; date?: string | null }>, now?: Date): number | null {
|
|
const today = localToday(now)
|
|
// Only the date part: the column is a date, but a caller could hand over an
|
|
// ISO timestamp and a trip would silently stop having a "today".
|
|
const match = days.find(d => typeof d.date === 'string' && d.date.slice(0, 10) === today)
|
|
return match ? match.id : null
|
|
}
|
|
|
|
/**
|
|
* The day a single-day view should open on: today while the trip is running,
|
|
* otherwise the next dated day still ahead. A trip with a gap in its dates —
|
|
* or one that has not started yet — lands on the day that is actually coming
|
|
* instead of on day one.
|
|
*
|
|
* Null once every dated day has passed, and for an undated itinerary, so the
|
|
* caller keeps its own fallback: a finished trip opens where it always did,
|
|
* on its first day.
|
|
*
|
|
* The desktop plan shows every day at once and only needs findTodayDayId; the
|
|
* phone plan is single-day and has to land somewhere, which is the only reason
|
|
* the two differ. Both read "today" off localToday, so that part cannot drift.
|
|
*/
|
|
export function findFocusDayId(days: Array<{ id: number; date?: string | null }>, now?: Date): number | null {
|
|
const today = localToday(now)
|
|
const ahead = days
|
|
.map(d => ({ id: d.id, date: typeof d.date === 'string' ? d.date.slice(0, 10) : '' }))
|
|
.filter(d => d.date.length === 10 && d.date >= today)
|
|
.sort((a, b) => a.date.localeCompare(b.date))
|
|
return ahead[0]?.id ?? null
|
|
}
|