From eb612f1cdfbde848b381b4942ea06bbe4391f732 Mon Sep 17 00:00:00 2001 From: alex Date: Sat, 7 Mar 2026 15:28:33 +0000 Subject: [PATCH] fix: keep itinerary connectors visible when route data is unavailable --- .../CollectionItineraryPlanner.svelte | 117 ++++++++++++------ 1 file changed, 81 insertions(+), 36 deletions(-) diff --git a/frontend/src/lib/components/collections/CollectionItineraryPlanner.svelte b/frontend/src/lib/components/collections/CollectionItineraryPlanner.svelte index 0dba9f7e..319452ab 100644 --- a/frontend/src/lib/components/collections/CollectionItineraryPlanner.svelte +++ b/frontend/src/lib/components/collections/CollectionItineraryPlanner.svelte @@ -31,6 +31,7 @@ import ItineraryLinkModal from '$lib/components/collections/ItineraryLinkModal.svelte'; import ItineraryDayPickModal from '$lib/components/collections/ItineraryDayPickModal.svelte'; import Car from '~icons/mdi/car'; + import Walk from '~icons/mdi/walk'; import LocationMarker from '~icons/mdi/map-marker'; import { t } from 'svelte-i18n'; import { addToast } from '$lib/toasts'; @@ -430,30 +431,42 @@ return (degrees * Math.PI) / 180; } - function haversineDistanceKm(from: Location, to: Location): number | null { - if ( - from.latitude === null || - from.longitude === null || - to.latitude === null || - to.longitude === null - ) { - return null; + function normalizeCoordinate(value: number | string | null | undefined): number | null { + if (typeof value === 'number') { + return Number.isFinite(value) ? value : null; } + if (typeof value === 'string') { + const trimmed = value.trim(); + if (!trimmed) return null; + + const parsed = Number(trimmed); + return Number.isFinite(parsed) ? parsed : null; + } + + return null; + } + + function haversineDistanceKm(from: Location, to: Location): number | null { + const fromLatitude = normalizeCoordinate(from.latitude); + const fromLongitude = normalizeCoordinate(from.longitude); + const toLatitude = normalizeCoordinate(to.latitude); + const toLongitude = normalizeCoordinate(to.longitude); + if ( - !Number.isFinite(from.latitude) || - !Number.isFinite(from.longitude) || - !Number.isFinite(to.latitude) || - !Number.isFinite(to.longitude) + fromLatitude === null || + fromLongitude === null || + toLatitude === null || + toLongitude === null ) { return null; } const earthRadiusKm = 6371; - const latDelta = toRadians(to.latitude - from.latitude); - const lonDelta = toRadians(to.longitude - from.longitude); - const fromLat = toRadians(from.latitude); - const toLat = toRadians(to.latitude); + const latDelta = toRadians(toLatitude - fromLatitude); + const lonDelta = toRadians(toLongitude - fromLongitude); + const fromLat = toRadians(fromLatitude); + const toLat = toRadians(toLatitude); const a = Math.sin(latDelta / 2) * Math.sin(latDelta / 2) + @@ -478,6 +491,7 @@ distanceLabel: string; durationLabel: string; mode: 'walking' | 'driving'; + unavailable?: boolean; }; type ConnectorPair = { @@ -526,20 +540,16 @@ const nextLocation = nextItem.resolvedObject as Location | null; if (!currentLocation || !nextLocation) return null; - const fromLatitude = currentLocation.latitude; - const fromLongitude = currentLocation.longitude; - const toLatitude = nextLocation.latitude; - const toLongitude = nextLocation.longitude; + const fromLatitude = normalizeCoordinate(currentLocation.latitude); + const fromLongitude = normalizeCoordinate(currentLocation.longitude); + const toLatitude = normalizeCoordinate(nextLocation.latitude); + const toLongitude = normalizeCoordinate(nextLocation.longitude); if ( fromLatitude === null || fromLongitude === null || toLatitude === null || - toLongitude === null || - !Number.isFinite(fromLatitude) || - !Number.isFinite(fromLongitude) || - !Number.isFinite(toLatitude) || - !Number.isFinite(toLongitude) + toLongitude === null ) { return null; } @@ -707,12 +717,19 @@ const nextType = nextItem.item?.type || ''; if (currentType !== 'location' || nextType !== 'location') return null; + const unavailableConnector: LocationConnector = { + distanceLabel: '', + durationLabel: getI18nText('itinerary.route_unavailable', 'Route unavailable'), + mode: 'walking', + unavailable: true + }; + const currentLocation = currentItem.resolvedObject as Location | null; const nextLocation = nextItem.resolvedObject as Location | null; - if (!currentLocation || !nextLocation) return null; + if (!currentLocation || !nextLocation) return unavailableConnector; const distanceKm = haversineDistanceKm(currentLocation, nextLocation); - if (distanceKm === null) return null; + if (distanceKm === null) return unavailableConnector; const walkingMinutes = (distanceKm / WALKING_SPEED_KMH) * 60; const drivingMinutes = (distanceKm / DRIVING_SPEED_KMH) * 60; @@ -737,6 +754,11 @@ return getFallbackLocationConnector(currentItem, nextItem); } + function getI18nText(key: string, fallback: string): string { + const translated = $t(key); + return translated && translated !== key ? translated : fallback; + } + function editTransportationInline(transportation: Transportation) { handleEditTransportation({ detail: transportation } as CustomEvent); } @@ -2607,16 +2629,39 @@ {#if locationConnector}
-
- {locationConnector.distanceLabel} - - - {locationConnector.mode === 'driving' ? '🚗' : '🚶'} - {locationConnector.durationLabel} - -
+ {#if locationConnector.unavailable} +
+ + + {locationConnector.durationLabel} + + + + + {getI18nText('itinerary.directions', 'Directions')} + +
+ {:else} +
+ + {#if locationConnector.mode === 'driving'} + + {:else} + + {/if} + {locationConnector.durationLabel} + + + {locationConnector.distanceLabel} + + + + {getI18nText('itinerary.directions', 'Directions')} + +
+ {/if}
{/if}