Improve catalog UX: remove category, add offers count, dynamic layout
All checks were successful
Build Docker Image / build (push) Successful in 3m37s

- ProductCard: remove category field, add offersCount display
- CatalogPage: add fullWidthMap prop for map-only view
- catalog/index: pass fullWidthMap based on selectMode
- i18n: add offers pluralization
This commit is contained in:
Ruslan Bakiev
2026-01-22 16:59:33 +07:00
parent 6da5bf10c9
commit 39f8364edb
5 changed files with 41 additions and 10 deletions

View File

@@ -14,10 +14,10 @@
]" ]"
> >
<Stack gap="2"> <Stack gap="2">
<Stack gap="1">
<Text size="base" weight="semibold">{{ product.name }}</Text> <Text size="base" weight="semibold">{{ product.name }}</Text>
<Text tone="muted">{{ product.categoryName || t('catalogProduct.labels.category_unknown') }}</Text> <Text v-if="product.offersCount" tone="muted" size="sm">
</Stack> {{ product.offersCount }} {{ t('catalog.offers', product.offersCount) }}
</Text>
<Text v-if="product.description && !compact" tone="muted" size="sm">{{ product.description }}</Text> <Text v-if="product.description && !compact" tone="muted" size="sm">{{ product.description }}</Text>
</Stack> </Stack>
</Card> </Card>
@@ -30,8 +30,8 @@ import { NuxtLink } from '#components'
interface Product { interface Product {
uuid?: string | null uuid?: string | null
name?: string | null name?: string | null
categoryName?: string | null
description?: string | null description?: string | null
offersCount?: number | null
} }
const props = defineProps<{ const props = defineProps<{

View File

@@ -30,10 +30,32 @@
</div> </div>
</div> </div>
<!-- With Map: Split Layout --> <!-- With Map: Split Layout or Full Width Map -->
<template v-if="withMap"> <template v-if="withMap">
<!-- Desktop: side-by-side --> <!-- Full Width Map Mode (after selection) -->
<div class="hidden lg:flex flex-1 gap-4 min-h-0 py-4"> <div v-if="fullWidthMap" class="hidden lg:flex flex-1 min-h-0 py-4">
<div class="w-full">
<div class="sticky rounded-xl overflow-hidden shadow-lg" :style="mapStyle">
<ClientOnly>
<CatalogMap
ref="mapRef"
:map-id="mapId"
:items="useServerClustering ? [] : itemsWithCoords"
:clustered-points="useServerClustering ? clusteredNodes : []"
:use-server-clustering="useServerClustering"
:point-color="pointColor"
:hovered-item-id="hoveredId"
:hovered-item="hoveredItem"
@select-item="onMapSelect"
@bounds-change="onBoundsChange"
/>
</ClientOnly>
</div>
</div>
</div>
<!-- Desktop: side-by-side (during selection) -->
<div v-else class="hidden lg:flex flex-1 gap-4 min-h-0 py-4">
<!-- Left: List (scrollable) --> <!-- Left: List (scrollable) -->
<div class="w-2/5 overflow-y-auto pr-2"> <div class="w-2/5 overflow-y-auto pr-2">
<Stack gap="4"> <Stack gap="4">
@@ -209,6 +231,7 @@ const props = withDefaults(defineProps<{
mapItems?: MapItem[] // Optional separate items for map (if different from list items) mapItems?: MapItem[] // Optional separate items for map (if different from list items)
loading?: boolean loading?: boolean
withMap?: boolean withMap?: boolean
fullWidthMap?: boolean // Map takes full width (no grid), used after filter selection
useServerClustering?: boolean // Use server-side h3 clustering for ALL points useServerClustering?: boolean // Use server-side h3 clustering for ALL points
clusterNodeType?: string // Node type for clustering: 'logistics' | 'offer' | 'supplier' clusterNodeType?: string // Node type for clustering: 'logistics' | 'offer' | 'supplier'
mapId?: string mapId?: string
@@ -221,6 +244,7 @@ const props = withDefaults(defineProps<{
}>(), { }>(), {
loading: false, loading: false,
withMap: true, withMap: true,
fullWidthMap: false,
useServerClustering: false, useServerClustering: false,
clusterNodeType: 'logistics', clusterNodeType: 'logistics',
mapId: 'catalog-map', mapId: 'catalog-map',

View File

@@ -5,6 +5,7 @@
:total-count="currentItems.length" :total-count="currentItems.length"
:grid-columns="3" :grid-columns="3"
:with-map="showMap" :with-map="showMap"
:full-width-map="fullWidthMap"
:use-server-clustering="useServerClustering" :use-server-clustering="useServerClustering"
:cluster-node-type="clusterNodeType" :cluster-node-type="clusterNodeType"
map-id="unified-catalog-map" map-id="unified-catalog-map"
@@ -126,6 +127,10 @@ const cardType = computed(() => {
const showMap = computed(() => displayMode.value !== 'hero') const showMap = computed(() => displayMode.value !== 'hero')
// Full width map when not in select mode (after filter is selected)
// selectMode is active = grid + map, after selection = only map
const fullWidthMap = computed(() => !selectMode.value && displayMode.value !== 'hero')
// Use server clustering for grids that need it // Use server clustering for grids that need it
const useServerClustering = computed(() => { const useServerClustering = computed(() => {
// Products grid - show offers clusters // Products grid - show offers clusters

View File

@@ -32,6 +32,7 @@
"noHubs": "No hubs found", "noHubs": "No hubs found",
"noOffers": "No offers found", "noOffers": "No offers found",
"noResults": "No results found" "noResults": "No results found"
} },
"offers": "offer | offers"
} }
} }

View File

@@ -32,6 +32,7 @@
"noHubs": "Хабы не найдены", "noHubs": "Хабы не найдены",
"noOffers": "Предложения не найдены", "noOffers": "Предложения не найдены",
"noResults": "Ничего не найдено" "noResults": "Ничего не найдено"
} },
"offers": "предложение | предложения | предложений"
} }
} }