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:
Maurice
2026-05-25 22:27:49 +02:00
committed by GitHub
parent c130ed41be
commit e050814c42
13 changed files with 441 additions and 266 deletions
@@ -268,14 +268,7 @@ describe('DayPlanSidebar', () => {
const user = userEvent.setup()
const day = buildDay({ id: 10, date: '2025-06-01', title: 'Original Title' })
render(<DayPlanSidebar {...makeDefaultProps({ days: [day] })} />)
// Find the pencil/edit button next to the title
const editButtons = screen.getAllByRole('button')
const editBtn = editButtons.find(btn => btn.querySelector('svg') && btn.closest('[style]')?.textContent?.includes('Original Title'))
// Click the edit (pencil) button — it's the small one near the title
// The pencil button is inside the title area with opacity 0.35
const titleEl = screen.getByText('Original Title')
const pencilBtn = titleEl.parentElement?.querySelector('button')
if (pencilBtn) await user.click(pencilBtn)
await user.click(screen.getByLabelText('Edit'))
await waitFor(() => {
expect(screen.getByDisplayValue('Original Title')).toBeInTheDocument()
})
@@ -287,9 +280,7 @@ describe('DayPlanSidebar', () => {
const onUpdateDayTitle = vi.fn()
render(<DayPlanSidebar {...makeDefaultProps({ days: [day], onUpdateDayTitle })} />)
// Enter edit mode
const titleEl = screen.getByText('Original Title')
const pencilBtn = titleEl.parentElement?.querySelector('button')
if (pencilBtn) await user.click(pencilBtn)
await user.click(screen.getByLabelText('Edit'))
const input = await screen.findByDisplayValue('Original Title')
await user.clear(input)
await user.type(input, 'New Title')
@@ -301,9 +292,7 @@ describe('DayPlanSidebar', () => {
const user = userEvent.setup()
const day = buildDay({ id: 10, date: '2025-06-01', title: 'Original Title' })
render(<DayPlanSidebar {...makeDefaultProps({ days: [day] })} />)
const titleEl = screen.getByText('Original Title')
const pencilBtn = titleEl.parentElement?.querySelector('button')
if (pencilBtn) await user.click(pencilBtn)
await user.click(screen.getByLabelText('Edit'))
const input = await screen.findByDisplayValue('Original Title')
await user.keyboard('{Escape}')
expect(screen.queryByDisplayValue('Original Title')).not.toBeInTheDocument()
@@ -625,9 +614,7 @@ describe('DayPlanSidebar', () => {
const onUpdateDayTitle = vi.fn()
const day = buildDay({ id: 10, date: '2025-06-01', title: 'Old Title' })
render(<DayPlanSidebar {...makeDefaultProps({ days: [day], onUpdateDayTitle })} />)
const titleEl = screen.getByText('Old Title')
const pencilBtn = titleEl.parentElement?.querySelector('button')
if (pencilBtn) await user.click(pencilBtn)
await user.click(screen.getByLabelText('Edit'))
const input = await screen.findByDisplayValue('Old Title')
await user.clear(input)
await user.type(input, 'New Title')