import type { ProductsListQueryResult, NearestOffersQueryResult } from '~/composables/graphql/public/geo-generated' import { ProductsListDocument, GetNodeDocument, NearestOffersDocument } from '~/composables/graphql/public/geo-generated' import { GetSupplierProfileDocument } from '~/composables/graphql/public/exchange-generated' // Type from codegen type ProductItem = NonNullable[number]> type OfferItem = NonNullable[number]> // Product aggregated from offers interface AggregatedProduct { uuid: string name: string | null | undefined offersCount: number } // Shared state const items = ref([]) const isLoading = ref(false) const isLoadingMore = ref(false) const isInitialized = ref(false) // Filter state const filterSupplierUuid = ref(null) const filterHubUuid = ref(null) const filterBounds = ref<{ west: number; south: number; east: number; north: number } | null>(null) export function useCatalogProducts() { const { execute } = useGraphQL() // Products don't have server-side pagination yet, so we load all at once const canLoadMore = computed(() => false) const fetchProducts = async () => { if (isLoading.value) return isLoading.value = true try { let data if (filterSupplierUuid.value) { // Products from specific supplier - get supplier coordinates first const supplierData = await execute( GetSupplierProfileDocument, { uuid: filterSupplierUuid.value }, 'public', 'exchange' ) const supplier = supplierData?.getSupplierProfile if (!supplier?.latitude || !supplier?.longitude) { console.warn('Supplier has no coordinates') items.value = [] } else { // Get offers near supplier and group by product const offersData = await execute( NearestOffersDocument, { lat: supplier.latitude, lon: supplier.longitude, radius: 500 }, 'public', 'geo' ) // Group offers by product const productsMap = new Map() offersData?.nearestOffers?.forEach((offer) => { if (!offer?.productUuid) return if (!productsMap.has(offer.productUuid)) { productsMap.set(offer.productUuid, { uuid: offer.productUuid, name: offer.productName, offersCount: 0 }) } productsMap.get(offer.productUuid)!.offersCount++ }) items.value = Array.from(productsMap.values()) as ProductItem[] } } else if (filterHubUuid.value) { // Products near hub - get hub coordinates first const hubData = await execute( GetNodeDocument, { uuid: filterHubUuid.value }, 'public', 'geo' ) const hub = hubData?.node if (!hub?.latitude || !hub?.longitude) { console.warn('Hub has no coordinates') items.value = [] } else { // Get offers near hub and group by product const offersData = await execute( NearestOffersDocument, { lat: hub.latitude, lon: hub.longitude, radius: 500 }, 'public', 'geo' ) // Group offers by product const productsMap = new Map() offersData?.nearestOffers?.forEach((offer) => { if (!offer?.productUuid) return if (!productsMap.has(offer.productUuid)) { productsMap.set(offer.productUuid, { uuid: offer.productUuid, name: offer.productName, offersCount: 0 }) } productsMap.get(offer.productUuid)!.offersCount++ }) items.value = Array.from(productsMap.values()) as ProductItem[] } } else { // All products from graph data = await execute( ProductsListDocument, { limit: 500, ...(filterBounds.value && { west: filterBounds.value.west, south: filterBounds.value.south, east: filterBounds.value.east, north: filterBounds.value.north }) }, 'public', 'geo' ) items.value = (data?.productsList || []).filter((p): p is ProductItem => p !== null) } isInitialized.value = true } finally { isLoading.value = false } } const loadMore = async () => { // No-op: products don't support pagination yet } const init = async () => { if (!isInitialized.value && items.value.length === 0) { await fetchProducts() } } // Filter setters const setSupplierFilter = (uuid: string | null) => { if (filterSupplierUuid.value !== uuid) { filterSupplierUuid.value = uuid filterHubUuid.value = null // clear other filter isInitialized.value = false fetchProducts() } } const setHubFilter = (uuid: string | null) => { if (filterHubUuid.value !== uuid) { filterHubUuid.value = uuid filterSupplierUuid.value = null // clear other filter isInitialized.value = false fetchProducts() } } const clearFilters = () => { if (filterSupplierUuid.value || filterHubUuid.value) { filterSupplierUuid.value = null filterHubUuid.value = null isInitialized.value = false fetchProducts() } } // Products are filtered by offer locations within bounds 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) { fetchProducts() } } return { items, isLoading, isLoadingMore, isInitialized, canLoadMore, fetchProducts, loadMore, init, setSupplierFilter, setHubFilter, clearFilters, setBoundsFilter } }