diff --git a/app/components/catalog/CatalogMap.vue b/app/components/catalog/CatalogMap.vue index 15c4770..a77017a 100644 --- a/app/components/catalog/CatalogMap.vue +++ b/app/components/catalog/CatalogMap.vue @@ -51,6 +51,7 @@ const props = withDefaults(defineProps<{ entityType?: 'offer' | 'hub' | 'supplier' initialCenter?: [number, number] initialZoom?: number + infoLoading?: boolean relatedPoints?: Array<{ uuid: string name: string @@ -64,6 +65,7 @@ const props = withDefaults(defineProps<{ initialCenter: () => [37.64, 55.76], initialZoom: 2, useServerClustering: false, + infoLoading: false, items: () => [], clusteredPoints: () => [], relatedPoints: () => [] @@ -693,11 +695,8 @@ watch(() => props.hoveredItem, () => { } }, { deep: true }) -// Debounced fitBounds to avoid camera jumping when points load incrementally -let fitBoundsTimeout: ReturnType | null = null - -// Update related points layer when relatedPoints changes + fit bounds (debounced) -watch(() => props.relatedPoints, (points) => { +// Update related points layer when relatedPoints changes +watch(() => props.relatedPoints, () => { if (!mapRef.value || !mapInitialized.value) return // Update the source data immediately @@ -705,26 +704,23 @@ watch(() => props.relatedPoints, (points) => { if (source) { source.setData(relatedPointsGeoJson.value) } - - // Debounce fitBounds - wait for all points to load before zooming - if (fitBoundsTimeout) { - clearTimeout(fitBoundsTimeout) - } - - if (points && points.length > 0) { - fitBoundsTimeout = setTimeout(() => { - if (!mapRef.value) return - const bounds = new LngLatBounds() - points.forEach(p => { - bounds.extend([p.longitude, p.latitude]) - }) - if (!bounds.isEmpty()) { - mapRef.value.fitBounds(bounds, { padding: 80, maxZoom: 12 }) - } - }, 300) // Wait 300ms for all points to load - } }, { deep: true }) +// Fit bounds when info loading finishes (all related data loaded) +watch(() => props.infoLoading, (loading, wasLoading) => { + // Only fit bounds when loading changes from true to false (data finished loading) + if (wasLoading && !loading && props.relatedPoints && props.relatedPoints.length > 0) { + if (!mapRef.value) return + const bounds = new LngLatBounds() + props.relatedPoints.forEach(p => { + bounds.extend([p.longitude, p.latitude]) + }) + if (!bounds.isEmpty()) { + mapRef.value.fitBounds(bounds, { padding: 80, maxZoom: 12 }) + } + } +}) + // Watch for pointColor or entityType changes - update colors and icons watch([() => props.pointColor, () => props.entityType], async ([newColor, newType]) => { if (!mapRef.value || !mapInitialized.value) return diff --git a/app/components/page/CatalogPage.vue b/app/components/page/CatalogPage.vue index 9e1b013..595cf6d 100644 --- a/app/components/page/CatalogPage.vue +++ b/app/components/page/CatalogPage.vue @@ -24,6 +24,7 @@ :hovered-item-id="hoveredId" :hovered-item="hoveredItem" :related-points="relatedPoints" + :info-loading="infoLoading" @select-item="onMapSelect" @bounds-change="onBoundsChange" /> @@ -250,6 +251,7 @@ const props = withDefaults(defineProps<{ items?: MapItem[] showPanel?: boolean filterByBounds?: boolean + infoLoading?: boolean relatedPoints?: Array<{ uuid: string name: string @@ -266,6 +268,7 @@ const props = withDefaults(defineProps<{ items: () => [], showPanel: false, filterByBounds: false, + infoLoading: false, relatedPoints: () => [] }) diff --git a/app/pages/catalog/index.vue b/app/pages/catalog/index.vue index e8b25fe..5d03303 100644 --- a/app/pages/catalog/index.vue +++ b/app/pages/catalog/index.vue @@ -11,6 +11,7 @@ :show-panel="showPanel" :filter-by-bounds="filterByBounds" :related-points="relatedPoints" + :info-loading="isInfoLoading" @select="onMapSelect" @bounds-change="onBoundsChange" @update:filter-by-bounds="$event ? setBoundsInUrl(currentMapBounds) : clearBoundsFromUrl()" @@ -356,6 +357,11 @@ watch(searchTrigger, () => { // Loading state const isLoading = computed(() => offersLoading.value || selectionLoading.value) +// Info loading state for map fitBounds (true while any info data is still loading) +const isInfoLoading = computed(() => + infoLoading.value || isLoadingProducts.value || isLoadingHubs.value || isLoadingSuppliers.value || isLoadingOffers.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