diff --git a/app/components/catalog/CatalogMap.vue b/app/components/catalog/CatalogMap.vue index 714001c..8699df4 100644 --- a/app/components/catalog/CatalogMap.vue +++ b/app/components/catalog/CatalogMap.vue @@ -208,12 +208,7 @@ const initClientClusteringLayers = (map: MapboxMapType) => { filter: ['!', ['has', 'point_count']], paint: { 'circle-radius': 12, - 'circle-color': [ - 'case', - ['==', ['get', 'uuid'], props.hoveredItemId || ''], - '#facc15', // yellow when hovered - props.pointColor - ], + 'circle-color': props.pointColor, 'circle-stroke-width': 3, 'circle-stroke-color': '#ffffff' } @@ -260,6 +255,36 @@ const initClientClusteringLayers = (map: MapboxMapType) => { map.on('mouseenter', 'unclustered-point', () => { map.getCanvas().style.cursor = 'pointer' }) map.on('mouseleave', 'unclustered-point', () => { map.getCanvas().style.cursor = '' }) + // Hovered point layer (on top of everything) - "target" effect with border + map.addSource(hoveredSourceId.value, { + type: 'geojson', + data: hoveredPointGeoJson.value + }) + // Outer ring (white) + map.addLayer({ + id: 'hovered-point-ring', + type: 'circle', + source: hoveredSourceId.value, + paint: { + 'circle-radius': 20, + 'circle-color': 'transparent', + 'circle-stroke-width': 3, + 'circle-stroke-color': '#ffffff' + } + }) + // Inner point (same as entity color) + map.addLayer({ + id: 'hovered-point-layer', + type: 'circle', + source: hoveredSourceId.value, + paint: { + 'circle-radius': 14, + 'circle-color': props.pointColor, + 'circle-stroke-width': 3, + 'circle-stroke-color': '#ffffff' + } + }) + // Auto-fit bounds to all items if (!didFitBounds.value && props.items.length > 0) { const bounds = new LngLatBounds() @@ -312,12 +337,7 @@ const initServerClusteringLayers = (map: MapboxMapType) => { filter: ['==', ['get', 'count'], 1], paint: { 'circle-radius': 12, - 'circle-color': [ - 'case', - ['==', ['get', 'id'], props.hoveredItemId || ''], - '#facc15', // yellow when hovered - props.pointColor - ], + 'circle-color': props.pointColor, 'circle-stroke-width': 3, 'circle-stroke-color': '#ffffff' } @@ -365,18 +385,31 @@ const initServerClusteringLayers = (map: MapboxMapType) => { map.on('mouseenter', 'server-points', () => { map.getCanvas().style.cursor = 'pointer' }) map.on('mouseleave', 'server-points', () => { map.getCanvas().style.cursor = '' }) - // Hovered point layer (on top of everything) + // Hovered point layer (on top of everything) - "target" effect with border map.addSource(hoveredSourceId.value, { type: 'geojson', data: hoveredPointGeoJson.value }) + // Outer ring (white) + map.addLayer({ + id: 'hovered-point-ring', + type: 'circle', + source: hoveredSourceId.value, + paint: { + 'circle-radius': 20, + 'circle-color': 'transparent', + 'circle-stroke-width': 3, + 'circle-stroke-color': '#ffffff' + } + }) + // Inner point (same as entity color) map.addLayer({ id: 'hovered-point-layer', type: 'circle', source: hoveredSourceId.value, paint: { 'circle-radius': 14, - 'circle-color': '#3b82f6', + 'circle-color': props.pointColor, 'circle-stroke-width': 3, 'circle-stroke-color': '#ffffff' } diff --git a/app/components/navigation/MainNavigation.vue b/app/components/navigation/MainNavigation.vue index 369ea67..987aa01 100644 --- a/app/components/navigation/MainNavigation.vue +++ b/app/components/navigation/MainNavigation.vue @@ -27,7 +27,8 @@
@@ -204,6 +205,7 @@ diff --git a/app/composables/useCatalogSearch.ts b/app/composables/useCatalogSearch.ts index ff9f361..eedc62c 100644 --- a/app/composables/useCatalogSearch.ts +++ b/app/composables/useCatalogSearch.ts @@ -12,7 +12,7 @@ export type DisplayMode = | 'grid-offers' export interface SearchFilter { - type: 'product' | 'supplier' | 'hub' | 'location' | 'quantity' + type: 'product' | 'supplier' | 'hub' | 'quantity' id: string label: string } @@ -22,16 +22,22 @@ export interface SearchState { product: { id: string; name: string } | null supplier: { id: string; name: string } | null hub: { id: string; name: string } | null - location: { id: string; name: string } | null quantity: string | null } +// Color scheme for entity types +export const entityColors = { + product: '#f97316', // orange + supplier: '#3b82f6', // blue + hub: '#22c55e', // green + offer: '#f97316' // orange (same as product context) +} as const + // Filter labels cache (to show names instead of UUIDs) const filterLabels = ref>>({ product: {}, supplier: {}, - hub: {}, - location: {} + hub: {} }) export function useCatalogSearch() { @@ -51,7 +57,6 @@ export function useCatalogSearch() { const productId = computed(() => route.query.product as string | undefined) const supplierId = computed(() => route.query.supplier as string | undefined) const hubId = computed(() => route.query.hub as string | undefined) - const locationId = computed(() => route.query.location as string | undefined) const quantity = computed(() => route.query.qty as string | undefined) // Get label for a filter (from cache or fallback to ID) @@ -96,14 +101,6 @@ export function useCatalogSearch() { icon: 'lucide:map-pin' }) } - if (locationId.value) { - tokens.push({ - type: 'location', - id: locationId.value, - label: getLabel('location', locationId.value) || t('catalog.filters.location'), - icon: 'lucide:navigation' - }) - } if (quantity.value) { tokens.push({ type: 'quantity', @@ -129,10 +126,8 @@ export function useCatalogSearch() { if (!hubId.value && selectMode.value !== 'hub') { chips.push({ type: 'hub', label: t('catalog.filters.hub') }) } - if (!locationId.value) { - chips.push({ type: 'location', label: t('catalog.filters.location') }) - } - if (!quantity.value) { + // Quantity only available after product is selected + if (productId.value && !quantity.value) { chips.push({ type: 'quantity', label: t('catalog.filters.quantity') }) } @@ -226,10 +221,12 @@ export function useCatalogSearch() { productId, supplierId, hubId, - locationId, quantity, searchQuery, + // Colors + entityColors, + // Computed activeTokens, availableChips, diff --git a/app/pages/catalog/index.vue b/app/pages/catalog/index.vue index 2e75691..83f8b43 100644 --- a/app/pages/catalog/index.vue +++ b/app/pages/catalog/index.vue @@ -143,11 +143,14 @@ const clusterNodeType = computed(() => { return 'logistics' }) +// Import entity colors +const { entityColors } = useCatalogSearch() + const mapPointColor = computed(() => { - if (cardType.value === 'supplier') return '#3b82f6' - if (cardType.value === 'hub') return '#10b981' - if (cardType.value === 'offer') return '#22c55e' - return '#f59e0b' + if (cardType.value === 'supplier') return entityColors.supplier // blue + if (cardType.value === 'hub') return entityColors.hub // green + if (cardType.value === 'offer') return entityColors.offer // orange + return entityColors.product // orange }) const headerText = computed(() => { diff --git a/i18n/locales/en/catalog.json b/i18n/locales/en/catalog.json index 1645b19..c4e1eee 100644 --- a/i18n/locales/en/catalog.json +++ b/i18n/locales/en/catalog.json @@ -4,7 +4,6 @@ "product": "Product", "supplier": "Supplier", "hub": "Hub", - "location": "Location", "quantity": "Quantity" }, "search": { diff --git a/i18n/locales/ru/catalog.json b/i18n/locales/ru/catalog.json index 7280e7f..70f60e1 100644 --- a/i18n/locales/ru/catalog.json +++ b/i18n/locales/ru/catalog.json @@ -4,7 +4,6 @@ "product": "Товар", "supplier": "Поставщик", "hub": "Хаб", - "location": "Локация", "quantity": "Количество" }, "search": {