feat(catalog): filter map clusters by chips
All checks were successful
Build Docker Image / build (push) Successful in 5m1s

This commit is contained in:
Ruslan Bakiev
2026-02-07 08:35:22 +07:00
parent aa7790f45e
commit 755a92d194
7 changed files with 89 additions and 103 deletions

View File

@@ -116,13 +116,6 @@
:key="product.uuid ?? index" :key="product.uuid ?? index"
class="relative group" class="relative group"
> >
<button
v-if="product.uuid"
class="absolute top-2 right-2 z-10 opacity-0 group-hover:opacity-100 transition-opacity bg-black/40 hover:bg-black/60 text-white rounded-full w-6 h-6 flex items-center justify-center"
@click.stop="emit('pin', 'product', product)"
>
<Icon name="lucide:pin" size="12" />
</button>
<ProductCard <ProductCard
:product="product" :product="product"
compact compact
@@ -191,13 +184,6 @@
:key="supplier.uuid ?? index" :key="supplier.uuid ?? index"
class="relative group" class="relative group"
> >
<button
v-if="supplier.uuid"
class="absolute top-2 right-2 z-10 opacity-0 group-hover:opacity-100 transition-opacity bg-black/40 hover:bg-black/60 text-white rounded-full w-6 h-6 flex items-center justify-center"
@click.stop="emit('pin', 'supplier', supplier)"
>
<Icon name="lucide:pin" size="12" />
</button>
<SupplierCard <SupplierCard
:supplier="supplier" :supplier="supplier"
selectable selectable
@@ -235,13 +221,6 @@
:key="hub.uuid ?? index" :key="hub.uuid ?? index"
class="relative group" class="relative group"
> >
<button
v-if="hub.uuid"
class="absolute top-2 right-2 z-10 opacity-0 group-hover:opacity-100 transition-opacity bg-black/40 hover:bg-black/60 text-white rounded-full w-6 h-6 flex items-center justify-center"
@click.stop="emit('pin', 'hub', hub)"
>
<Icon name="lucide:pin" size="12" />
</button>
<HubCard <HubCard
:hub="hub" :hub="hub"
:origin="originCoords" :origin="originCoords"
@@ -267,13 +246,6 @@
:key="hub.uuid ?? index" :key="hub.uuid ?? index"
class="relative group" class="relative group"
> >
<button
v-if="hub.uuid"
class="absolute top-2 right-2 z-10 opacity-0 group-hover:opacity-100 transition-opacity bg-black/40 hover:bg-black/60 text-white rounded-full w-6 h-6 flex items-center justify-center"
@click.stop="emit('pin', 'hub', hub)"
>
<Icon name="lucide:pin" size="12" />
</button>
<HubCard <HubCard
:hub="hub" :hub="hub"
:origin="originCoords" :origin="originCoords"
@@ -286,11 +258,6 @@
</div> </div>
</section> </section>
<!-- Add to filter button -->
<button class="btn btn-primary btn-sm mt-2" @click="emit('add-to-filter')">
<Icon name="lucide:filter-plus" size="16" />
{{ $t('catalog.info.addToFilter') }}
</button>
</div> </div>
</div> </div>
</div> </div>
@@ -326,8 +293,6 @@ const props = defineProps<{
const emit = defineEmits<{ const emit = defineEmits<{
'close': [] 'close': []
'add-to-filter': []
'pin': [type: 'product' | 'supplier' | 'hub', item: { uuid?: string | null; name?: string | null }]
'open-info': [type: InfoEntityType, uuid: string] 'open-info': [type: InfoEntityType, uuid: string]
'select-product': [uuid: string | null] 'select-product': [uuid: string | null]
'select-offer': [offer: { uuid: string; productUuid?: string | null }] 'select-offer': [offer: { uuid: string; productUuid?: string | null }]

View File

@@ -31,13 +31,6 @@
@mouseenter="emit('hover', item.uuid ?? null)" @mouseenter="emit('hover', item.uuid ?? null)"
@mouseleave="emit('hover', null)" @mouseleave="emit('hover', null)"
> >
<button
v-if="item.uuid"
class="absolute top-2 right-2 z-10 opacity-0 group-hover:opacity-100 transition-opacity bg-black/40 hover:bg-black/60 text-white rounded-full w-6 h-6 flex items-center justify-center"
@click.stop="onPin(item)"
>
<Icon name="lucide:pin" size="12" />
</button>
<ProductCard <ProductCard
:product="item" :product="item"
selectable selectable
@@ -56,13 +49,6 @@
@mouseenter="emit('hover', item.uuid ?? null)" @mouseenter="emit('hover', item.uuid ?? null)"
@mouseleave="emit('hover', null)" @mouseleave="emit('hover', null)"
> >
<button
v-if="item.uuid"
class="absolute top-2 right-2 z-10 opacity-0 group-hover:opacity-100 transition-opacity bg-black/40 hover:bg-black/60 text-white rounded-full w-6 h-6 flex items-center justify-center"
@click.stop="onPin(item)"
>
<Icon name="lucide:pin" size="12" />
</button>
<HubCard <HubCard
:hub="item" :hub="item"
selectable selectable
@@ -80,13 +66,6 @@
@mouseenter="emit('hover', item.uuid ?? null)" @mouseenter="emit('hover', item.uuid ?? null)"
@mouseleave="emit('hover', null)" @mouseleave="emit('hover', null)"
> >
<button
v-if="item.uuid"
class="absolute top-2 right-2 z-10 opacity-0 group-hover:opacity-100 transition-opacity bg-black/40 hover:bg-black/60 text-white rounded-full w-6 h-6 flex items-center justify-center"
@click.stop="onPin(item)"
>
<Icon name="lucide:pin" size="12" />
</button>
<SupplierCard <SupplierCard
:supplier="item" :supplier="item"
selectable selectable
@@ -129,7 +108,6 @@ const props = defineProps<{
const emit = defineEmits<{ const emit = defineEmits<{
'select': [type: string, item: Item] 'select': [type: string, item: Item]
'pin': [type: string, item: Item]
'close': [] 'close': []
'load-more': [] 'load-more': []
'hover': [uuid: string | null] 'hover': [uuid: string | null]
@@ -191,10 +169,4 @@ const onSelect = (item: Item) => {
emit('select', props.selectMode, item) emit('select', props.selectMode, item)
} }
} }
const onPin = (item: Item) => {
if (props.selectMode && item.uuid) {
emit('pin', props.selectMode, item)
}
}
</script> </script>

View File

@@ -71,6 +71,7 @@
<!-- View mode toggle --> <!-- View mode toggle -->
<div class="flex gap-1 glass-bright rounded-full p-1"> <div class="flex gap-1 glass-bright rounded-full p-1">
<button <button
v-if="showOffersToggle"
class="flex items-center gap-2 px-3 py-1.5 rounded-md text-sm font-medium transition-colors" class="flex items-center gap-2 px-3 py-1.5 rounded-md text-sm font-medium transition-colors"
:class="mapViewMode === 'offers' ? 'bg-white/20 text-white' : 'text-white/70 hover:text-white hover:bg-white/10'" :class="mapViewMode === 'offers' ? 'bg-white/20 text-white' : 'text-white/70 hover:text-white hover:bg-white/10'"
@click="setMapViewMode('offers')" @click="setMapViewMode('offers')"
@@ -81,6 +82,7 @@
{{ $t('catalog.views.offers') }} {{ $t('catalog.views.offers') }}
</button> </button>
<button <button
v-if="showHubsToggle"
class="flex items-center gap-2 px-3 py-1.5 rounded-md text-sm font-medium transition-colors" class="flex items-center gap-2 px-3 py-1.5 rounded-md text-sm font-medium transition-colors"
:class="mapViewMode === 'hubs' ? 'bg-white/20 text-white' : 'text-white/70 hover:text-white hover:bg-white/10'" :class="mapViewMode === 'hubs' ? 'bg-white/20 text-white' : 'text-white/70 hover:text-white hover:bg-white/10'"
@click="setMapViewMode('hubs')" @click="setMapViewMode('hubs')"
@@ -91,6 +93,7 @@
{{ $t('catalog.views.hubs') }} {{ $t('catalog.views.hubs') }}
</button> </button>
<button <button
v-if="showSuppliersToggle"
class="flex items-center gap-2 px-3 py-1.5 rounded-md text-sm font-medium transition-colors" class="flex items-center gap-2 px-3 py-1.5 rounded-md text-sm font-medium transition-colors"
:class="mapViewMode === 'suppliers' ? 'bg-white/20 text-white' : 'text-white/70 hover:text-white hover:bg-white/10'" :class="mapViewMode === 'suppliers' ? 'bg-white/20 text-white' : 'text-white/70 hover:text-white hover:bg-white/10'"
@click="setMapViewMode('suppliers')" @click="setMapViewMode('suppliers')"
@@ -132,6 +135,7 @@
<!-- Mobile view toggle - hide in info mode or when hideViewToggle --> <!-- Mobile view toggle - hide in info mode or when hideViewToggle -->
<div v-if="!isInfoMode && !hideViewToggle" class="flex gap-1 glass-bright rounded-full p-1"> <div v-if="!isInfoMode && !hideViewToggle" class="flex gap-1 glass-bright rounded-full p-1">
<button <button
v-if="showOffersToggle"
class="flex items-center justify-center w-8 h-8 rounded-md transition-colors" class="flex items-center justify-center w-8 h-8 rounded-md transition-colors"
:class="mapViewMode === 'offers' ? 'bg-white/20' : 'hover:bg-white/10'" :class="mapViewMode === 'offers' ? 'bg-white/20' : 'hover:bg-white/10'"
@click="setMapViewMode('offers')" @click="setMapViewMode('offers')"
@@ -141,6 +145,7 @@
</span> </span>
</button> </button>
<button <button
v-if="showHubsToggle"
class="flex items-center justify-center w-8 h-8 rounded-md transition-colors" class="flex items-center justify-center w-8 h-8 rounded-md transition-colors"
:class="mapViewMode === 'hubs' ? 'bg-white/20' : 'hover:bg-white/10'" :class="mapViewMode === 'hubs' ? 'bg-white/20' : 'hover:bg-white/10'"
@click="setMapViewMode('hubs')" @click="setMapViewMode('hubs')"
@@ -150,6 +155,7 @@
</span> </span>
</button> </button>
<button <button
v-if="showSuppliersToggle"
class="flex items-center justify-center w-8 h-8 rounded-md transition-colors" class="flex items-center justify-center w-8 h-8 rounded-md transition-colors"
:class="mapViewMode === 'suppliers' ? 'bg-white/20' : 'hover:bg-white/10'" :class="mapViewMode === 'suppliers' ? 'bg-white/20' : 'hover:bg-white/10'"
@click="setMapViewMode('suppliers')" @click="setMapViewMode('suppliers')"
@@ -258,6 +264,12 @@ const props = withDefaults(defineProps<{
forceInfoMode?: boolean forceInfoMode?: boolean
panelWidth?: string panelWidth?: string
hideViewToggle?: boolean hideViewToggle?: boolean
showOffersToggle?: boolean
showHubsToggle?: boolean
showSuppliersToggle?: boolean
clusterProductUuid?: string
clusterHubUuid?: string
clusterSupplierUuid?: string
relatedPoints?: Array<{ relatedPoints?: Array<{
uuid: string uuid: string
name: string name: string
@@ -279,6 +291,12 @@ const props = withDefaults(defineProps<{
forceInfoMode: false, forceInfoMode: false,
panelWidth: 'w-96', panelWidth: 'w-96',
hideViewToggle: false, hideViewToggle: false,
showOffersToggle: true,
showHubsToggle: true,
showSuppliersToggle: true,
clusterProductUuid: undefined,
clusterHubUuid: undefined,
clusterSupplierUuid: undefined,
relatedPoints: () => [] relatedPoints: () => []
}) })
@@ -291,13 +309,23 @@ const emit = defineEmits<{
const useTypedClusters = computed(() => props.useTypedClusters && props.useServerClustering) const useTypedClusters = computed(() => props.useTypedClusters && props.useServerClustering)
const clusterProductUuid = computed(() => props.clusterProductUuid ?? undefined)
const clusterHubUuid = computed(() => props.clusterHubUuid ?? undefined)
const clusterSupplierUuid = computed(() => props.clusterSupplierUuid ?? undefined)
// Server-side clustering (single-type mode) // Server-side clustering (single-type mode)
const { clusteredNodes, fetchClusters, loading: singleClusterLoading, clearNodes } = useClusteredNodes(undefined, activeClusterNodeType) const { clusteredNodes, fetchClusters, loading: singleClusterLoading, clearNodes } = useClusteredNodes(
undefined,
activeClusterNodeType,
clusterProductUuid,
clusterHubUuid,
clusterSupplierUuid
)
// Server-side clustering (typed mode) // Server-side clustering (typed mode)
const offerClusters = useClusteredNodes(undefined, ref('offer')) const offerClusters = useClusteredNodes(undefined, ref('offer'), clusterProductUuid, clusterHubUuid, clusterSupplierUuid)
const hubClusters = useClusteredNodes(undefined, ref('logistics')) const hubClusters = useClusteredNodes(undefined, ref('logistics'), clusterProductUuid, clusterHubUuid, clusterSupplierUuid)
const supplierClusters = useClusteredNodes(undefined, ref('supplier')) const supplierClusters = useClusteredNodes(undefined, ref('supplier'), clusterProductUuid, clusterHubUuid, clusterSupplierUuid)
const clusteredPointsByType = computed(() => ({ const clusteredPointsByType = computed(() => ({
offer: offerClusters.clusteredNodes.value, offer: offerClusters.clusteredNodes.value,
@@ -341,6 +369,7 @@ const fetchActiveClusters = async () => {
// Refetch clusters when view mode changes // Refetch clusters when view mode changes
watch(mapViewMode, async () => { watch(mapViewMode, async () => {
if (!props.useServerClustering) return if (!props.useServerClustering) return
if (isInfoMode.value) return
if (useTypedClusters.value) { if (useTypedClusters.value) {
clearInactiveClusters(activeClusterType.value) clearInactiveClusters(activeClusterType.value)
if (currentBounds.value) { if (currentBounds.value) {
@@ -356,6 +385,17 @@ watch(mapViewMode, async () => {
} }
}) })
watch([clusterProductUuid, clusterHubUuid, clusterSupplierUuid], async () => {
if (!props.useServerClustering) return
if (isInfoMode.value) return
if (!currentBounds.value) return
if (useTypedClusters.value) {
await fetchActiveClusters()
return
}
await fetchClusters(currentBounds.value)
})
// Map refs // Map refs
const mapRef = ref<{ flyTo: (lat: number, lng: number, zoom?: number) => void } | null>(null) const mapRef = ref<{ flyTo: (lat: number, lng: number, zoom?: number) => void } | null>(null)

View File

@@ -206,9 +206,12 @@ export type QueryAutoRouteArgs = {
/** Root query. */ /** Root query. */
export type QueryClusteredNodesArgs = { export type QueryClusteredNodesArgs = {
east: Scalars['Float']['input']; east: Scalars['Float']['input'];
hubUuid?: InputMaybe<Scalars['String']['input']>;
nodeType?: InputMaybe<Scalars['String']['input']>; nodeType?: InputMaybe<Scalars['String']['input']>;
north: Scalars['Float']['input']; north: Scalars['Float']['input'];
productUuid?: InputMaybe<Scalars['String']['input']>;
south: Scalars['Float']['input']; south: Scalars['Float']['input'];
supplierUuid?: InputMaybe<Scalars['String']['input']>;
transportType?: InputMaybe<Scalars['String']['input']>; transportType?: InputMaybe<Scalars['String']['input']>;
west: Scalars['Float']['input']; west: Scalars['Float']['input'];
zoom: Scalars['Int']['input']; zoom: Scalars['Int']['input'];
@@ -479,6 +482,9 @@ export type GetClusteredNodesQueryVariables = Exact<{
zoom: Scalars['Int']['input']; zoom: Scalars['Int']['input'];
transportType?: InputMaybe<Scalars['String']['input']>; transportType?: InputMaybe<Scalars['String']['input']>;
nodeType?: InputMaybe<Scalars['String']['input']>; nodeType?: InputMaybe<Scalars['String']['input']>;
productUuid?: InputMaybe<Scalars['String']['input']>;
hubUuid?: InputMaybe<Scalars['String']['input']>;
supplierUuid?: InputMaybe<Scalars['String']['input']>;
}>; }>;
@@ -594,7 +600,7 @@ export type SuppliersListQueryResult = { __typename?: 'Query', suppliersList?: A
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<GetAutoRouteQueryResult, GetAutoRouteQueryVariables>; 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<GetAutoRouteQueryResult, GetAutoRouteQueryVariables>;
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<GetClusteredNodesQueryResult, GetClusteredNodesQueryVariables>; 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"}}},{"kind":"VariableDefinition","variable":{"kind":"Variable","name":{"kind":"Name","value":"productUuid"}},"type":{"kind":"NamedType","name":{"kind":"Name","value":"String"}}},{"kind":"VariableDefinition","variable":{"kind":"Variable","name":{"kind":"Name","value":"hubUuid"}},"type":{"kind":"NamedType","name":{"kind":"Name","value":"String"}}},{"kind":"VariableDefinition","variable":{"kind":"Variable","name":{"kind":"Name","value":"supplierUuid"}},"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"}}},{"kind":"Argument","name":{"kind":"Name","value":"productUuid"},"value":{"kind":"Variable","name":{"kind":"Name","value":"productUuid"}}},{"kind":"Argument","name":{"kind":"Name","value":"hubUuid"},"value":{"kind":"Variable","name":{"kind":"Name","value":"hubUuid"}}},{"kind":"Argument","name":{"kind":"Name","value":"supplierUuid"},"value":{"kind":"Variable","name":{"kind":"Name","value":"supplierUuid"}}}],"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<GetClusteredNodesQueryResult, GetClusteredNodesQueryVariables>;
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<GetHubCountriesQueryResult, GetHubCountriesQueryVariables>; 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<GetHubCountriesQueryResult, GetHubCountriesQueryVariables>;
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":"transportTypes"}}]}}]}}]} as unknown as DocumentNode<GetNodeQueryResult, GetNodeQueryVariables>; 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":"transportTypes"}}]}}]}}]} as unknown as DocumentNode<GetNodeQueryResult, GetNodeQueryVariables>;
export const GetRailRouteDocument = {"kind":"Document","definitions":[{"kind":"OperationDefinition","operation":"query","name":{"kind":"Name","value":"GetRailRoute"},"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":"railRoute"},"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<GetRailRouteQueryResult, GetRailRouteQueryVariables>; export const GetRailRouteDocument = {"kind":"Document","definitions":[{"kind":"OperationDefinition","operation":"query","name":{"kind":"Name","value":"GetRailRoute"},"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":"railRoute"},"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<GetRailRouteQueryResult, GetRailRouteQueryVariables>;

View File

@@ -11,7 +11,10 @@ export interface MapBounds {
export function useClusteredNodes( export function useClusteredNodes(
transportType?: Ref<string | undefined>, transportType?: Ref<string | undefined>,
nodeType?: Ref<string | undefined> nodeType?: Ref<string | undefined>,
productUuid?: Ref<string | undefined>,
hubUuid?: Ref<string | undefined>,
supplierUuid?: Ref<string | undefined>
) { ) {
const { client } = useApolloClient('publicGeo') const { client } = useApolloClient('publicGeo')
@@ -30,7 +33,10 @@ export function useClusteredNodes(
north: bounds.north, north: bounds.north,
zoom: Math.floor(bounds.zoom), zoom: Math.floor(bounds.zoom),
transportType: transportType?.value, transportType: transportType?.value,
nodeType: nodeType?.value nodeType: nodeType?.value,
productUuid: productUuid?.value,
hubUuid: hubUuid?.value,
supplierUuid: supplierUuid?.value
}, },
fetchPolicy: 'network-only' fetchPolicy: 'network-only'
}) })

View File

@@ -17,6 +17,12 @@
:info-loading="mapInfoLoading" :info-loading="mapInfoLoading"
:force-info-mode="forceInfoMode" :force-info-mode="forceInfoMode"
:hide-view-toggle="hideViewToggle" :hide-view-toggle="hideViewToggle"
:show-offers-toggle="showOffersToggle"
:show-hubs-toggle="showHubsToggle"
:show-suppliers-toggle="showSuppliersToggle"
:cluster-product-uuid="clusterProductUuid"
:cluster-hub-uuid="clusterHubUuid"
:cluster-supplier-uuid="clusterSupplierUuid"
@select="onMapSelect" @select="onMapSelect"
@bounds-change="onBoundsChange" @bounds-change="onBoundsChange"
@update:filter-by-bounds="$event ? setBoundsInUrl(currentMapBounds) : clearBoundsFromUrl()" @update:filter-by-bounds="$event ? setBoundsInUrl(currentMapBounds) : clearBoundsFromUrl()"
@@ -34,7 +40,6 @@
:loading-more="selectionLoadingMore" :loading-more="selectionLoadingMore"
:has-more="selectionHasMore && !filterByBounds" :has-more="selectionHasMore && !filterByBounds"
@select="onSelectItem" @select="onSelectItem"
@pin="onPinItem"
@close="onClosePanel" @close="onClosePanel"
@load-more="onLoadMore" @load-more="onLoadMore"
@hover="onHoverItem" @hover="onHoverItem"
@@ -57,8 +62,6 @@
:loading-suppliers="isLoadingSuppliers" :loading-suppliers="isLoadingSuppliers"
:loading-offers="isLoadingOffers" :loading-offers="isLoadingOffers"
@close="onInfoClose" @close="onInfoClose"
@add-to-filter="onInfoAddToFilter"
@pin="onPinItem"
@open-info="onInfoOpenRelated" @open-info="onInfoOpenRelated"
@select-product="onInfoSelectProduct" @select-product="onInfoSelectProduct"
@select-offer="onSelectOffer" @select-offer="onSelectOffer"
@@ -312,6 +315,16 @@ watch(productId, (newProductId) => {
setSupplierProductFilter(newProductId || null) setSupplierProductFilter(newProductId || null)
}, { immediate: true }) }, { immediate: true })
// If a filter locks a view type, switch away from that view
watch([hubId, supplierId], ([newHubId, newSupplierId]) => {
if (newHubId && mapViewMode.value === 'hubs') {
setMapViewMode('offers')
}
if (newSupplierId && mapViewMode.value === 'suppliers') {
setMapViewMode('offers')
}
}, { immediate: true })
// Apply bounds filter when "filter by map bounds" is enabled // Apply bounds filter when "filter by map bounds" is enabled
// Only watch URL bounds - currentMapBounds changes too often (every map move) // Only watch URL bounds - currentMapBounds changes too often (every map move)
watch([filterByBounds, urlBounds], ([enabled, urlB]) => { watch([filterByBounds, urlBounds], ([enabled, urlB]) => {
@@ -474,6 +487,14 @@ const mapInfoLoading = computed(() =>
const forceInfoMode = computed(() => showQuoteResults.value) const forceInfoMode = computed(() => showQuoteResults.value)
const hideViewToggle = computed(() => showQuoteResults.value) const hideViewToggle = computed(() => showQuoteResults.value)
const showOffersToggle = computed(() => true)
const showHubsToggle = computed(() => !hubId.value)
const showSuppliersToggle = computed(() => !supplierId.value)
const clusterProductUuid = computed(() => productId.value || undefined)
const clusterHubUuid = computed(() => hubId.value || undefined)
const clusterSupplierUuid = computed(() => supplierId.value || undefined)
// Show panel when selecting OR when showing info OR when showing quote results // Show panel when selecting OR when showing info OR when showing quote results
const showPanel = computed(() => { const showPanel = computed(() => {
return selectMode.value !== null || infoId.value !== null || showQuoteResults.value return selectMode.value !== null || infoId.value !== null || showQuoteResults.value
@@ -564,12 +585,6 @@ const onSelectItem = (type: string, item: { uuid?: string | null; name?: string
} }
} }
const onPinItem = (type: string, item: { uuid?: string | null; name?: string | null }) => {
if (item.uuid && item.name) {
selectItem(type, item.uuid, item.name)
}
}
// Close panel (cancel select mode) // Close panel (cancel select mode)
const onClosePanel = () => { const onClosePanel = () => {
cancelSelect() cancelSelect()
@@ -581,30 +596,6 @@ const onInfoClose = () => {
clearInfo() clearInfo()
} }
const onInfoAddToFilter = () => {
if (!infoId.value || !entity.value) return
const { type, uuid } = infoId.value
// For offers, add the product AND hub to filter
if (type === 'offer') {
if (entity.value.productUuid) {
const productName = entity.value.productName || entity.value.name || uuid.slice(0, 8) + '...'
selectItem('product', entity.value.productUuid, productName)
}
// Also add hub (location) to filter if available
if (entity.value.locationUuid) {
const hubName = entity.value.locationName || entity.value.locationUuid.slice(0, 8) + '...'
selectItem('hub', entity.value.locationUuid, hubName)
}
} else {
// For hubs and suppliers, add directly
const name = entity.value.name || uuid.slice(0, 8) + '...'
selectItem(type, uuid, name)
}
closeInfo()
clearInfo()
}
const onInfoOpenRelated = (type: 'hub' | 'supplier' | 'offer', uuid: string) => { const onInfoOpenRelated = (type: 'hub' | 'supplier' | 'offer', uuid: string) => {
openInfo(type, uuid) openInfo(type, uuid)

View File

@@ -6,6 +6,9 @@ query GetClusteredNodes(
$zoom: Int! $zoom: Int!
$transportType: String $transportType: String
$nodeType: String $nodeType: String
$productUuid: String
$hubUuid: String
$supplierUuid: String
) { ) {
clusteredNodes( clusteredNodes(
west: $west west: $west
@@ -15,6 +18,9 @@ query GetClusteredNodes(
zoom: $zoom zoom: $zoom
transportType: $transportType transportType: $transportType
nodeType: $nodeType nodeType: $nodeType
productUuid: $productUuid
hubUuid: $hubUuid
supplierUuid: $supplierUuid
) { ) {
id id
latitude latitude