All checks were successful
Build Docker Image / build (push) Successful in 3m46s
When hovering over a card on the left, the corresponding point on the map now shows a blue highlight.
159 lines
4.5 KiB
Vue
159 lines
4.5 KiB
Vue
<template>
|
||
<CatalogPage
|
||
:items="filteredProducts"
|
||
:map-items="mapItems"
|
||
:loading="isLoading"
|
||
:total-count="products.length"
|
||
with-map
|
||
map-id="hub-products-map"
|
||
point-color="#10b981"
|
||
:hovered-id="hoveredId"
|
||
@update:hovered-id="hoveredId = $event"
|
||
>
|
||
<template #searchBar="{ displayedCount, totalCount }">
|
||
<CatalogSearchBar
|
||
v-model:search-query="searchQuery"
|
||
:active-filters="navigationFilters"
|
||
:displayed-count="displayedCount"
|
||
:total-count="totalCount"
|
||
@remove-filter="handleRemoveFilter"
|
||
/>
|
||
</template>
|
||
|
||
<template #header>
|
||
<Text v-if="!isLoading && !hub" tone="muted">Хаб не найден</Text>
|
||
<Text v-else-if="!isLoading" tone="muted">Выберите товар</Text>
|
||
</template>
|
||
|
||
<template #card="{ item }">
|
||
<HubProductCard
|
||
:name="item.name"
|
||
:price-history="getMockPriceHistory(item.uuid)"
|
||
@select="goToProduct(item.uuid)"
|
||
/>
|
||
</template>
|
||
|
||
<template #empty>
|
||
<Stack align="center" gap="2">
|
||
<Icon name="lucide:package-x" size="32" class="text-base-content/40" />
|
||
<Text tone="muted">{{ t('catalogHub.products.empty') }}</Text>
|
||
</Stack>
|
||
</template>
|
||
</CatalogPage>
|
||
</template>
|
||
|
||
<script setup lang="ts">
|
||
import { GetNodeConnectionsDocument, GetProductsNearHubDocument } from '~/composables/graphql/public/geo-generated'
|
||
|
||
definePageMeta({
|
||
layout: 'topnav'
|
||
})
|
||
|
||
const route = useRoute()
|
||
const localePath = useLocalePath()
|
||
const { t } = useI18n()
|
||
|
||
const isLoading = ref(true)
|
||
const hoveredId = ref<string>()
|
||
const hub = ref<any>(null)
|
||
const products = ref<Array<{ uuid: string; name: string }>>([])
|
||
|
||
const hubId = computed(() => route.params.id as string)
|
||
|
||
// Navigation filters for search bar badges
|
||
const navigationFilters = computed(() => {
|
||
const filters: Array<{ id: string; label: string; key: string }> = []
|
||
|
||
if (hub.value?.name) {
|
||
filters.push({
|
||
id: 'hub',
|
||
key: 'Хаб',
|
||
label: hub.value.name
|
||
})
|
||
}
|
||
|
||
return filters
|
||
})
|
||
|
||
// Handle removing navigation filter (navigate back)
|
||
const handleRemoveFilter = (filterId: string) => {
|
||
if (filterId === 'hub') {
|
||
navigateTo(localePath('/catalog/hubs'))
|
||
}
|
||
}
|
||
|
||
// Search
|
||
const searchQuery = ref('')
|
||
|
||
const filteredProducts = computed(() => {
|
||
if (!searchQuery.value.trim()) return products.value
|
||
const q = searchQuery.value.toLowerCase()
|
||
return products.value.filter(item =>
|
||
item.name?.toLowerCase().includes(q)
|
||
)
|
||
})
|
||
|
||
// Map items - show the hub itself
|
||
const mapItems = computed(() => {
|
||
if (!hub.value?.latitude || !hub.value?.longitude) return []
|
||
return [{
|
||
uuid: hub.value.uuid || hubId.value,
|
||
name: hub.value.name || '',
|
||
latitude: Number(hub.value.latitude),
|
||
longitude: Number(hub.value.longitude),
|
||
country: hub.value.country
|
||
}]
|
||
})
|
||
|
||
// Navigate to product page
|
||
const goToProduct = (productId: string) => {
|
||
navigateTo(localePath(`/catalog/hubs/${hubId.value}/${productId}`))
|
||
}
|
||
|
||
// Mock price history generator (seeded by uuid for consistent results)
|
||
const getMockPriceHistory = (uuid: string): number[] => {
|
||
const seed = uuid.split('').reduce((acc, char) => acc + char.charCodeAt(0), 0)
|
||
const basePrice = 100 + (seed % 200)
|
||
return Array.from({ length: 7 }, (_, i) => {
|
||
const variation = Math.sin(seed + i * 0.5) * 20 + Math.cos(seed * 0.3 + i) * 10
|
||
return Math.round(basePrice + variation)
|
||
})
|
||
}
|
||
|
||
// Initial load
|
||
try {
|
||
const [{ data: connectionsData }, { data: productsData }] = await Promise.all([
|
||
useServerQuery('hub-connections', GetNodeConnectionsDocument, { uuid: hubId.value }, 'public', 'geo'),
|
||
useServerQuery('products-near-hub', GetProductsNearHubDocument, { hubUuid: hubId.value, radiusKm: 500 }, 'public', 'geo')
|
||
])
|
||
|
||
hub.value = connectionsData.value?.nodeConnections?.hub || null
|
||
|
||
// Get products near this hub (from geo)
|
||
products.value = (productsData.value?.productsNearHub || [])
|
||
.filter((p): p is { uuid: string; name?: string | null } => p !== null && !!p.uuid)
|
||
.map(p => ({ uuid: p.uuid!, name: p.name || '' }))
|
||
} catch (error) {
|
||
console.error('Error loading hub:', error)
|
||
} finally {
|
||
isLoading.value = false
|
||
}
|
||
|
||
// SEO
|
||
useHead(() => ({
|
||
title: hub.value?.name
|
||
? t('catalogHub.meta.title_with_name', { name: hub.value.name })
|
||
: t('catalogHub.meta.title'),
|
||
meta: [
|
||
{
|
||
name: 'description',
|
||
content: t('catalogHub.meta.description', {
|
||
name: hub.value?.name || '',
|
||
country: hub.value?.country || '',
|
||
products: products.value.length
|
||
})
|
||
}
|
||
]
|
||
}))
|
||
</script>
|