Files
webapp/app/pages/catalog/hubs/index.vue
Ruslan Bakiev d8befc8b9f
All checks were successful
Build Docker Image / build (push) Successful in 3m46s
Add hoveredId to all catalog pages for map point highlighting
When hovering over a card on the left, the corresponding point
on the map now shows a blue highlight.
2026-01-19 13:01:57 +07:00

184 lines
5.1 KiB
Vue
Raw Blame History

This file contains ambiguous Unicode characters

This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.

<template>
<CatalogPage
:items="displayItems"
:map-items="itemsWithCoords"
:loading="isLoading"
with-map
use-server-clustering
map-id="hubs-map"
point-color="#10b981"
:total-count="total"
:hovered-id="hoveredId"
@select="onSelectHub"
@update:hovered-id="hoveredId = $event"
>
<template #searchBar="{ displayedCount, totalCount }">
<CatalogSearchBar
v-model:search-query="searchQuery"
:active-filters="activeFilterBadges"
:displayed-count="displayedCount"
:total-count="totalCount"
@remove-filter="onRemoveFilter"
@search="onSearch"
>
<template #filters>
<div class="p-2 space-y-3">
<!-- Transport filter -->
<div>
<div class="text-xs font-semibold mb-1 text-base-content/70">{{ t('catalogHubsSection.filters.transport') }}</div>
<ul class="menu menu-compact">
<li v-for="filter in filters" :key="filter.id">
<a
:class="{ 'active': selectedFilter === filter.id }"
@click="selectedFilter = filter.id"
>{{ filter.label }}</a>
</li>
</ul>
</div>
<div class="divider my-0"></div>
<!-- Country filter -->
<div>
<div class="text-xs font-semibold mb-1 text-base-content/70">{{ t('catalogHubsSection.filters.country') }}</div>
<ul class="menu menu-compact max-h-48 overflow-y-auto">
<li v-for="filter in countryFilters" :key="filter.id">
<a
:class="{ 'active': selectedCountry === filter.id }"
@click="selectedCountry = filter.id"
>{{ filter.label }}</a>
</li>
</ul>
</div>
</div>
</template>
</CatalogSearchBar>
</template>
<template #header>
<Text v-if="!isLoading" tone="muted">Выберите хаб</Text>
</template>
<template #card="{ item }">
<HubCard :hub="item" />
</template>
<template #pagination>
<PaginationLoadMore
v-if="displayItems.length > 0"
:shown="displayItems.length"
:total="total"
:can-load-more="canLoadMore"
:loading="isLoadingMore"
hide-counter
@load-more="loadMore"
class="mt-4"
/>
</template>
<template #empty>
<Text tone="muted">{{ t('catalogHubsSection.empty.no_hubs') }}</Text>
</template>
</CatalogPage>
</template>
<script setup lang="ts">
import type { MapBounds } from '~/components/catalog/CatalogMap.vue'
definePageMeta({
layout: 'topnav'
})
const { t } = useI18n()
const localePath = useLocalePath()
const {
items,
total,
selectedFilter,
selectedCountry,
filters,
countryFilters,
isLoading,
isLoadingMore,
canLoadMore,
loadMore,
init
} = useCatalogHubs()
// Hover state
const hoveredId = ref<string>()
// Search bar
const searchQuery = ref('')
// Search with map checkbox
const searchWithMap = ref(false)
const currentBounds = ref<MapBounds | null>(null)
// Filter items with valid coordinates for map
const itemsWithCoords = computed(() =>
items.value.filter(item =>
item.latitude != null &&
item.longitude != null &&
!isNaN(Number(item.latitude)) &&
!isNaN(Number(item.longitude))
).map(item => ({
uuid: item.uuid,
name: item.name || '',
latitude: Number(item.latitude),
longitude: Number(item.longitude),
country: item.country
}))
)
// Filtered items when searchWithMap is enabled
const displayItems = computed(() => {
if (!searchWithMap.value || !currentBounds.value) return items.value
return items.value.filter(item => {
if (item.latitude == null || item.longitude == null) return false
const { west, east, north, south } = currentBounds.value!
const lng = Number(item.longitude)
const lat = Number(item.latitude)
return lng >= west && lng <= east && lat >= south && lat <= north
})
})
// Active filter badges (non-default filters shown as badges)
const activeFilterBadges = computed(() => {
const badges: { id: string; label: string }[] = []
if (selectedFilter.value !== 'all') {
const filter = filters.value.find(f => f.id === selectedFilter.value)
if (filter) badges.push({ id: `transport:${filter.id}`, label: filter.label })
}
if (selectedCountry.value !== 'all') {
const filter = countryFilters.value.find(f => f.id === selectedCountry.value)
if (filter) badges.push({ id: `country:${filter.id}`, label: filter.label })
}
return badges
})
// Remove filter badge
const onRemoveFilter = (id: string) => {
if (id.startsWith('transport:')) {
selectedFilter.value = 'all'
} else if (id.startsWith('country:')) {
selectedCountry.value = 'all'
}
}
// Search handler (for future use)
const onSearch = () => {
// TODO: Implement search by hub name
}
// Handle hub selection from map
const onSelectHub = (hub: any) => {
navigateTo(localePath(`/catalog/hubs/${hub.uuid}`))
}
await init()
useHead(() => ({
title: t('catalogHubsSection.header.title')
}))
</script>