From 6c1cea39b49ec901a527cbcb04e95b715f68dd9b Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?=D0=98=D0=BB=D1=8C=D1=8F=D1=81=20=D0=A1=D1=83=D0=BB=D1=82?= =?UTF-8?q?=D0=B0=D0=BD=D0=BE=D0=B2?= Date: Wed, 23 Sep 2026 08:49:15 +0300 Subject: [PATCH] =?UTF-8?q?fix(gps):=20=D0=BA=D0=BB=D0=B8=D0=BA=20=D0=BF?= =?UTF-8?q?=D0=BE=20=D1=82=D0=BE=D1=87=D0=BA=D0=B5=20=D0=BE=D1=82=D0=BA?= =?UTF-8?q?=D1=80=D1=8B=D0=B2=D0=B0=D0=B5=D1=82=20=D0=BF=D0=BE=D0=BF=D0=B0?= =?UTF-8?q?=D0=BF=20=D1=81=20=D0=BD=D0=B0=D0=B7=D0=B2=D0=B0=D0=BD=D0=B8?= =?UTF-8?q?=D0=B5=D0=BC=20(=D0=B4=D0=B5=D0=BB=D0=B5=D0=B3=D0=B8=D1=80?= =?UTF-8?q?=D0=BE=D0=B2=D0=B0=D0=BD=D0=B8=D0=B5=20=D1=82=D0=BE=D0=BB=D1=8C?= =?UTF-8?q?=D0=BA=D0=BE=20=D0=BF=D0=BE=20data-gps-action);=20=D1=81=D0=BF?= =?UTF-8?q?=D0=B0=D0=B9=D0=B4=D0=B5=D1=80=D1=84=D0=B0=D0=B9=20=D0=BA=D0=BB?= =?UTF-8?q?=D0=B0=D1=81=D1=82=D0=B5=D1=80=D0=BE=D0=B2=20=D0=B2=20=D0=BE?= =?UTF-8?q?=D0=B4=D0=BD=D0=BE=D0=B9=20=D0=BA=D0=BE=D0=BE=D1=80=D0=B4=D0=B8?= =?UTF-8?q?=D0=BD=D0=B0=D1=82=D0=B5=20=D0=BD=D0=B0=20max=20zoom?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- client/src/components/gps/OsmMap.tsx | 9 ++++++--- client/src/components/gps/Yandex21Map.tsx | 6 ++++-- client/src/components/gps/marker-html.ts | 9 +++++---- client/src/pages/gps/MapTab.tsx | 2 +- 4 files changed, 16 insertions(+), 10 deletions(-) diff --git a/client/src/components/gps/OsmMap.tsx b/client/src/components/gps/OsmMap.tsx index 9db4caa..9ff1a3e 100644 --- a/client/src/components/gps/OsmMap.tsx +++ b/client/src/components/gps/OsmMap.tsx @@ -84,7 +84,8 @@ export function OsmMap({ iconCreateFunction: (cluster) => L.divIcon({ html: gpsClusterHtml(cluster.getChildCount()), className: '', iconSize: [0, 0] }), zoomToBoundsOnClick: false, - spiderfyOnMaxZoom: false, + // Точки в одной координате («3») на max zoom раскрываются «паучком» + spiderfyOnMaxZoom: true, showCoverageOnHover: false, }).addTo(map); clusterLayerRef.current = clusterLayer; @@ -113,9 +114,11 @@ export function OsmMap({ // Делегированный клик по пункту списка кластера в popup // (контент popup рендерится внутри контейнера карты) map.getContainer().addEventListener('click', (ev) => { - const el = (ev.target as HTMLElement | null)?.closest?.('[data-gps-marker-id]') as HTMLElement | null; + const el = (ev.target as HTMLElement | null)?.closest?.('[data-gps-action]') as HTMLElement | null; if (!el) return; - const marker = markersByIdRef.current.get(el.dataset.gpsMarkerId || ''); + const action = el.dataset.gpsAction || ''; + if (!action.startsWith('open:')) return; + const marker = markersByIdRef.current.get(action.slice(5)); if (marker?.onClick) { marker.onClick(); map.closePopup(); diff --git a/client/src/components/gps/Yandex21Map.tsx b/client/src/components/gps/Yandex21Map.tsx index eb9324c..1e73ab8 100644 --- a/client/src/components/gps/Yandex21Map.tsx +++ b/client/src/components/gps/Yandex21Map.tsx @@ -170,9 +170,11 @@ export function Yandex21Map({ const container = containerRef.current; if (!mapReady || !container) return; const handler = (ev: MouseEvent) => { - const el = (ev.target as HTMLElement | null)?.closest?.('[data-gps-marker-id]') as HTMLElement | null; + const el = (ev.target as HTMLElement | null)?.closest?.('[data-gps-action]') as HTMLElement | null; if (!el) return; - const marker = markersByIdRef.current.get(el.dataset.gpsMarkerId || ''); + const action = el.dataset.gpsAction || ''; + if (!action.startsWith('open:')) return; + const marker = markersByIdRef.current.get(action.slice(5)); if (marker?.onClick) { marker.onClick(); mapRef.current?.balloon.close(); diff --git a/client/src/components/gps/marker-html.ts b/client/src/components/gps/marker-html.ts index 58bc1b4..84fee0c 100644 --- a/client/src/components/gps/marker-html.ts +++ b/client/src/components/gps/marker-html.ts @@ -94,9 +94,10 @@ export function gpsMarkerHtml(marker: GpsMapMarker): string { } } - // data-gps-marker-id: делегированный обработчик контейнера карты ловит клики - // по любому маркеру (надёжнее нативных событий placemark/divIcon) - return `
${body}${label}
`; + // data-* атрибута на корне НЕТ намеренно: делегированный обработчик ловит + // только кнопки с data-gps-action (список кластера, «Открыть задачу» в попапе), + // иначе клик по точке сразу вызывал onClick и глушил открытие попапа + return `
${body}${label}
`; } /** @@ -119,7 +120,7 @@ export function gpsClusterHtml(count: number | string): string { export function gpsClusterListHtml(markers: GpsMapMarker[]): string { const rows = markers .map( - (m) => `` diff --git a/client/src/pages/gps/MapTab.tsx b/client/src/pages/gps/MapTab.tsx index 332d9d9..2e83b35 100644 --- a/client/src/pages/gps/MapTab.tsx +++ b/client/src/pages/gps/MapTab.tsx @@ -236,7 +236,7 @@ export function GpsMapTab({ onShowRoute }: { onShowRoute: (assetId: number) => v const st = shortTitle(t.title); // Попап по клику: название (две строки) + кнопка «Открыть задачу». // Работает и при выключенных подписях — название видно по клику. - const popup = `
${esc(st.line1)}${st.line2 ? `
${esc(st.line2)}
` : ''}
`; + const popup = `
${esc(st.line1)}${st.line2 ? `
${esc(st.line2)}
` : ''}
`; markers.push({ id: `task-${t.id}`, lat: geo.lat,