refactor(planner): project a day's tours by assignment in one place

The day route plan and the day plan sidebar both built the map of a day's
stops projected onto their tours, each stage on its own stretch, by hand.
projectedById in dayTourProjection builds it for both, and the sidebar's
drive runs reuse the route plan's point type instead of spelling it out
twice.
This commit is contained in:
Maurice
2026-10-20 18:26:00 +02:00
parent 49e306ea48
commit 49dd2db040
3 changed files with 17 additions and 6 deletions
+2 -2
View File
@@ -3,7 +3,7 @@ import { getTransportRouteEndpoints, getTransportForDay, getMergedItems, isCarri
import { getDayBookendHotels, shouldDrawMorningLeg, shouldDrawEveningLeg, type CarrierEdge } from '../../utils/dayOrder'
import { withinDriveRange } from '@trek/shared/roadtrip'
import type { Accommodation, AssignmentsMap, Day, Place, Reservation } from '../../types'
import { projectDayItinerary, type TourStageStops } from './dayTourProjection'
import { projectedById, type TourStageStops } from './dayTourProjection'
export const TRANSPORT_TYPES = ['flight', 'train', 'bus', 'car', 'taxi', 'bicycle', 'cruise', 'ferry', 'cable_car', 'transit', 'transport_other']
@@ -85,7 +85,7 @@ export function buildDayRouteRuns(dayId: number, input: DayRouteInputs): DayRout
days: allDays,
})
const merged = getMergedItems({ dayAssignments: da, dayNotes: [], dayTransports, dayId })
const projected = new Map(projectDayItinerary(da, input.toursEnabled ?? false, input.places, input.tourStageStops).map(item => [item.assignment.id, item]))
const projected = projectedById(da, input.toursEnabled ?? false, input.places, input.tourStageStops)
type Entry =
| { kind: 'place'; id: number; lat: number; lng: number; pos: number; time: string | null; mode: string | null; incoming: string | null }
@@ -85,4 +85,14 @@ export function projectDayItinerary(
loop: !!start && !!end && start.lat === end.lat && start.lng === end.lng,
valid: !!end } as const
})
}
/** The day's stops projected as `projectDayItinerary` does, by assignment id, for the route that runs through them. */
export function projectedById(
assignments: Assignment[],
toursEnabled: boolean,
places?: readonly Place[],
stageStops?: TourStageStops,
): Map<number, DayItineraryItem> {
return new Map(projectDayItinerary(assignments, toursEnabled, places, stageStops).map(item => [item.assignment.id, item]))
}
@@ -69,7 +69,8 @@ import { RouteConnector, HotelRouteConnector } from './DayPlanSidebarRouteConnec
import { resolveLegMode } from './legMode'
import { useLegModeActions } from './useLegModeActions'
import { dayExportStops, fillAroundLocked } from './dayRoute'
import { projectDayItinerary, type TourStageStops } from '../Map/dayTourProjection'
import { projectDayItinerary, projectedById, type TourStageStops } from '../Map/dayTourProjection'
import type { DayRoutePoint } from '../Map/dayRoutePlan'
import { usePluginDaySchedule, usePluginDayTints, dayTintBackground, dayTinted, PluginDayScheduleRow, formatScheduleMinutes } from '../Plugins/PluginDaySchedule'
import { MobileAddPlaceButton } from './DayPlanSidebarMobileAddPlaceButton'
import { DayPlanSidebarToolbar } from './DayPlanSidebarToolbar'
@@ -623,12 +624,12 @@ function useDayPlanSidebar(props: DayPlanSidebarProps) {
// legs to draw. Side-effect free, so the async loop below only does OSRM I/O.
const planDay = (dayId: number) => {
const merged = mergedItemsMap[dayId] || []
const projected = new Map(projectDayItinerary(assignments[String(dayId)] || [], toursEnabled, places, props.tourStageStops).map(item => [item.assignment.id, item]))
const projected = projectedById(assignments[String(dayId)] || [], toursEnabled, places, props.tourStageStops)
// Each run point carries the ORIGIN assignment's per-segment travel mode
// (#1281): the leg from this point to the next is routed with `mode` (null =
// inherit the day default). Transport endpoints carry no mode → day default.
const runs: { id: number; lat: number; lng: number; isPlace: boolean; leg_transport_mode?: string | null; incoming_leg_transport_mode?: string | null; assignmentId?: number }[][] = []
let cur: { id: number; lat: number; lng: number; isPlace: boolean; leg_transport_mode?: string | null; incoming_leg_transport_mode?: string | null; assignmentId?: number }[] = []
const runs: (DayRoutePoint & { id: number })[][] = []
let cur: (DayRoutePoint & { id: number })[] = []
// A run is only a real drive when it holds an actual place. Two back-to-back
// transports (e.g. two flights on one day) would otherwise pair the first's
// arrival with the second's departure into a phantom airport→airport leg — the