From aa7790f45ec272ca781283e424d6e2b31a6e6a66 Mon Sep 17 00:00:00 2001 From: Ruslan Bakiev <572431+veikab@users.noreply.github.com> Date: Fri, 6 Feb 2026 20:07:43 +0700 Subject: [PATCH] feat(catalog): focus map during quote search --- app/components/page/CatalogPage.vue | 6 ++- app/pages/catalog/index.vue | 66 ++++++++++++++++++++++++++++- 2 files changed, 68 insertions(+), 4 deletions(-) diff --git a/app/components/page/CatalogPage.vue b/app/components/page/CatalogPage.vue index 20ca68c..6a9eaf4 100644 --- a/app/components/page/CatalogPage.vue +++ b/app/components/page/CatalogPage.vue @@ -18,7 +18,7 @@ :map-id="mapId" :items="isInfoMode ? [] : (useServerClustering ? [] : itemsWithCoords)" :clustered-points="isInfoMode ? [] : (useServerClustering && !useTypedClusters ? clusteredNodes : [])" - :clustered-points-by-type="isInfoMode ? undefined : (useServerClustering && useTypedClusters ? clusteredPointsByType : undefined)" + :clustered-points-by-type="isInfoMode ? undefined : (useServerClustering && useTypedClusters ? clusteredPointsByType : undefined)" :use-server-clustering="useServerClustering && !isInfoMode" :point-color="activePointColor" :entity-type="activeEntityType" @@ -255,6 +255,7 @@ const props = withDefaults(defineProps<{ showPanel?: boolean filterByBounds?: boolean infoLoading?: boolean + forceInfoMode?: boolean panelWidth?: string hideViewToggle?: boolean relatedPoints?: Array<{ @@ -275,6 +276,7 @@ const props = withDefaults(defineProps<{ showPanel: false, filterByBounds: false, infoLoading: false, + forceInfoMode: false, panelWidth: 'w-96', hideViewToggle: false, relatedPoints: () => [] @@ -364,7 +366,7 @@ const selectedMapItem = ref(null) const mobilePanelExpanded = ref(false) // Info mode - when relatedPoints are present, hide clusters and show only related points -const isInfoMode = computed(() => props.relatedPoints && props.relatedPoints.length > 0) +const isInfoMode = computed(() => props.forceInfoMode || (props.relatedPoints && props.relatedPoints.length > 0)) // Hovered item with coordinates for map highlight const hoveredItem = computed(() => { diff --git a/app/pages/catalog/index.vue b/app/pages/catalog/index.vue index f5b1652..941e075 100644 --- a/app/pages/catalog/index.vue +++ b/app/pages/catalog/index.vue @@ -14,7 +14,9 @@ :show-panel="showPanel && !kycSheetUuid" :filter-by-bounds="filterByBounds" :related-points="relatedPoints" - :info-loading="isInfoLoading" + :info-loading="mapInfoLoading" + :force-info-mode="forceInfoMode" + :hide-view-toggle="hideViewToggle" @select="onMapSelect" @bounds-change="onBoundsChange" @update:filter-by-bounds="$event ? setBoundsInUrl(currentMapBounds) : clearBoundsFromUrl()" @@ -131,6 +133,7 @@ const toMapItems = { + if (showQuoteResults.value) return [] if (selectMode.value === 'product') return [] // Products don't have coordinates if (selectMode.value === 'hub') return toMapItems(filteredHubs.value) if (selectMode.value === 'supplier') return toMapItems(filteredSuppliers.value) @@ -340,7 +343,7 @@ watch(infoProduct, async (productUuid) => { }) // Related points for Info mode (shown on map) - show current entity + all related entities -const relatedPoints = computed(() => { +const infoRelatedPoints = computed(() => { if (!infoId.value) return [] const points: Array<{ @@ -394,6 +397,50 @@ const relatedPoints = computed(() => { return points }) +// Related points for Quote mode (shown on map) +const searchHubPoint = ref(null) + +const searchOfferPoints = computed(() => + offers.value + .filter((offer) => offer.latitude != null && offer.longitude != null) + .map((offer) => ({ + uuid: offer.uuid, + name: offer.productName || '', + latitude: Number(offer.latitude), + longitude: Number(offer.longitude), + type: 'offer' as const + })) +) + +const searchRelatedPoints = computed(() => { + const points: Array<{ + uuid: string + name: string + latitude: number + longitude: number + type: 'hub' | 'supplier' | 'offer' + }> = [] + + if (searchHubPoint.value) { + points.push({ + uuid: searchHubPoint.value.uuid, + name: searchHubPoint.value.name, + latitude: searchHubPoint.value.latitude, + longitude: searchHubPoint.value.longitude, + type: 'hub' + }) + } + + searchOfferPoints.value.forEach((point) => points.push(point)) + return points +}) + +const relatedPoints = computed(() => { + if (infoId.value) return infoRelatedPoints.value + if (showQuoteResults.value) return searchRelatedPoints.value + return [] +}) + // Offers data for quote results const offers = ref([]) const quoteCalculations = ref([]) @@ -420,6 +467,13 @@ const isInfoLoading = computed(() => infoLoading.value || isLoadingProducts.value || isLoadingHubs.value || isLoadingSuppliers.value || isLoadingOffers.value ) +const mapInfoLoading = computed(() => + isInfoLoading.value || (showQuoteResults.value && offersLoading.value) +) + +const forceInfoMode = computed(() => showQuoteResults.value) +const hideViewToggle = computed(() => showQuoteResults.value) + // 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 @@ -581,6 +635,7 @@ const onSearch = async () => { offersLoading.value = true showQuoteResults.value = true + searchHubPoint.value = null try { // Prefer geo-based offers with routes when hub + product are selected @@ -588,6 +643,12 @@ const onSearch = async () => { const hubData = await execute(GetNodeDocument, { uuid: hubId.value }, 'public', 'geo') const hub = hubData?.node if (hub?.latitude != null && hub?.longitude != null) { + searchHubPoint.value = { + uuid: hub.uuid, + name: hub.name || hub.uuid, + latitude: Number(hub.latitude), + longitude: Number(hub.longitude) + } try { const calcData = await execute( QuoteCalculationsDocument, @@ -647,6 +708,7 @@ const onSearch = async () => { quoteCalculations.value = [] } } else { + searchHubPoint.value = null const vars: GetOffersQueryVariables = {} if (productId.value) vars.productUuid = productId.value if (supplierId.value) vars.teamUuid = supplierId.value