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"
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
:product="product"
compact
@@ -191,13 +184,6 @@
:key="supplier.uuid ?? index"
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
:supplier="supplier"
selectable
@@ -235,13 +221,6 @@
:key="hub.uuid ?? index"
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
:hub="hub"
:origin="originCoords"
@@ -267,13 +246,6 @@
:key="hub.uuid ?? index"
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
:hub="hub"
:origin="originCoords"
@@ -286,11 +258,6 @@
</div>
</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>
@@ -326,8 +293,6 @@ const props = defineProps<{
const emit = defineEmits<{
'close': []
'add-to-filter': []
'pin': [type: 'product' | 'supplier' | 'hub', item: { uuid?: string | null; name?: string | null }]
'open-info': [type: InfoEntityType, uuid: string]
'select-product': [uuid: string | null]
'select-offer': [offer: { uuid: string; productUuid?: string | null }]

View File

@@ -31,13 +31,6 @@
@mouseenter="emit('hover', item.uuid ?? 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
:product="item"
selectable
@@ -56,13 +49,6 @@
@mouseenter="emit('hover', item.uuid ?? 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
:hub="item"
selectable
@@ -80,13 +66,6 @@
@mouseenter="emit('hover', item.uuid ?? 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
:supplier="item"
selectable
@@ -129,7 +108,6 @@ const props = defineProps<{
const emit = defineEmits<{
'select': [type: string, item: Item]
'pin': [type: string, item: Item]
'close': []
'load-more': []
'hover': [uuid: string | null]
@@ -191,10 +169,4 @@ const onSelect = (item: Item) => {
emit('select', props.selectMode, item)
}
}
const onPin = (item: Item) => {
if (props.selectMode && item.uuid) {
emit('pin', props.selectMode, item)
}
}
</script>

View File

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

View File

@@ -206,9 +206,12 @@ export type QueryAutoRouteArgs = {
/** Root query. */
export type QueryClusteredNodesArgs = {
east: Scalars['Float']['input'];
hubUuid?: InputMaybe<Scalars['String']['input']>;
nodeType?: InputMaybe<Scalars['String']['input']>;
north: Scalars['Float']['input'];
productUuid?: InputMaybe<Scalars['String']['input']>;
south: Scalars['Float']['input'];
supplierUuid?: InputMaybe<Scalars['String']['input']>;
transportType?: InputMaybe<Scalars['String']['input']>;
west: Scalars['Float']['input'];
zoom: Scalars['Int']['input'];
@@ -479,6 +482,9 @@ export type GetClusteredNodesQueryVariables = Exact<{
zoom: Scalars['Int']['input'];
transportType?: 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 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 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>;

View File

@@ -11,7 +11,10 @@ export interface MapBounds {
export function useClusteredNodes(
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')
@@ -30,7 +33,10 @@ export function useClusteredNodes(
north: bounds.north,
zoom: Math.floor(bounds.zoom),
transportType: transportType?.value,
nodeType: nodeType?.value
nodeType: nodeType?.value,
productUuid: productUuid?.value,
hubUuid: hubUuid?.value,
supplierUuid: supplierUuid?.value
},
fetchPolicy: 'network-only'
})

View File

@@ -17,6 +17,12 @@
:info-loading="mapInfoLoading"
:force-info-mode="forceInfoMode"
: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"
@bounds-change="onBoundsChange"
@update:filter-by-bounds="$event ? setBoundsInUrl(currentMapBounds) : clearBoundsFromUrl()"
@@ -34,7 +40,6 @@
:loading-more="selectionLoadingMore"
:has-more="selectionHasMore && !filterByBounds"
@select="onSelectItem"
@pin="onPinItem"
@close="onClosePanel"
@load-more="onLoadMore"
@hover="onHoverItem"
@@ -57,8 +62,6 @@
:loading-suppliers="isLoadingSuppliers"
:loading-offers="isLoadingOffers"
@close="onInfoClose"
@add-to-filter="onInfoAddToFilter"
@pin="onPinItem"
@open-info="onInfoOpenRelated"
@select-product="onInfoSelectProduct"
@select-offer="onSelectOffer"
@@ -312,6 +315,16 @@ watch(productId, (newProductId) => {
setSupplierProductFilter(newProductId || null)
}, { 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
// Only watch URL bounds - currentMapBounds changes too often (every map move)
watch([filterByBounds, urlBounds], ([enabled, urlB]) => {
@@ -474,6 +487,14 @@ const mapInfoLoading = computed(() =>
const forceInfoMode = 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
const showPanel = computed(() => {
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)
const onClosePanel = () => {
cancelSelect()
@@ -581,30 +596,6 @@ const onInfoClose = () => {
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) => {
openInfo(type, uuid)