Files
webapp/app/pages/catalog/offers/index.vue
Ruslan Bakiev 43310f5c28
All checks were successful
Build Docker Image / build (push) Successful in 4m37s
Enable server-side clustering on all catalog pages
- Add clusterNodeType prop to CatalogPage
- Update useClusteredNodes to accept nodeType parameter
- Enable server clustering on /offers and /suppliers pages
- Add hoveredId sync on /offers page
- Remove hover:shadow-lg animation from Card
2026-01-16 17:32:59 +07:00

101 lines
2.8 KiB
Vue

<template>
<CatalogPage
:items="filteredProducts"
:loading="isLoading"
:total-count="products.length"
with-map
use-server-clustering
cluster-node-type="offer"
map-id="offers-products-map"
point-color="#3b82f6"
:hovered-id="hoveredProductId"
@update:hovered-id="hoveredProductId = $event"
>
<template #searchBar="{ displayedCount, totalCount }">
<CatalogSearchBar
v-model:search-query="searchQuery"
:displayed-count="displayedCount"
:total-count="totalCount"
/>
</template>
<template #header>
<!-- Empty -->
<Card v-if="!isLoading && products.length === 0" padding="lg">
<Stack align="center" gap="4">
<IconCircle tone="primary">
<Icon name="lucide:package" size="24" />
</IconCircle>
<Heading :level="2">{{ t('catalogProducts.empty.title') }}</Heading>
<Text tone="muted">{{ t('catalogProducts.empty.subtitle') }}</Text>
</Stack>
</Card>
<!-- Header -->
<div v-else>
<Heading :level="1">{{ t('catalogProducts.header.title') }}</Heading>
<Text tone="muted" size="sm">{{ t('catalogProducts.header.subtitle', { count: products.length }) }}</Text>
</div>
</template>
<template #card="{ item }">
<HubProductCard
:name="item.name || ''"
:price-history="getMockPriceHistory(item.uuid)"
@select="goToProduct(item.uuid)"
/>
</template>
<template #empty>
<Text tone="muted">{{ t('catalogProducts.empty.subtitle') }}</Text>
</template>
</CatalogPage>
</template>
<script setup lang="ts">
definePageMeta({
layout: 'topnav'
})
const localePath = useLocalePath()
const { t } = useI18n()
const { items: products, isLoading, init } = useCatalogProducts()
// Hovered product for map sync
const hoveredProductId = ref<string>()
// 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)
)
})
// Navigate to product detail
const goToProduct = (productId: string) => {
navigateTo(localePath(`/catalog/offers/${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)
})
}
// Initialize
await init()
useHead(() => ({
title: t('catalogProducts.header.title')
}))
</script>