From 43310f5c28ccb33a892a459e5fd3620d06097a69 Mon Sep 17 00:00:00 2001 From: Ruslan Bakiev <572431+veikab@users.noreply.github.com> Date: Fri, 16 Jan 2026 17:32:59 +0700 Subject: [PATCH] Enable server-side clustering on all catalog pages - Add clusterNodeType prop to CatalogPage - Update useClusteredNodes to accept nodeType parameter - Enable server clustering on /offers and /suppliers pages - Add hoveredId sync on /offers page - Remove hover:shadow-lg animation from Card --- app/components/page/CatalogPage.vue | 5 ++++- app/components/ui/Card.vue | 2 +- app/composables/graphql/public/geo-generated.ts | 4 +++- app/composables/useClusteredNodes.ts | 8 ++++++-- app/pages/catalog/offers/index.vue | 7 +++++++ app/pages/catalog/suppliers/index.vue | 2 ++ graphql/operations/public/geo/GetClusteredNodes.graphql | 2 ++ 7 files changed, 25 insertions(+), 5 deletions(-) diff --git a/app/components/page/CatalogPage.vue b/app/components/page/CatalogPage.vue index 766b62b..01f5060 100644 --- a/app/components/page/CatalogPage.vue +++ b/app/components/page/CatalogPage.vue @@ -210,6 +210,7 @@ const props = withDefaults(defineProps<{ loading?: boolean withMap?: boolean useServerClustering?: boolean // Use server-side h3 clustering for ALL points + clusterNodeType?: string // Node type for clustering: 'logistics' | 'offer' | 'supplier' mapId?: string pointColor?: string selectedId?: string @@ -220,6 +221,7 @@ const props = withDefaults(defineProps<{ loading: false, withMap: true, useServerClustering: false, + clusterNodeType: 'logistics', mapId: 'catalog-map', pointColor: '#3b82f6', hasSubNav: true, @@ -251,7 +253,8 @@ const emit = defineEmits<{ }>() // Server-side clustering -const { clusteredNodes, fetchClusters } = useClusteredNodes() +const nodeTypeRef = computed(() => props.clusterNodeType) +const { clusteredNodes, fetchClusters } = useClusteredNodes(undefined, nodeTypeRef) // Search with map checkbox const searchWithMap = ref(false) diff --git a/app/components/ui/Card.vue b/app/components/ui/Card.vue index ea0cbe1..65394fa 100644 --- a/app/components/ui/Card.vue +++ b/app/components/ui/Card.vue @@ -35,7 +35,7 @@ const toneMap: Record = { const cardClass = computed(() => { const paddingClass = paddingMap[props.padding] || paddingMap.medium const toneClass = toneMap[props.tone] || toneMap.default - const interactiveClass = props.interactive ? 'cursor-pointer hover:shadow-lg' : '' + const interactiveClass = props.interactive ? 'cursor-pointer' : '' const baseClass = 'card transition-all duration-200' return [baseClass, paddingClass, toneClass, interactiveClass].filter(Boolean).join(' ') }) diff --git a/app/composables/graphql/public/geo-generated.ts b/app/composables/graphql/public/geo-generated.ts index 50c3130..e581707 100644 --- a/app/composables/graphql/public/geo-generated.ts +++ b/app/composables/graphql/public/geo-generated.ts @@ -155,6 +155,7 @@ export type QueryAutoRouteArgs = { /** Root query. */ export type QueryClusteredNodesArgs = { east: Scalars['Float']['input']; + nodeType?: InputMaybe; north: Scalars['Float']['input']; south: Scalars['Float']['input']; transportType?: InputMaybe; @@ -313,6 +314,7 @@ export type GetClusteredNodesQueryVariables = Exact<{ north: Scalars['Float']['input']; zoom: Scalars['Int']['input']; transportType?: InputMaybe; + nodeType?: InputMaybe; }>; @@ -426,7 +428,7 @@ export type GetSuppliersQuery = { __typename?: 'Query', suppliers?: Array<{ __ty export const GetAutoRouteDocument = {"kind":"Document","definitions":[{"kind":"OperationDefinition","operation":"query","name":{"kind":"Name","value":"GetAutoRoute"},"variableDefinitions":[{"kind":"VariableDefinition","variable":{"kind":"Variable","name":{"kind":"Name","value":"fromLat"}},"type":{"kind":"NonNullType","type":{"kind":"NamedType","name":{"kind":"Name","value":"Float"}}}},{"kind":"VariableDefinition","variable":{"kind":"Variable","name":{"kind":"Name","value":"fromLon"}},"type":{"kind":"NonNullType","type":{"kind":"NamedType","name":{"kind":"Name","value":"Float"}}}},{"kind":"VariableDefinition","variable":{"kind":"Variable","name":{"kind":"Name","value":"toLat"}},"type":{"kind":"NonNullType","type":{"kind":"NamedType","name":{"kind":"Name","value":"Float"}}}},{"kind":"VariableDefinition","variable":{"kind":"Variable","name":{"kind":"Name","value":"toLon"}},"type":{"kind":"NonNullType","type":{"kind":"NamedType","name":{"kind":"Name","value":"Float"}}}}],"selectionSet":{"kind":"SelectionSet","selections":[{"kind":"Field","name":{"kind":"Name","value":"autoRoute"},"arguments":[{"kind":"Argument","name":{"kind":"Name","value":"fromLat"},"value":{"kind":"Variable","name":{"kind":"Name","value":"fromLat"}}},{"kind":"Argument","name":{"kind":"Name","value":"fromLon"},"value":{"kind":"Variable","name":{"kind":"Name","value":"fromLon"}}},{"kind":"Argument","name":{"kind":"Name","value":"toLat"},"value":{"kind":"Variable","name":{"kind":"Name","value":"toLat"}}},{"kind":"Argument","name":{"kind":"Name","value":"toLon"},"value":{"kind":"Variable","name":{"kind":"Name","value":"toLon"}}}],"selectionSet":{"kind":"SelectionSet","selections":[{"kind":"Field","name":{"kind":"Name","value":"distanceKm"}},{"kind":"Field","name":{"kind":"Name","value":"geometry"}}]}}]}}]} as unknown as DocumentNode; -export const GetClusteredNodesDocument = {"kind":"Document","definitions":[{"kind":"OperationDefinition","operation":"query","name":{"kind":"Name","value":"GetClusteredNodes"},"variableDefinitions":[{"kind":"VariableDefinition","variable":{"kind":"Variable","name":{"kind":"Name","value":"west"}},"type":{"kind":"NonNullType","type":{"kind":"NamedType","name":{"kind":"Name","value":"Float"}}}},{"kind":"VariableDefinition","variable":{"kind":"Variable","name":{"kind":"Name","value":"south"}},"type":{"kind":"NonNullType","type":{"kind":"NamedType","name":{"kind":"Name","value":"Float"}}}},{"kind":"VariableDefinition","variable":{"kind":"Variable","name":{"kind":"Name","value":"east"}},"type":{"kind":"NonNullType","type":{"kind":"NamedType","name":{"kind":"Name","value":"Float"}}}},{"kind":"VariableDefinition","variable":{"kind":"Variable","name":{"kind":"Name","value":"north"}},"type":{"kind":"NonNullType","type":{"kind":"NamedType","name":{"kind":"Name","value":"Float"}}}},{"kind":"VariableDefinition","variable":{"kind":"Variable","name":{"kind":"Name","value":"zoom"}},"type":{"kind":"NonNullType","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"}}}],"selectionSet":{"kind":"SelectionSet","selections":[{"kind":"Field","name":{"kind":"Name","value":"clusteredNodes"},"arguments":[{"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"}}},{"kind":"Argument","name":{"kind":"Name","value":"zoom"},"value":{"kind":"Variable","name":{"kind":"Name","value":"zoom"}}},{"kind":"Argument","name":{"kind":"Name","value":"transportType"},"value":{"kind":"Variable","name":{"kind":"Name","value":"transportType"}}}],"selectionSet":{"kind":"SelectionSet","selections":[{"kind":"Field","name":{"kind":"Name","value":"id"}},{"kind":"Field","name":{"kind":"Name","value":"latitude"}},{"kind":"Field","name":{"kind":"Name","value":"longitude"}},{"kind":"Field","name":{"kind":"Name","value":"count"}},{"kind":"Field","name":{"kind":"Name","value":"expansionZoom"}},{"kind":"Field","name":{"kind":"Name","value":"name"}}]}}]}}]} as unknown as DocumentNode; +export const GetClusteredNodesDocument = {"kind":"Document","definitions":[{"kind":"OperationDefinition","operation":"query","name":{"kind":"Name","value":"GetClusteredNodes"},"variableDefinitions":[{"kind":"VariableDefinition","variable":{"kind":"Variable","name":{"kind":"Name","value":"west"}},"type":{"kind":"NonNullType","type":{"kind":"NamedType","name":{"kind":"Name","value":"Float"}}}},{"kind":"VariableDefinition","variable":{"kind":"Variable","name":{"kind":"Name","value":"south"}},"type":{"kind":"NonNullType","type":{"kind":"NamedType","name":{"kind":"Name","value":"Float"}}}},{"kind":"VariableDefinition","variable":{"kind":"Variable","name":{"kind":"Name","value":"east"}},"type":{"kind":"NonNullType","type":{"kind":"NamedType","name":{"kind":"Name","value":"Float"}}}},{"kind":"VariableDefinition","variable":{"kind":"Variable","name":{"kind":"Name","value":"north"}},"type":{"kind":"NonNullType","type":{"kind":"NamedType","name":{"kind":"Name","value":"Float"}}}},{"kind":"VariableDefinition","variable":{"kind":"Variable","name":{"kind":"Name","value":"zoom"}},"type":{"kind":"NonNullType","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":"nodeType"}},"type":{"kind":"NamedType","name":{"kind":"Name","value":"String"}}}],"selectionSet":{"kind":"SelectionSet","selections":[{"kind":"Field","name":{"kind":"Name","value":"clusteredNodes"},"arguments":[{"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"}}},{"kind":"Argument","name":{"kind":"Name","value":"zoom"},"value":{"kind":"Variable","name":{"kind":"Name","value":"zoom"}}},{"kind":"Argument","name":{"kind":"Name","value":"transportType"},"value":{"kind":"Variable","name":{"kind":"Name","value":"transportType"}}},{"kind":"Argument","name":{"kind":"Name","value":"nodeType"},"value":{"kind":"Variable","name":{"kind":"Name","value":"nodeType"}}}],"selectionSet":{"kind":"SelectionSet","selections":[{"kind":"Field","name":{"kind":"Name","value":"id"}},{"kind":"Field","name":{"kind":"Name","value":"latitude"}},{"kind":"Field","name":{"kind":"Name","value":"longitude"}},{"kind":"Field","name":{"kind":"Name","value":"count"}},{"kind":"Field","name":{"kind":"Name","value":"expansionZoom"}},{"kind":"Field","name":{"kind":"Name","value":"name"}}]}}]}}]} as unknown as DocumentNode; export const GetHubCountriesDocument = {"kind":"Document","definitions":[{"kind":"OperationDefinition","operation":"query","name":{"kind":"Name","value":"GetHubCountries"},"selectionSet":{"kind":"SelectionSet","selections":[{"kind":"Field","name":{"kind":"Name","value":"hubCountries"}}]}}]} as unknown as DocumentNode; 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; diff --git a/app/composables/useClusteredNodes.ts b/app/composables/useClusteredNodes.ts index f43b580..84dc2ad 100644 --- a/app/composables/useClusteredNodes.ts +++ b/app/composables/useClusteredNodes.ts @@ -9,7 +9,10 @@ export interface MapBounds { zoom: number } -export function useClusteredNodes(transportType?: Ref) { +export function useClusteredNodes( + transportType?: Ref, + nodeType?: Ref +) { const { client } = useApolloClient('publicGeo') const clusteredNodes = ref([]) @@ -26,7 +29,8 @@ export function useClusteredNodes(transportType?: Ref) { east: bounds.east, north: bounds.north, zoom: Math.floor(bounds.zoom), - transportType: transportType?.value + transportType: transportType?.value, + nodeType: nodeType?.value }, fetchPolicy: 'network-only' }) diff --git a/app/pages/catalog/offers/index.vue b/app/pages/catalog/offers/index.vue index 6bc5c52..f532555 100644 --- a/app/pages/catalog/offers/index.vue +++ b/app/pages/catalog/offers/index.vue @@ -4,8 +4,12 @@ :loading="isLoading" :total-count="products.length" with-map + use-server-clustering + cluster-node-type="offer" map-id="offers-products-map" point-color="#3b82f6" + :hovered-id="hoveredProductId" + @update:hovered-id="hoveredProductId = $event" >