From f0c687c3ff927c399c105ed229936c3c2bed1024 Mon Sep 17 00:00:00 2001 From: Ruslan Bakiev <572431+veikab@users.noreply.github.com> Date: Fri, 6 Feb 2026 15:21:24 +0700 Subject: [PATCH] Improve selection panel and hub card compass --- app/components/catalog/HubCard.vue | 51 ++++++++++-- app/components/catalog/InfoPanel.vue | 9 +++ app/components/catalog/SelectionPanel.vue | 95 ++++++++++++++++------- app/pages/catalog/index.vue | 1 + 4 files changed, 121 insertions(+), 35 deletions(-) diff --git a/app/components/catalog/HubCard.vue b/app/components/catalog/HubCard.vue index 19c7ed5..d532b5f 100644 --- a/app/components/catalog/HubCard.vue +++ b/app/components/catalog/HubCard.vue @@ -16,16 +16,29 @@ ]" >
- - {{ hub.name }} - + +
+ {{ hub.name }} +
+ {{ distanceLabel }} +
+
+ +
+ {{ Math.round(bearing) }}° +
+
+
+
{{ countryFlag }} {{ hub.country || t('catalogMap.labels.country_unknown') }} - - {{ distanceLabel }} -
@@ -47,6 +60,8 @@ interface Hub { name?: string | null country?: string | null countryCode?: string | null + latitude?: number | null + longitude?: number | null distance?: string distanceKm?: number | null transportTypes?: (string | null)[] | null @@ -54,6 +69,7 @@ interface Hub { const props = defineProps<{ hub: Hub + origin?: { latitude: number; longitude: number } | null selectable?: boolean isSelected?: boolean linkTo?: string @@ -88,4 +104,27 @@ const distanceLabel = computed(() => { if (props.hub.distanceKm != null) return `${Math.round(props.hub.distanceKm)} km` return '' }) + +const toRadians = (deg: number) => (deg * Math.PI) / 180 +const toDegrees = (rad: number) => (rad * 180) / Math.PI + +const bearing = computed(() => { + const origin = props.origin + const lat2 = props.hub.latitude + const lon2 = props.hub.longitude + if (!origin || lat2 == null || lon2 == null) return null + const lat1 = origin.latitude + const lon1 = origin.longitude + if (lat1 == null || lon1 == null) return null + + const φ1 = toRadians(lat1) + const φ2 = toRadians(lat2) + const Δλ = toRadians(lon2 - lon1) + + const y = Math.sin(Δλ) * Math.cos(φ2) + const x = Math.cos(φ1) * Math.sin(φ2) - Math.sin(φ1) * Math.cos(φ2) * Math.cos(Δλ) + const θ = Math.atan2(y, x) + const deg = (toDegrees(θ) + 360) % 360 + return deg +}) diff --git a/app/components/catalog/InfoPanel.vue b/app/components/catalog/InfoPanel.vue index ce47937..bd17d96 100644 --- a/app/components/catalog/InfoPanel.vue +++ b/app/components/catalog/InfoPanel.vue @@ -206,6 +206,7 @@ v-for="(hub, index) in railHubs" :key="hub.uuid ?? index" :hub="hub" + :origin="originCoords" selectable @select="onHubSelect(hub)" /> @@ -226,6 +227,7 @@ v-for="(hub, index) in seaHubs" :key="hub.uuid ?? index" :hub="hub" + :origin="originCoords" selectable @select="onHubSelect(hub)" /> @@ -306,6 +308,13 @@ const entityLocation = computed(() => { return parts.length > 0 ? parts.join(', ') : null }) +const originCoords = computed(() => { + const lat = props.entity?.locationLatitude ?? props.entity?.latitude + const lon = props.entity?.locationLongitude ?? props.entity?.longitude + if (lat == null || lon == null) return null + return { latitude: Number(lat), longitude: Number(lon) } +}) + // Products section title based on entity type const productsSectionTitle = computed(() => { return props.entityType === 'hub' diff --git a/app/components/catalog/SelectionPanel.vue b/app/components/catalog/SelectionPanel.vue index 0ee75cf..a9cdec0 100644 --- a/app/components/catalog/SelectionPanel.vue +++ b/app/components/catalog/SelectionPanel.vue @@ -8,12 +8,21 @@
- +
+ + {{ item.name }} + + +
@@ -22,7 +31,7 @@ -
+

{{ $t('catalog.empty.noResults') }}

@@ -31,11 +40,19 @@