Files
webapp/app/composables/useCatalogHubs.ts
Ruslan Bakiev 19aca61845
All checks were successful
Build Docker Image / build (push) Successful in 4m14s
fix(catalog): prevent unnecessary list reloads on map movement
- 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.
2026-01-26 22:24:47 +07:00

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
}
}