feat: add multi-day transport reservations with dedicated modal and route segmentation

Introduces a TransportModal for creating/editing flight, train, car, and cruise
reservations that span multiple days. Transport entries now break the map route
into disconnected segments so the polyline reflects actual travel legs.

- Add TransportModal with airport/location pickers, multi-day date range, and all transport types
- Extend DB schema with end_day_id on reservations (migration 110) and backfill from existing dates
- Refactor useRouteCalculation to emit [][][number,number] segments split at transport boundaries
- Update MapView, DayPlanSidebar, ReservationsPanel, TripPlannerPage to wire up transport flow
- Add transport i18n keys across all 15 languages
This commit is contained in:
jubnl
2026-04-18 06:10:33 +02:00
parent 8e04deb0f5
commit 3f61e1ca38
32 changed files with 1188 additions and 501 deletions
@@ -20,7 +20,8 @@ const { calculateSegments } = await import('../../../src/components/Map/RouteCal
function buildMockStore(assignments: Record<string, ReturnType<typeof buildAssignment>[]> = {}): Partial<TripStoreState> {
// Also populate the real Zustand store so updateRouteForDay (which reads from
// useTripStore.getState()) sees the same assignments as the hook's tripStore param.
useTripStore.setState({ assignments } as any);
// Reset reservations and days to empty so transport-split logic doesn't interfere.
useTripStore.setState({ assignments, reservations: [], days: [] } as any);
return { assignments } as Partial<TripStoreState>;
}
@@ -77,9 +78,9 @@ describe('useRouteCalculation', () => {
);
await act(async () => {});
// route is an array of segments; no transport → single segment with all places
expect(result.current.route).toEqual([
[p1.lat, p1.lng],
[p2.lat, p2.lng],
[[p1.lat, p1.lng], [p2.lat, p2.lng]],
]);
});
@@ -139,8 +140,7 @@ describe('useRouteCalculation', () => {
// After sort: a2 (order_index=0) first, then a1 (order_index=1)
expect(result.current.route).toEqual([
[p2.lat, p2.lng],
[p1.lat, p1.lng],
[[p2.lat, p2.lng], [p1.lat, p1.lng]],
]);
});
@@ -289,8 +289,7 @@ describe('useRouteCalculation', () => {
await act(async () => {});
expect(result.current.route).toEqual([
[p1.lat, p1.lng],
[p2.lat, p2.lng],
[[p1.lat, p1.lng], [p2.lat, p2.lng]],
]);
// Now add a third place — update both the local store object and the Zustand store
@@ -305,9 +304,7 @@ describe('useRouteCalculation', () => {
await act(async () => {});
expect(result.current.route).toEqual([
[p1.lat, p1.lng],
[p2.lat, p2.lng],
[p3.lat, p3.lng],
[[p1.lat, p1.lng], [p2.lat, p2.lng], [p3.lat, p3.lng]],
]);
});
});