import type { HubsListQueryResult, NearestHubsQueryResult } from '~/composables/graphql/public/geo-generated' import { HubsListDocument, GetHubCountriesDocument, NearestHubsDocument } from '~/composables/graphql/public/geo-generated' const PAGE_SIZE = 24 // Type from codegen - exported for use in pages export type CatalogHubItem = NonNullable[number]> export type CatalogNearestHubItem = NonNullable[number]> // Internal aliases type HubItem = CatalogHubItem type NearestHubItem = CatalogNearestHubItem // Shared state across list and map views const items = ref>([]) const total = ref(0) const selectedFilter = ref('all') const selectedCountry = ref('all') const countries = ref([]) const isLoading = ref(false) const isLoadingMore = ref(false) const isInitialized = ref(false) const filterProductUuid = ref(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>() 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 || []).filter((h): h is NearestHubItem => h !== null) 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 || []).filter((h): h is HubItem => h !== null) 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 } }