From 9b99d8981cd342ebb1023d6d183114e891de7715 Mon Sep 17 00:00:00 2001 From: Ruslan Bakiev <572431+veikab@users.noreply.github.com> Date: Sat, 24 Jan 2026 12:19:00 +0700 Subject: [PATCH] Optimize catalog loading: backend bounds filtering + early returns - Add bounds (west/south/east/north) parameters to GetNodes query - Add setBoundsFilter to useCatalogHubs, useCatalogSuppliers, useCatalogProducts - Replace client-side bounds filtering with backend query - Add early return to setProductFilter to avoid redundant fetches - Watch filterByBounds to trigger backend refetch when checkbox changes --- .../graphql/public/geo-generated.ts | 16 ++++++- app/composables/useCatalogHubs.ts | 26 ++++++++++- app/composables/useCatalogProducts.ts | 10 ++++- app/composables/useCatalogSuppliers.ts | 34 ++++++++++++-- app/pages/catalog/index.vue | 45 +++++++------------ .../operations/public/geo/GetNodes.graphql | 6 +-- 6 files changed, 97 insertions(+), 40 deletions(-) diff --git a/app/composables/graphql/public/geo-generated.ts b/app/composables/graphql/public/geo-generated.ts index ac707ec..f15e50e 100644 --- a/app/composables/graphql/public/geo-generated.ts +++ b/app/composables/graphql/public/geo-generated.ts @@ -124,7 +124,7 @@ export type Query = { nodeConnections?: Maybe; /** Get all nodes (without edges for performance) */ nodes?: Maybe>>; - /** Get total count of nodes (with optional transport/country filter) */ + /** Get total count of nodes (with optional transport/country/bounds filter) */ nodesCount?: Maybe; /** Get route from a specific offer to hub */ offerToHub?: Maybe; @@ -209,17 +209,25 @@ export type QueryNodeConnectionsArgs = { /** Root query. */ export type QueryNodesArgs = { country?: InputMaybe; + east?: InputMaybe; limit?: InputMaybe; + north?: InputMaybe; offset?: InputMaybe; search?: InputMaybe; + south?: InputMaybe; transportType?: InputMaybe; + west?: InputMaybe; }; /** Root query. */ export type QueryNodesCountArgs = { country?: InputMaybe; + east?: InputMaybe; + north?: InputMaybe; + south?: InputMaybe; transportType?: InputMaybe; + west?: InputMaybe; }; @@ -381,6 +389,10 @@ export type GetNodesQueryVariables = Exact<{ offset?: InputMaybe; transportType?: InputMaybe; country?: InputMaybe; + west?: InputMaybe; + south?: InputMaybe; + east?: InputMaybe; + north?: InputMaybe; }>; @@ -468,7 +480,7 @@ export const GetHubsForProductDocument = {"kind":"Document","definitions":[{"kin export const GetHubsNearOfferDocument = {"kind":"Document","definitions":[{"kind":"OperationDefinition","operation":"query","name":{"kind":"Name","value":"GetHubsNearOffer"},"variableDefinitions":[{"kind":"VariableDefinition","variable":{"kind":"Variable","name":{"kind":"Name","value":"offerUuid"}},"type":{"kind":"NonNullType","type":{"kind":"NamedType","name":{"kind":"Name","value":"String"}}}},{"kind":"VariableDefinition","variable":{"kind":"Variable","name":{"kind":"Name","value":"limit"}},"type":{"kind":"NamedType","name":{"kind":"Name","value":"Int"}}}],"selectionSet":{"kind":"SelectionSet","selections":[{"kind":"Field","name":{"kind":"Name","value":"hubsNearOffer"},"arguments":[{"kind":"Argument","name":{"kind":"Name","value":"offerUuid"},"value":{"kind":"Variable","name":{"kind":"Name","value":"offerUuid"}}},{"kind":"Argument","name":{"kind":"Name","value":"limit"},"value":{"kind":"Variable","name":{"kind":"Name","value":"limit"}}}],"selectionSet":{"kind":"SelectionSet","selections":[{"kind":"Field","name":{"kind":"Name","value":"uuid"}},{"kind":"Field","name":{"kind":"Name","value":"name"}},{"kind":"Field","name":{"kind":"Name","value":"latitude"}},{"kind":"Field","name":{"kind":"Name","value":"longitude"}},{"kind":"Field","name":{"kind":"Name","value":"country"}},{"kind":"Field","name":{"kind":"Name","value":"countryCode"}},{"kind":"Field","name":{"kind":"Name","value":"transportTypes"}}]}}]}}]} as unknown as DocumentNode; export const GetNodeDocument = {"kind":"Document","definitions":[{"kind":"OperationDefinition","operation":"query","name":{"kind":"Name","value":"GetNode"},"variableDefinitions":[{"kind":"VariableDefinition","variable":{"kind":"Variable","name":{"kind":"Name","value":"uuid"}},"type":{"kind":"NonNullType","type":{"kind":"NamedType","name":{"kind":"Name","value":"String"}}}}],"selectionSet":{"kind":"SelectionSet","selections":[{"kind":"Field","name":{"kind":"Name","value":"node"},"arguments":[{"kind":"Argument","name":{"kind":"Name","value":"uuid"},"value":{"kind":"Variable","name":{"kind":"Name","value":"uuid"}}}],"selectionSet":{"kind":"SelectionSet","selections":[{"kind":"Field","name":{"kind":"Name","value":"uuid"}},{"kind":"Field","name":{"kind":"Name","value":"name"}},{"kind":"Field","name":{"kind":"Name","value":"latitude"}},{"kind":"Field","name":{"kind":"Name","value":"longitude"}},{"kind":"Field","name":{"kind":"Name","value":"country"}},{"kind":"Field","name":{"kind":"Name","value":"countryCode"}},{"kind":"Field","name":{"kind":"Name","value":"syncedAt"}},{"kind":"Field","name":{"kind":"Name","value":"edges"},"selectionSet":{"kind":"SelectionSet","selections":[{"kind":"Field","name":{"kind":"Name","value":"toUuid"}},{"kind":"Field","name":{"kind":"Name","value":"toName"}},{"kind":"Field","name":{"kind":"Name","value":"toLatitude"}},{"kind":"Field","name":{"kind":"Name","value":"toLongitude"}},{"kind":"Field","name":{"kind":"Name","value":"distanceKm"}},{"kind":"Field","name":{"kind":"Name","value":"travelTimeSeconds"}},{"kind":"Field","name":{"kind":"Name","value":"transportType"}}]}}]}}]}}]} as unknown as DocumentNode; export const GetNodeConnectionsDocument = {"kind":"Document","definitions":[{"kind":"OperationDefinition","operation":"query","name":{"kind":"Name","value":"GetNodeConnections"},"variableDefinitions":[{"kind":"VariableDefinition","variable":{"kind":"Variable","name":{"kind":"Name","value":"uuid"}},"type":{"kind":"NonNullType","type":{"kind":"NamedType","name":{"kind":"Name","value":"String"}}}},{"kind":"VariableDefinition","variable":{"kind":"Variable","name":{"kind":"Name","value":"limitAuto"}},"type":{"kind":"NamedType","name":{"kind":"Name","value":"Int"}}},{"kind":"VariableDefinition","variable":{"kind":"Variable","name":{"kind":"Name","value":"limitRail"}},"type":{"kind":"NamedType","name":{"kind":"Name","value":"Int"}}}],"selectionSet":{"kind":"SelectionSet","selections":[{"kind":"Field","name":{"kind":"Name","value":"nodeConnections"},"arguments":[{"kind":"Argument","name":{"kind":"Name","value":"uuid"},"value":{"kind":"Variable","name":{"kind":"Name","value":"uuid"}}},{"kind":"Argument","name":{"kind":"Name","value":"limitAuto"},"value":{"kind":"Variable","name":{"kind":"Name","value":"limitAuto"}}},{"kind":"Argument","name":{"kind":"Name","value":"limitRail"},"value":{"kind":"Variable","name":{"kind":"Name","value":"limitRail"}}}],"selectionSet":{"kind":"SelectionSet","selections":[{"kind":"Field","name":{"kind":"Name","value":"hub"},"selectionSet":{"kind":"SelectionSet","selections":[{"kind":"Field","name":{"kind":"Name","value":"uuid"}},{"kind":"Field","name":{"kind":"Name","value":"name"}},{"kind":"Field","name":{"kind":"Name","value":"latitude"}},{"kind":"Field","name":{"kind":"Name","value":"longitude"}},{"kind":"Field","name":{"kind":"Name","value":"country"}},{"kind":"Field","name":{"kind":"Name","value":"countryCode"}},{"kind":"Field","name":{"kind":"Name","value":"syncedAt"}}]}},{"kind":"Field","name":{"kind":"Name","value":"railNode"},"selectionSet":{"kind":"SelectionSet","selections":[{"kind":"Field","name":{"kind":"Name","value":"uuid"}},{"kind":"Field","name":{"kind":"Name","value":"name"}},{"kind":"Field","name":{"kind":"Name","value":"latitude"}},{"kind":"Field","name":{"kind":"Name","value":"longitude"}},{"kind":"Field","name":{"kind":"Name","value":"country"}},{"kind":"Field","name":{"kind":"Name","value":"countryCode"}},{"kind":"Field","name":{"kind":"Name","value":"syncedAt"}}]}},{"kind":"Field","name":{"kind":"Name","value":"autoEdges"},"selectionSet":{"kind":"SelectionSet","selections":[{"kind":"Field","name":{"kind":"Name","value":"toUuid"}},{"kind":"Field","name":{"kind":"Name","value":"toName"}},{"kind":"Field","name":{"kind":"Name","value":"toLatitude"}},{"kind":"Field","name":{"kind":"Name","value":"toLongitude"}},{"kind":"Field","name":{"kind":"Name","value":"distanceKm"}},{"kind":"Field","name":{"kind":"Name","value":"travelTimeSeconds"}},{"kind":"Field","name":{"kind":"Name","value":"transportType"}}]}},{"kind":"Field","name":{"kind":"Name","value":"railEdges"},"selectionSet":{"kind":"SelectionSet","selections":[{"kind":"Field","name":{"kind":"Name","value":"toUuid"}},{"kind":"Field","name":{"kind":"Name","value":"toName"}},{"kind":"Field","name":{"kind":"Name","value":"toLatitude"}},{"kind":"Field","name":{"kind":"Name","value":"toLongitude"}},{"kind":"Field","name":{"kind":"Name","value":"distanceKm"}},{"kind":"Field","name":{"kind":"Name","value":"travelTimeSeconds"}},{"kind":"Field","name":{"kind":"Name","value":"transportType"}}]}}]}}]}}]} as unknown as DocumentNode; -export const GetNodesDocument = {"kind":"Document","definitions":[{"kind":"OperationDefinition","operation":"query","name":{"kind":"Name","value":"GetNodes"},"variableDefinitions":[{"kind":"VariableDefinition","variable":{"kind":"Variable","name":{"kind":"Name","value":"limit"}},"type":{"kind":"NamedType","name":{"kind":"Name","value":"Int"}}},{"kind":"VariableDefinition","variable":{"kind":"Variable","name":{"kind":"Name","value":"offset"}},"type":{"kind":"NamedType","name":{"kind":"Name","value":"Int"}}},{"kind":"VariableDefinition","variable":{"kind":"Variable","name":{"kind":"Name","value":"transportType"}},"type":{"kind":"NamedType","name":{"kind":"Name","value":"String"}}},{"kind":"VariableDefinition","variable":{"kind":"Variable","name":{"kind":"Name","value":"country"}},"type":{"kind":"NamedType","name":{"kind":"Name","value":"String"}}}],"selectionSet":{"kind":"SelectionSet","selections":[{"kind":"Field","name":{"kind":"Name","value":"nodes"},"arguments":[{"kind":"Argument","name":{"kind":"Name","value":"limit"},"value":{"kind":"Variable","name":{"kind":"Name","value":"limit"}}},{"kind":"Argument","name":{"kind":"Name","value":"offset"},"value":{"kind":"Variable","name":{"kind":"Name","value":"offset"}}},{"kind":"Argument","name":{"kind":"Name","value":"transportType"},"value":{"kind":"Variable","name":{"kind":"Name","value":"transportType"}}},{"kind":"Argument","name":{"kind":"Name","value":"country"},"value":{"kind":"Variable","name":{"kind":"Name","value":"country"}}}],"selectionSet":{"kind":"SelectionSet","selections":[{"kind":"Field","name":{"kind":"Name","value":"uuid"}},{"kind":"Field","name":{"kind":"Name","value":"name"}},{"kind":"Field","name":{"kind":"Name","value":"latitude"}},{"kind":"Field","name":{"kind":"Name","value":"longitude"}},{"kind":"Field","name":{"kind":"Name","value":"country"}},{"kind":"Field","name":{"kind":"Name","value":"countryCode"}},{"kind":"Field","name":{"kind":"Name","value":"syncedAt"}},{"kind":"Field","name":{"kind":"Name","value":"transportTypes"}}]}},{"kind":"Field","name":{"kind":"Name","value":"nodesCount"},"arguments":[{"kind":"Argument","name":{"kind":"Name","value":"transportType"},"value":{"kind":"Variable","name":{"kind":"Name","value":"transportType"}}},{"kind":"Argument","name":{"kind":"Name","value":"country"},"value":{"kind":"Variable","name":{"kind":"Name","value":"country"}}}]}]}}]} as unknown as DocumentNode; +export const GetNodesDocument = {"kind":"Document","definitions":[{"kind":"OperationDefinition","operation":"query","name":{"kind":"Name","value":"GetNodes"},"variableDefinitions":[{"kind":"VariableDefinition","variable":{"kind":"Variable","name":{"kind":"Name","value":"limit"}},"type":{"kind":"NamedType","name":{"kind":"Name","value":"Int"}}},{"kind":"VariableDefinition","variable":{"kind":"Variable","name":{"kind":"Name","value":"offset"}},"type":{"kind":"NamedType","name":{"kind":"Name","value":"Int"}}},{"kind":"VariableDefinition","variable":{"kind":"Variable","name":{"kind":"Name","value":"transportType"}},"type":{"kind":"NamedType","name":{"kind":"Name","value":"String"}}},{"kind":"VariableDefinition","variable":{"kind":"Variable","name":{"kind":"Name","value":"country"}},"type":{"kind":"NamedType","name":{"kind":"Name","value":"String"}}},{"kind":"VariableDefinition","variable":{"kind":"Variable","name":{"kind":"Name","value":"west"}},"type":{"kind":"NamedType","name":{"kind":"Name","value":"Float"}}},{"kind":"VariableDefinition","variable":{"kind":"Variable","name":{"kind":"Name","value":"south"}},"type":{"kind":"NamedType","name":{"kind":"Name","value":"Float"}}},{"kind":"VariableDefinition","variable":{"kind":"Variable","name":{"kind":"Name","value":"east"}},"type":{"kind":"NamedType","name":{"kind":"Name","value":"Float"}}},{"kind":"VariableDefinition","variable":{"kind":"Variable","name":{"kind":"Name","value":"north"}},"type":{"kind":"NamedType","name":{"kind":"Name","value":"Float"}}}],"selectionSet":{"kind":"SelectionSet","selections":[{"kind":"Field","name":{"kind":"Name","value":"nodes"},"arguments":[{"kind":"Argument","name":{"kind":"Name","value":"limit"},"value":{"kind":"Variable","name":{"kind":"Name","value":"limit"}}},{"kind":"Argument","name":{"kind":"Name","value":"offset"},"value":{"kind":"Variable","name":{"kind":"Name","value":"offset"}}},{"kind":"Argument","name":{"kind":"Name","value":"transportType"},"value":{"kind":"Variable","name":{"kind":"Name","value":"transportType"}}},{"kind":"Argument","name":{"kind":"Name","value":"country"},"value":{"kind":"Variable","name":{"kind":"Name","value":"country"}}},{"kind":"Argument","name":{"kind":"Name","value":"west"},"value":{"kind":"Variable","name":{"kind":"Name","value":"west"}}},{"kind":"Argument","name":{"kind":"Name","value":"south"},"value":{"kind":"Variable","name":{"kind":"Name","value":"south"}}},{"kind":"Argument","name":{"kind":"Name","value":"east"},"value":{"kind":"Variable","name":{"kind":"Name","value":"east"}}},{"kind":"Argument","name":{"kind":"Name","value":"north"},"value":{"kind":"Variable","name":{"kind":"Name","value":"north"}}}],"selectionSet":{"kind":"SelectionSet","selections":[{"kind":"Field","name":{"kind":"Name","value":"uuid"}},{"kind":"Field","name":{"kind":"Name","value":"name"}},{"kind":"Field","name":{"kind":"Name","value":"latitude"}},{"kind":"Field","name":{"kind":"Name","value":"longitude"}},{"kind":"Field","name":{"kind":"Name","value":"country"}},{"kind":"Field","name":{"kind":"Name","value":"countryCode"}},{"kind":"Field","name":{"kind":"Name","value":"syncedAt"}},{"kind":"Field","name":{"kind":"Name","value":"transportTypes"}}]}},{"kind":"Field","name":{"kind":"Name","value":"nodesCount"},"arguments":[{"kind":"Argument","name":{"kind":"Name","value":"transportType"},"value":{"kind":"Variable","name":{"kind":"Name","value":"transportType"}}},{"kind":"Argument","name":{"kind":"Name","value":"country"},"value":{"kind":"Variable","name":{"kind":"Name","value":"country"}}},{"kind":"Argument","name":{"kind":"Name","value":"west"},"value":{"kind":"Variable","name":{"kind":"Name","value":"west"}}},{"kind":"Argument","name":{"kind":"Name","value":"south"},"value":{"kind":"Variable","name":{"kind":"Name","value":"south"}}},{"kind":"Argument","name":{"kind":"Name","value":"east"},"value":{"kind":"Variable","name":{"kind":"Name","value":"east"}}},{"kind":"Argument","name":{"kind":"Name","value":"north"},"value":{"kind":"Variable","name":{"kind":"Name","value":"north"}}}]}]}}]} as unknown as DocumentNode; export const GetOfferToHubDocument = {"kind":"Document","definitions":[{"kind":"OperationDefinition","operation":"query","name":{"kind":"Name","value":"GetOfferToHub"},"variableDefinitions":[{"kind":"VariableDefinition","variable":{"kind":"Variable","name":{"kind":"Name","value":"offerUuid"}},"type":{"kind":"NonNullType","type":{"kind":"NamedType","name":{"kind":"Name","value":"String"}}}},{"kind":"VariableDefinition","variable":{"kind":"Variable","name":{"kind":"Name","value":"hubUuid"}},"type":{"kind":"NonNullType","type":{"kind":"NamedType","name":{"kind":"Name","value":"String"}}}}],"selectionSet":{"kind":"SelectionSet","selections":[{"kind":"Field","name":{"kind":"Name","value":"offerToHub"},"arguments":[{"kind":"Argument","name":{"kind":"Name","value":"offerUuid"},"value":{"kind":"Variable","name":{"kind":"Name","value":"offerUuid"}}},{"kind":"Argument","name":{"kind":"Name","value":"hubUuid"},"value":{"kind":"Variable","name":{"kind":"Name","value":"hubUuid"}}}],"selectionSet":{"kind":"SelectionSet","selections":[{"kind":"Field","name":{"kind":"Name","value":"sourceUuid"}},{"kind":"Field","name":{"kind":"Name","value":"sourceName"}},{"kind":"Field","name":{"kind":"Name","value":"sourceLat"}},{"kind":"Field","name":{"kind":"Name","value":"sourceLon"}},{"kind":"Field","name":{"kind":"Name","value":"distanceKm"}},{"kind":"Field","name":{"kind":"Name","value":"routes"},"selectionSet":{"kind":"SelectionSet","selections":[{"kind":"Field","name":{"kind":"Name","value":"totalDistanceKm"}},{"kind":"Field","name":{"kind":"Name","value":"totalTimeSeconds"}},{"kind":"Field","name":{"kind":"Name","value":"stages"},"selectionSet":{"kind":"SelectionSet","selections":[{"kind":"Field","name":{"kind":"Name","value":"fromUuid"}},{"kind":"Field","name":{"kind":"Name","value":"fromName"}},{"kind":"Field","name":{"kind":"Name","value":"fromLat"}},{"kind":"Field","name":{"kind":"Name","value":"fromLon"}},{"kind":"Field","name":{"kind":"Name","value":"toUuid"}},{"kind":"Field","name":{"kind":"Name","value":"toName"}},{"kind":"Field","name":{"kind":"Name","value":"toLat"}},{"kind":"Field","name":{"kind":"Name","value":"toLon"}},{"kind":"Field","name":{"kind":"Name","value":"distanceKm"}},{"kind":"Field","name":{"kind":"Name","value":"travelTimeSeconds"}},{"kind":"Field","name":{"kind":"Name","value":"transportType"}}]}}]}}]}}]}}]} as unknown as DocumentNode; export const GetOffersByHubDocument = {"kind":"Document","definitions":[{"kind":"OperationDefinition","operation":"query","name":{"kind":"Name","value":"GetOffersByHub"},"variableDefinitions":[{"kind":"VariableDefinition","variable":{"kind":"Variable","name":{"kind":"Name","value":"hubUuid"}},"type":{"kind":"NonNullType","type":{"kind":"NamedType","name":{"kind":"Name","value":"String"}}}},{"kind":"VariableDefinition","variable":{"kind":"Variable","name":{"kind":"Name","value":"productUuid"}},"type":{"kind":"NonNullType","type":{"kind":"NamedType","name":{"kind":"Name","value":"String"}}}},{"kind":"VariableDefinition","variable":{"kind":"Variable","name":{"kind":"Name","value":"limit"}},"type":{"kind":"NamedType","name":{"kind":"Name","value":"Int"}}}],"selectionSet":{"kind":"SelectionSet","selections":[{"kind":"Field","name":{"kind":"Name","value":"offersByHub"},"arguments":[{"kind":"Argument","name":{"kind":"Name","value":"hubUuid"},"value":{"kind":"Variable","name":{"kind":"Name","value":"hubUuid"}}},{"kind":"Argument","name":{"kind":"Name","value":"productUuid"},"value":{"kind":"Variable","name":{"kind":"Name","value":"productUuid"}}},{"kind":"Argument","name":{"kind":"Name","value":"limit"},"value":{"kind":"Variable","name":{"kind":"Name","value":"limit"}}}],"selectionSet":{"kind":"SelectionSet","selections":[{"kind":"Field","name":{"kind":"Name","value":"sourceUuid"}},{"kind":"Field","name":{"kind":"Name","value":"sourceName"}},{"kind":"Field","name":{"kind":"Name","value":"sourceLat"}},{"kind":"Field","name":{"kind":"Name","value":"sourceLon"}},{"kind":"Field","name":{"kind":"Name","value":"distanceKm"}},{"kind":"Field","name":{"kind":"Name","value":"routes"},"selectionSet":{"kind":"SelectionSet","selections":[{"kind":"Field","name":{"kind":"Name","value":"totalDistanceKm"}},{"kind":"Field","name":{"kind":"Name","value":"totalTimeSeconds"}},{"kind":"Field","name":{"kind":"Name","value":"stages"},"selectionSet":{"kind":"SelectionSet","selections":[{"kind":"Field","name":{"kind":"Name","value":"fromUuid"}},{"kind":"Field","name":{"kind":"Name","value":"fromName"}},{"kind":"Field","name":{"kind":"Name","value":"fromLat"}},{"kind":"Field","name":{"kind":"Name","value":"fromLon"}},{"kind":"Field","name":{"kind":"Name","value":"toUuid"}},{"kind":"Field","name":{"kind":"Name","value":"toName"}},{"kind":"Field","name":{"kind":"Name","value":"toLat"}},{"kind":"Field","name":{"kind":"Name","value":"toLon"}},{"kind":"Field","name":{"kind":"Name","value":"distanceKm"}},{"kind":"Field","name":{"kind":"Name","value":"travelTimeSeconds"}},{"kind":"Field","name":{"kind":"Name","value":"transportType"}}]}}]}}]}}]}}]} as unknown as DocumentNode; export const GetOffersByProductDocument = {"kind":"Document","definitions":[{"kind":"OperationDefinition","operation":"query","name":{"kind":"Name","value":"GetOffersByProduct"},"variableDefinitions":[{"kind":"VariableDefinition","variable":{"kind":"Variable","name":{"kind":"Name","value":"productUuid"}},"type":{"kind":"NonNullType","type":{"kind":"NamedType","name":{"kind":"Name","value":"String"}}}}],"selectionSet":{"kind":"SelectionSet","selections":[{"kind":"Field","name":{"kind":"Name","value":"offersByProduct"},"arguments":[{"kind":"Argument","name":{"kind":"Name","value":"productUuid"},"value":{"kind":"Variable","name":{"kind":"Name","value":"productUuid"}}}],"selectionSet":{"kind":"SelectionSet","selections":[{"kind":"Field","name":{"kind":"Name","value":"uuid"}},{"kind":"Field","name":{"kind":"Name","value":"productUuid"}},{"kind":"Field","name":{"kind":"Name","value":"productName"}},{"kind":"Field","name":{"kind":"Name","value":"supplierUuid"}},{"kind":"Field","name":{"kind":"Name","value":"latitude"}},{"kind":"Field","name":{"kind":"Name","value":"longitude"}},{"kind":"Field","name":{"kind":"Name","value":"country"}},{"kind":"Field","name":{"kind":"Name","value":"countryCode"}},{"kind":"Field","name":{"kind":"Name","value":"pricePerUnit"}},{"kind":"Field","name":{"kind":"Name","value":"currency"}},{"kind":"Field","name":{"kind":"Name","value":"quantity"}},{"kind":"Field","name":{"kind":"Name","value":"unit"}}]}}]}}]} as unknown as DocumentNode; diff --git a/app/composables/useCatalogHubs.ts b/app/composables/useCatalogHubs.ts index b648919..a731376 100644 --- a/app/composables/useCatalogHubs.ts +++ b/app/composables/useCatalogHubs.ts @@ -12,6 +12,7 @@ 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() @@ -69,9 +70,21 @@ export function useCatalogHubs() { // Default: fetch all hubs with filters const transportType = selectedFilter.value === 'all' ? null : selectedFilter.value const country = selectedCountry.value === 'all' ? null : selectedCountry.value + const bounds = filterBounds.value const data = await execute( GetNodesDocument, - { limit: PAGE_SIZE, offset, transportType, country }, + { + limit: PAGE_SIZE, + offset, + transportType, + country, + ...(bounds && { + west: bounds.west, + south: bounds.south, + east: bounds.east, + north: bounds.north + }) + }, 'public', 'geo' ) @@ -111,12 +124,20 @@ export function useCatalogHubs() { }) 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) => { + 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) { @@ -142,6 +163,7 @@ export function useCatalogHubs() { fetchPage, loadMore, init, - setProductFilter + setProductFilter, + setBoundsFilter } } diff --git a/app/composables/useCatalogProducts.ts b/app/composables/useCatalogProducts.ts index c8c8ed5..516c964 100644 --- a/app/composables/useCatalogProducts.ts +++ b/app/composables/useCatalogProducts.ts @@ -99,6 +99,13 @@ export function useCatalogProducts() { } } + // Products don't have coordinates directly (they're an aggregation of offers) + // Bounds filtering would require a new backend query that filters by offer locations + // For now, this is a no-op - products show all regardless of map bounds + const setBoundsFilter = (_bounds: { west: number; south: number; east: number; north: number } | null) => { + // No-op: products are not filterable by map bounds in current implementation + } + return { items, isLoading, @@ -110,6 +117,7 @@ export function useCatalogProducts() { init, setSupplierFilter, setHubFilter, - clearFilters + clearFilters, + setBoundsFilter } } diff --git a/app/composables/useCatalogSuppliers.ts b/app/composables/useCatalogSuppliers.ts index 438c3fe..c69d77b 100644 --- a/app/composables/useCatalogSuppliers.ts +++ b/app/composables/useCatalogSuppliers.ts @@ -10,6 +10,7 @@ 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 useCatalogSuppliers() { const { execute } = useGraphQL() @@ -52,15 +53,31 @@ export function useCatalogSuppliers() { } // Default: fetch all suppliers + // If bounds filter is active, fetch more and filter client-side + // TODO: Add bounds support to exchange backend for proper server-side filtering + const bounds = filterBounds.value + const fetchLimit = bounds ? 500 : PAGE_SIZE const data = await execute( GetSupplierProfilesDocument, - { limit: PAGE_SIZE, offset }, + { limit: fetchLimit, offset: bounds ? 0 : offset }, 'public', 'exchange' ) - const next = data?.getSupplierProfiles || [] + let next = data?.getSupplierProfiles || [] + + // Client-side bounds filtering (until exchange backend supports it) + if (bounds) { + next = next.filter((s: any) => { + if (!s.latitude || !s.longitude) return false + const lat = Number(s.latitude) + const lng = Number(s.longitude) + return lat >= bounds.south && lat <= bounds.north + && lng >= bounds.west && lng <= bounds.east + }) + } + items.value = replace ? next : items.value.concat(next) - total.value = data?.getSupplierProfilesCount ?? total.value + total.value = bounds ? next.length : (data?.getSupplierProfilesCount ?? total.value) isInitialized.value = true } finally { isLoading.value = false @@ -85,12 +102,20 @@ export function useCatalogSuppliers() { } 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) => { + filterBounds.value = bounds + if (isInitialized.value) { + fetchPage(0, true) + } + } + return { items, total, @@ -101,6 +126,7 @@ export function useCatalogSuppliers() { fetchPage, loadMore, init, - setProductFilter + setProductFilter, + setBoundsFilter } } diff --git a/app/pages/catalog/index.vue b/app/pages/catalog/index.vue index d8db380..708d965 100644 --- a/app/pages/catalog/index.vue +++ b/app/pages/catalog/index.vue @@ -82,16 +82,6 @@ const onBoundsChange = (bounds: MapBounds) => { currentMapBounds.value = bounds } -// Check if item is within map bounds -const isInBounds = (item: any, bounds: MapBounds | null): boolean => { - if (!bounds) return true - if (!item.latitude || !item.longitude) return false - const lat = Number(item.latitude) - const lng = Number(item.longitude) - return lat >= bounds.south && lat <= bounds.north - && lng >= bounds.west && lng <= bounds.east -} - const { catalogMode, selectMode, @@ -117,26 +107,17 @@ const { init: initProducts, setSupplierFilter, setHubFilter, - clearFilters: clearProductFilters + clearFilters: clearProductFilters, + setBoundsFilter: setProductBoundsFilter } = useCatalogProducts() -const { items: hubs, isLoading: hubsLoading, isLoadingMore: hubsLoadingMore, canLoadMore: hubsCanLoadMore, loadMore: loadMoreHubs, init: initHubs, setProductFilter: setHubProductFilter } = useCatalogHubs() -const { items: suppliers, isLoading: suppliersLoading, isLoadingMore: suppliersLoadingMore, canLoadMore: suppliersCanLoadMore, loadMore: loadMoreSuppliers, init: initSuppliers, setProductFilter: setSupplierProductFilter } = useCatalogSuppliers() +const { items: hubs, isLoading: hubsLoading, isLoadingMore: hubsLoadingMore, canLoadMore: hubsCanLoadMore, loadMore: loadMoreHubs, init: initHubs, setProductFilter: setHubProductFilter, setBoundsFilter: setHubBoundsFilter } = useCatalogHubs() +const { items: suppliers, isLoading: suppliersLoading, isLoadingMore: suppliersLoadingMore, canLoadMore: suppliersCanLoadMore, loadMore: loadMoreSuppliers, init: initSuppliers, setProductFilter: setSupplierProductFilter, setBoundsFilter: setSupplierBoundsFilter } = useCatalogSuppliers() -// Filtered items by map bounds -const filteredProducts = computed(() => { - if (!filterByBounds.value || !currentMapBounds.value) return products.value - return products.value.filter((p: any) => isInBounds(p, currentMapBounds.value)) -}) - -const filteredHubs = computed(() => { - if (!filterByBounds.value || !currentMapBounds.value) return hubs.value - return hubs.value.filter((h: any) => isInBounds(h, currentMapBounds.value)) -}) - -const filteredSuppliers = computed(() => { - if (!filterByBounds.value || !currentMapBounds.value) return suppliers.value - return suppliers.value.filter((s: any) => isInBounds(s, currentMapBounds.value)) -}) +// Items are now filtered on the backend via setBoundsFilter +// These are simple pass-throughs to maintain template compatibility +const filteredProducts = computed(() => products.value) +const filteredHubs = computed(() => hubs.value) +const filteredSuppliers = computed(() => suppliers.value) // Selection loading state const selectionLoading = computed(() => { @@ -202,6 +183,14 @@ watch(productId, (newProductId) => { setSupplierProductFilter(newProductId || null) }, { immediate: true }) +// Apply bounds filter when "filter by map bounds" is enabled +watch([filterByBounds, currentMapBounds], ([enabled, bounds]) => { + const boundsToApply = enabled && bounds ? bounds : null + setHubBoundsFilter(boundsToApply) + setSupplierBoundsFilter(boundsToApply) + setProductBoundsFilter(boundsToApply) +}) + // Offers data for quote results const offers = ref([]) const offersLoading = ref(false) diff --git a/graphql/operations/public/geo/GetNodes.graphql b/graphql/operations/public/geo/GetNodes.graphql index 7d434b4..89b07d4 100644 --- a/graphql/operations/public/geo/GetNodes.graphql +++ b/graphql/operations/public/geo/GetNodes.graphql @@ -1,5 +1,5 @@ -query GetNodes($limit: Int, $offset: Int, $transportType: String, $country: String) { - nodes(limit: $limit, offset: $offset, transportType: $transportType, country: $country) { +query GetNodes($limit: Int, $offset: Int, $transportType: String, $country: String, $west: Float, $south: Float, $east: Float, $north: Float) { + nodes(limit: $limit, offset: $offset, transportType: $transportType, country: $country, west: $west, south: $south, east: $east, north: $north) { uuid name latitude @@ -9,5 +9,5 @@ query GetNodes($limit: Int, $offset: Int, $transportType: String, $country: Stri syncedAt transportTypes } - nodesCount(transportType: $transportType, country: $country) + nodesCount(transportType: $transportType, country: $country, west: $west, south: $south, east: $east, north: $north) }