mirror of
https://github.com/mauriceboe/TREK.git
synced 2026-06-21 22:31:46 +00:00
feat(planner): real road routes (OSRM) with travel-time connectors (#1060)
* feat(planner): real road routes (OSRM) with travel-time connectors Replace the straight-line "as the crow flies" route with real OSRM road geometry (FOSSGIS routed-car/-foot) and an Apple-Maps style render (blue casing under a lighter core) on both the Leaflet and Mapbox GL maps. Routes are off by default and toggled per session, with a driving/walking mode switch in the day footer. Each day shows per-segment travel time/distance connectors between places, computed from the OSRM legs and split at transport bookings. Also redesigns the day header for visual consistency: vertical number+weather capsule, name with a divider before the date, subtle hotel/rental pills that stay on one line, and a hover-revealed 2x2 action square (edit / add transport / add note / collapse). Drops the Google Maps button. * test(planner): update route hook tests for calculateRouteWithLegs
This commit is contained in:
@@ -269,6 +269,10 @@ export default function TripPlannerPage(): React.ReactElement | null {
|
||||
const [showTransportModal, setShowTransportModal] = useState<boolean>(false)
|
||||
const [editingTransport, setEditingTransport] = useState<Reservation | null>(null)
|
||||
const [transportModalDayId, setTransportModalDayId] = useState<number | null>(null)
|
||||
// Manual route planning: off by default, toggled from the day-plan footer. Mode
|
||||
// (driving/walking) is per-session and selects which travel time the connectors show.
|
||||
const [routeShown, setRouteShown] = useState(false)
|
||||
const [routeProfile, setRouteProfile] = useState<'driving' | 'walking'>('driving')
|
||||
const [fitKey, setFitKey] = useState<number>(0)
|
||||
const initialFitTripId = useRef<number | null>(null)
|
||||
const [mobileSidebarOpen, setMobileSidebarOpen] = useState<'left' | 'right' | null>(null)
|
||||
@@ -398,7 +402,7 @@ export default function TripPlannerPage(): React.ReactElement | null {
|
||||
})
|
||||
}, [places, mapCategoryFilter, mapPlacesFilter, assignments, expandedDayIds])
|
||||
|
||||
const { route, routeSegments, routeInfo, setRoute, setRouteInfo, updateRouteForDay } = useRouteCalculation({ assignments } as any, selectedDayId)
|
||||
const { route, routeSegments, routeInfo, setRoute, setRouteInfo, updateRouteForDay } = useRouteCalculation({ assignments } as any, selectedDayId, routeShown, routeProfile)
|
||||
|
||||
const handleSelectDay = useCallback((dayId, skipFit) => {
|
||||
const changed = dayId !== selectedDayId
|
||||
@@ -891,6 +895,10 @@ export default function TripPlannerPage(): React.ReactElement | null {
|
||||
onEditPlace={(place, assignmentId) => { setEditingPlace(place); setEditingAssignmentId(assignmentId || null); setShowPlaceForm(true) }}
|
||||
onDeletePlace={(placeId) => handleDeletePlace(placeId)}
|
||||
accommodations={tripAccommodations}
|
||||
routeShown={routeShown}
|
||||
routeProfile={routeProfile}
|
||||
onToggleRoute={() => setRouteShown(v => !v)}
|
||||
onSetRouteProfile={setRouteProfile}
|
||||
onNavigateToFiles={() => handleTabChange('dateien')}
|
||||
onExpandedDaysChange={setExpandedDayIds}
|
||||
pushUndo={pushUndo}
|
||||
@@ -1117,7 +1125,7 @@ export default function TripPlannerPage(): React.ReactElement | null {
|
||||
</div>
|
||||
<div style={{ flex: 1, overflow: 'auto' }}>
|
||||
{mobileSidebarOpen === 'left'
|
||||
? <DayPlanSidebar tripId={tripId} trip={trip} days={days} places={places} categories={categories} assignments={assignments} selectedDayId={selectedDayId} selectedPlaceId={selectedPlaceId} selectedAssignmentId={selectedAssignmentId} onSelectDay={(id) => { handleSelectDay(id); setMobileSidebarOpen(null) }} onPlaceClick={(placeId, assignmentId) => { handlePlaceClick(placeId, assignmentId) }} onReorder={handleReorder} onUpdateDayTitle={handleUpdateDayTitle} onAssignToDay={handleAssignToDay} onRouteCalculated={(r) => { if (r) { setRoute(r.coordinates); setRouteInfo({ distance: r.distanceText, duration: r.durationText }) } }} reservations={reservations} visibleConnectionIds={visibleConnections} onToggleConnection={toggleConnection} onAddReservation={(dayId) => { setEditingReservation(null); tripActions.setSelectedDay(dayId); setShowReservationModal(true); setMobileSidebarOpen(null) }} onAddPlace={() => { setEditingPlace(null); setShowPlaceForm(true); setMobileSidebarOpen(null) }} onDayDetail={(day) => { setShowDayDetail(day); setSelectedPlaceId(null); selectAssignment(null) }} accommodations={tripAccommodations} onNavigateToFiles={() => { setMobileSidebarOpen(null); handleTabChange('dateien') }} onExpandedDaysChange={setExpandedDayIds} pushUndo={pushUndo} canUndo={canUndo} lastActionLabel={lastActionLabel} onUndo={handleUndo} onEditTransport={can('day_edit', trip) ? (reservation) => { setEditingTransport(reservation); setTransportModalDayId(reservation.day_id ?? null); setShowTransportModal(true); setMobileSidebarOpen(null) } : undefined} onEditReservation={can('reservation_edit', trip) ? (r) => { setEditingReservation(r); setShowReservationModal(true); setMobileSidebarOpen(null) } : undefined} initialScrollTop={mobilePlanScrollTopRef.current} onScrollTopChange={(top) => { mobilePlanScrollTopRef.current = top }} />
|
||||
? <DayPlanSidebar tripId={tripId} trip={trip} days={days} places={places} categories={categories} assignments={assignments} selectedDayId={selectedDayId} selectedPlaceId={selectedPlaceId} selectedAssignmentId={selectedAssignmentId} onSelectDay={(id) => { handleSelectDay(id); setMobileSidebarOpen(null) }} onPlaceClick={(placeId, assignmentId) => { handlePlaceClick(placeId, assignmentId) }} onReorder={handleReorder} onUpdateDayTitle={handleUpdateDayTitle} onAssignToDay={handleAssignToDay} onRouteCalculated={(r) => { if (r) { setRoute(r.coordinates); setRouteInfo({ distance: r.distanceText, duration: r.durationText }) } }} reservations={reservations} visibleConnectionIds={visibleConnections} onToggleConnection={toggleConnection} onAddReservation={(dayId) => { setEditingReservation(null); tripActions.setSelectedDay(dayId); setShowReservationModal(true); setMobileSidebarOpen(null) }} onAddTransport={can('day_edit', trip) ? (dayId) => { setTransportModalDayId(dayId); setEditingTransport(null); setShowTransportModal(true); setMobileSidebarOpen(null) } : undefined} onAddPlace={() => { setEditingPlace(null); setShowPlaceForm(true); setMobileSidebarOpen(null) }} onDayDetail={(day) => { setShowDayDetail(day); setSelectedPlaceId(null); selectAssignment(null) }} accommodations={tripAccommodations} routeShown={routeShown} routeProfile={routeProfile} onToggleRoute={() => setRouteShown(v => !v)} onSetRouteProfile={setRouteProfile} onNavigateToFiles={() => { setMobileSidebarOpen(null); handleTabChange('dateien') }} onExpandedDaysChange={setExpandedDayIds} pushUndo={pushUndo} canUndo={canUndo} lastActionLabel={lastActionLabel} onUndo={handleUndo} onEditTransport={can('day_edit', trip) ? (reservation) => { setEditingTransport(reservation); setTransportModalDayId(reservation.day_id ?? null); setShowTransportModal(true); setMobileSidebarOpen(null) } : undefined} onEditReservation={can('reservation_edit', trip) ? (r) => { setEditingReservation(r); setShowReservationModal(true); setMobileSidebarOpen(null) } : undefined} initialScrollTop={mobilePlanScrollTopRef.current} onScrollTopChange={(top) => { mobilePlanScrollTopRef.current = top }} />
|
||||
: <PlacesSidebar tripId={tripId} places={places} categories={categories} assignments={assignments} selectedDayId={selectedDayId} selectedPlaceId={selectedPlaceId} onPlaceClick={(placeId) => { handlePlaceClick(placeId); setMobileSidebarOpen(null) }} onAddPlace={() => { setEditingPlace(null); setShowPlaceForm(true); setMobileSidebarOpen(null) }} onAssignToDay={handleAssignToDay} onEditPlace={(place) => { setEditingPlace(place); setEditingAssignmentId(null); setShowPlaceForm(true); setMobileSidebarOpen(null) }} onDeletePlace={(placeId) => handleDeletePlace(placeId)} onBulkDeletePlaces={(ids) => setDeletePlaceIds(ids)} onBulkDeleteConfirm={(ids) => confirmDeletePlaces(ids)} days={days} isMobile onCategoryFilterChange={setMapCategoryFilter} onPlacesFilterChange={setMapPlacesFilter} pushUndo={pushUndo} initialScrollTop={mobilePlacesScrollTopRef.current} onScrollTopChange={(top) => { mobilePlacesScrollTopRef.current = top }} />
|
||||
}
|
||||
</div>
|
||||
|
||||
Reference in New Issue
Block a user