All checks were successful
Build Docker Image / build (push) Successful in 3m42s
Animation on map only needed on final pages (routes/sources). Intermediate pages now navigate directly on card click. Files changed: - hubs/index.vue - removed hover/select - suppliers/index.vue - removed hover/select - suppliers/[]/[]/index.vue - removed hover - offers/index.vue - removed hover - offers/[]/index.vue - removed hover
172 lines
4.8 KiB
Vue
172 lines
4.8 KiB
Vue
<template>
|
||
<CatalogPage
|
||
:items="displayItems"
|
||
:map-items="itemsWithCoords"
|
||
:loading="isLoading"
|
||
with-map
|
||
use-server-clustering
|
||
map-id="hubs-map"
|
||
point-color="#10b981"
|
||
:total-count="total"
|
||
>
|
||
<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 {
|
||
items,
|
||
total,
|
||
selectedFilter,
|
||
selectedCountry,
|
||
filters,
|
||
countryFilters,
|
||
isLoading,
|
||
isLoadingMore,
|
||
canLoadMore,
|
||
loadMore,
|
||
init
|
||
} = useCatalogHubs()
|
||
|
||
// 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
|
||
}
|
||
|
||
await init()
|
||
|
||
useHead(() => ({
|
||
title: t('catalogHubsSection.header.title')
|
||
}))
|
||
</script>
|