All checks were successful
Build Docker Image / build (push) Successful in 4m14s
- Remove currentMapBounds from watch - it changes on every map move - Watch only filterByBounds and urlBounds (URL-based state) - Add early return in setBoundsFilter if bounds haven't changed This fixes the issue where the list was reloading on every map movement even when the 'filter by bounds' checkbox was OFF.
193 lines
5.6 KiB
TypeScript
193 lines
5.6 KiB
TypeScript
import { HubsListDocument, GetHubCountriesDocument, NearestHubsDocument } from '~/composables/graphql/public/geo-generated'
|
|
|
|
const PAGE_SIZE = 24
|
|
|
|
// Shared state across list and map views
|
|
const items = ref<any[]>([])
|
|
const total = ref(0)
|
|
const selectedFilter = ref('all')
|
|
const selectedCountry = ref('all')
|
|
const countries = ref<string[]>([])
|
|
const isLoading = ref(false)
|
|
const isLoadingMore = ref(false)
|
|
const isInitialized = ref(false)
|
|
const filterProductUuid = ref<string | null>(null)
|
|
const filterBounds = ref<{ west: number; south: number; east: number; north: number } | null>(null)
|
|
|
|
export function useCatalogHubs() {
|
|
const { t } = useI18n()
|
|
const { execute } = useGraphQL()
|
|
|
|
const filters = computed(() => [
|
|
{ id: 'all', label: t('catalogHubsSection.filters.all') },
|
|
{ id: 'auto', label: t('catalogHubsSection.filters.auto') },
|
|
{ id: 'rail', label: t('catalogHubsSection.filters.rail') },
|
|
{ id: 'sea', label: t('catalogHubsSection.filters.sea') },
|
|
{ id: 'air', label: t('catalogHubsSection.filters.air') }
|
|
])
|
|
|
|
const countryFilters = computed(() => [
|
|
{ id: 'all', label: t('catalogHubsSection.filters.all_countries') },
|
|
...countries.value.map(c => ({ id: c, label: c }))
|
|
])
|
|
|
|
const itemsWithCoords = computed(() =>
|
|
items.value.filter(h => h.latitude && h.longitude)
|
|
)
|
|
|
|
const itemsByCountry = computed(() => {
|
|
const grouped = new Map<string, any[]>()
|
|
items.value.forEach(hub => {
|
|
const country = hub.country || t('catalogMap.labels.country_unknown')
|
|
if (!grouped.has(country)) grouped.set(country, [])
|
|
grouped.get(country)!.push(hub)
|
|
})
|
|
return Array.from(grouped.entries())
|
|
.map(([name, hubsList]) => ({ name, hubs: hubsList }))
|
|
.sort((a, b) => a.name.localeCompare(b.name))
|
|
})
|
|
|
|
const canLoadMore = computed(() => items.value.length < total.value)
|
|
|
|
const fetchPage = async (offset: number, replace = false) => {
|
|
if (replace) isLoading.value = true
|
|
try {
|
|
// If filtering by product, use nearestHubs with global search
|
|
// (center point 0,0 with very large radius to cover entire globe)
|
|
if (filterProductUuid.value) {
|
|
const data = await execute(
|
|
NearestHubsDocument,
|
|
{
|
|
lat: 0,
|
|
lon: 0,
|
|
radius: 20000, // 20000 km radius covers entire Earth
|
|
productUuid: filterProductUuid.value,
|
|
limit: 500 // Increased limit for global search
|
|
},
|
|
'public',
|
|
'geo'
|
|
)
|
|
const next = data?.nearestHubs || []
|
|
items.value = next
|
|
total.value = next.length
|
|
isInitialized.value = true
|
|
return
|
|
}
|
|
|
|
// Default: fetch all hubs with filters using hubsList
|
|
const transportType = selectedFilter.value === 'all' ? null : selectedFilter.value
|
|
const country = selectedCountry.value === 'all' ? null : selectedCountry.value
|
|
|
|
const data = await execute(
|
|
HubsListDocument,
|
|
{
|
|
limit: PAGE_SIZE,
|
|
offset,
|
|
transportType,
|
|
country,
|
|
...(filterBounds.value && {
|
|
west: filterBounds.value.west,
|
|
south: filterBounds.value.south,
|
|
east: filterBounds.value.east,
|
|
north: filterBounds.value.north
|
|
})
|
|
},
|
|
'public',
|
|
'geo'
|
|
)
|
|
const next = data?.hubsList || []
|
|
items.value = replace ? next : items.value.concat(next)
|
|
// hubsList doesn't return total count, estimate from fetched items
|
|
if (replace) {
|
|
total.value = next.length < PAGE_SIZE ? next.length : next.length + PAGE_SIZE
|
|
} else if (next.length < PAGE_SIZE) {
|
|
total.value = items.value.length
|
|
}
|
|
isInitialized.value = true
|
|
} finally {
|
|
isLoading.value = false
|
|
}
|
|
}
|
|
|
|
const loadCountries = async () => {
|
|
try {
|
|
const data = await execute(GetHubCountriesDocument, {}, 'public', 'geo')
|
|
countries.value = (data?.hubCountries || []).filter((c): c is string => c !== null)
|
|
} catch (e) {
|
|
console.error('Failed to load hub countries', e)
|
|
}
|
|
}
|
|
|
|
const loadMore = async () => {
|
|
if (isLoadingMore.value) return
|
|
isLoadingMore.value = true
|
|
try {
|
|
await fetchPage(items.value.length)
|
|
} finally {
|
|
isLoadingMore.value = false
|
|
}
|
|
}
|
|
|
|
// При смене фильтра - перезагрузка
|
|
watch([selectedFilter, selectedCountry], () => {
|
|
if (isInitialized.value) {
|
|
fetchPage(0, true)
|
|
}
|
|
})
|
|
|
|
const setProductFilter = (uuid: string | null) => {
|
|
if (filterProductUuid.value === uuid) return // Early return if unchanged
|
|
filterProductUuid.value = uuid
|
|
if (isInitialized.value) {
|
|
fetchPage(0, true)
|
|
}
|
|
}
|
|
|
|
const setBoundsFilter = (bounds: { west: number; south: number; east: number; north: number } | null) => {
|
|
// Early return if bounds haven't changed
|
|
const prev = filterBounds.value
|
|
const same = prev === bounds || (
|
|
prev && bounds &&
|
|
prev.west === bounds.west &&
|
|
prev.south === bounds.south &&
|
|
prev.east === bounds.east &&
|
|
prev.north === bounds.north
|
|
)
|
|
if (same) return
|
|
|
|
filterBounds.value = bounds
|
|
if (isInitialized.value) {
|
|
fetchPage(0, true)
|
|
}
|
|
}
|
|
|
|
// Initialize data if not already loaded
|
|
const init = async () => {
|
|
if (!isInitialized.value && items.value.length === 0) {
|
|
await Promise.all([
|
|
fetchPage(0, true),
|
|
loadCountries()
|
|
])
|
|
}
|
|
}
|
|
|
|
return {
|
|
items,
|
|
total,
|
|
selectedFilter,
|
|
selectedCountry,
|
|
filters,
|
|
countryFilters,
|
|
isLoading,
|
|
isLoadingMore,
|
|
itemsWithCoords,
|
|
itemsByCountry,
|
|
canLoadMore,
|
|
fetchPage,
|
|
loadMore,
|
|
init,
|
|
setProductFilter,
|
|
setBoundsFilter
|
|
}
|
|
}
|