Files
webapp/app/pages/catalog/hubs/[id]/index.vue
Ruslan Bakiev 181dc4ea6b
All checks were successful
Build Docker Image / build (push) Successful in 4m57s
feat: use filtered geo queries for catalog pages
- /hubs/[id]: use findProductsForHub to show only deliverable products
- /offers/[productId]: use findHubsForProduct to show only reachable hubs
- /suppliers/[id]/[productId]: use findSupplierProductHubs for supplier-specific hubs
- Add new GraphQL operations for geo queries
2026-01-16 01:42:18 +07:00

131 lines
4.4 KiB
Vue

<template>
<Stack gap="0">
<!-- Loading -->
<Section v-if="isLoading" variant="plain" paddingY="lg">
<Stack align="center" justify="center" gap="4">
<Spinner />
<Text tone="muted">{{ t('catalogHub.states.loading') }}</Text>
</Stack>
</Section>
<!-- Error / Not Found -->
<Section v-else-if="!hub" variant="plain" paddingY="lg">
<Card padding="lg">
<Stack align="center" gap="4">
<IconCircle tone="primary">
<Icon name="lucide:map-pin" size="24" />
</IconCircle>
<Heading :level="2">{{ t('catalogHub.not_found.title') }}</Heading>
<Text tone="muted">{{ t('catalogHub.not_found.subtitle') }}</Text>
<Button @click="navigateTo(localePath('/catalog'))">
{{ t('catalogHub.actions.back_to_catalog') }}
</Button>
</Stack>
</Card>
</Section>
<!-- Content -->
<Section v-else variant="plain" paddingY="lg">
<Stack gap="4">
<!-- Breadcrumbs -->
<CatalogBreadcrumbs :hub-id="hubId" :hub-name="hub.name" />
<!-- Header -->
<div>
<Heading :level="1">{{ hub.name }}</Heading>
<Text tone="muted" size="sm">{{ hub.country }}</Text>
</div>
<!-- Products grid -->
<div class="grid grid-cols-2 sm:grid-cols-3 lg:grid-cols-4 gap-4">
<HubProductCard
v-for="product in products"
:key="product.uuid"
:name="product.name"
:price-history="getMockPriceHistory(product.uuid)"
@select="goToProduct(product.uuid)"
/>
</div>
<!-- Empty state -->
<Stack v-if="products.length === 0" 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>
</Stack>
</Section>
</Stack>
</template>
<script setup lang="ts">
import { GetNodeConnectionsDocument, FindProductsForHubDocument } from '~/composables/graphql/public/geo-generated'
import { GetAvailableProductsDocument } from '~/composables/graphql/public/exchange-generated'
definePageMeta({
layout: 'topnav'
})
const route = useRoute()
const localePath = useLocalePath()
const { t } = useI18n()
const isLoading = ref(true)
const hub = ref<any>(null)
const products = ref<Array<{ uuid: string; name: string }>>([])
const hubId = computed(() => route.params.id as string)
// 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: productUuidsData }, { data: allProductsData }] = await Promise.all([
useServerQuery('hub-connections', GetNodeConnectionsDocument, { uuid: hubId.value }, 'public', 'geo'),
useServerQuery('hub-product-uuids', FindProductsForHubDocument, { hubUuid: hubId.value }, 'public', 'geo'),
useServerQuery('available-products', GetAvailableProductsDocument, {}, 'public', 'exchange')
])
hub.value = connectionsData.value?.nodeConnections?.hub || null
// Filter products by UUIDs that can be delivered to this hub
const relevantUuids = new Set(productUuidsData.value?.findProductsForHub?.filter(Boolean) || [])
products.value = (allProductsData.value?.getAvailableProducts || [])
.filter((p): p is { uuid: string; name: string } => p !== null && !!p.uuid && !!p.name && relevantUuids.has(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>