diff --git a/client/src/components/gps/GpsMap.tsx b/client/src/components/gps/GpsMap.tsx index 6b98d53..83a5478 100644 --- a/client/src/components/gps/GpsMap.tsx +++ b/client/src/components/gps/GpsMap.tsx @@ -26,6 +26,10 @@ export interface GpsMapMarker { label?: string; /** Вторая строка подписи (заказчик) */ label2?: string; + /** Участвовать в кластеризации (кроме kind vehicle/person) */ + clusterable?: boolean; + /** Название для списка кластера (когда label не задан) */ + clusterLabel?: string; /** Приглушённый маркер для offline-объектов */ offline?: boolean; /** Примерная позиция (LBS по вышкам, нет GPS): полупрозрачный маркер с пунктирной обводкой */ diff --git a/client/src/components/gps/OsmMap.tsx b/client/src/components/gps/OsmMap.tsx index 0d0e4e4..9db4caa 100644 --- a/client/src/components/gps/OsmMap.tsx +++ b/client/src/components/gps/OsmMap.tsx @@ -175,7 +175,9 @@ export function OsmMap({ zIndexOffset: m.zIndex ?? 0, gpsMarkerId: m.id, } as GpsMarkerOptions); - if (m.onClick) { + // Клик по маркеру: если задан popup — открываем его (показ названия), + // onClick вызывается только из элементов внутри popup (делегирование) + if (m.onClick && !m.popup) { leafletMarker.on('click', () => { markersByIdRef.current.get(String(m.id))?.onClick?.(); }); @@ -185,7 +187,7 @@ export function OsmMap({ leafletMarker.bindPopup(m.popup); } // Кластерим только объекты; dot-маркеры и noCluster — в обычный слой - const clusterable = (m.kind === 'vehicle' || m.kind === 'person') && !m.noCluster; + const clusterable = ((m.kind === 'vehicle' || m.kind === 'person') || m.clusterable === true) && !m.noCluster; if (clusterable) { leafletMarker.addTo(clusterLayer); } else { diff --git a/client/src/components/gps/Yandex21Map.tsx b/client/src/components/gps/Yandex21Map.tsx index 73b38eb..eb9324c 100644 --- a/client/src/components/gps/Yandex21Map.tsx +++ b/client/src/components/gps/Yandex21Map.tsx @@ -260,7 +260,9 @@ export function Yandex21Map({ ...(m.zIndex !== undefined ? { zIndex: m.zIndex } : {}), } ); - if (m.onClick) { + // Клик по маркеру: если задан popup (balloonContent) — открываем его, + // onClick вызывается только из элементов внутри balloon (делегирование) + if (m.onClick && !m.popup) { placemark.events.add('click', () => { markersByIdRef.current.get(String(m.id))?.onClick?.(); }); @@ -271,7 +273,7 @@ export function Yandex21Map({ for (const m of markers) { // Кластерим только объекты (vehicle/person); dot-маркеры и noCluster — // в обычную коллекцию (точки трека, старт/финиш, вершины геозон) - const clusterable = (m.kind === 'vehicle' || m.kind === 'person') && !m.noCluster; + const clusterable = ((m.kind === 'vehicle' || m.kind === 'person') || m.clusterable === true) && !m.noCluster; const placemark = createPlacemark(m); placemarksRef.current.set(String(m.id), placemark); if (clusterable) { diff --git a/client/src/components/gps/marker-html.ts b/client/src/components/gps/marker-html.ts index fa09507..58bc1b4 100644 --- a/client/src/components/gps/marker-html.ts +++ b/client/src/components/gps/marker-html.ts @@ -121,7 +121,7 @@ export function gpsClusterListHtml(markers: GpsMapMarker[]): string { .map( (m) => `` ) .join(''); diff --git a/client/src/pages/gps/MapTab.tsx b/client/src/pages/gps/MapTab.tsx index f304bdd..332d9d9 100644 --- a/client/src/pages/gps/MapTab.tsx +++ b/client/src/pages/gps/MapTab.tsx @@ -228,10 +228,15 @@ export function GpsMapTab({ onShowRoute }: { onShowRoute: (assetId: number) => v const taskMarkers: GpsMapMarker[] = useMemo(() => { if (!tasksLayer || mapFieldId === null) return []; + const esc = (t: string) => t.replace(/&/g, '&').replace(//g, '>'); const markers: GpsMapMarker[] = []; for (const t of tasksData?.tasks ?? []) { const geo = parseGeo(t.fieldValues?.[mapFieldId]); if (!geo) continue; + const st = shortTitle(t.title); + // Попап по клику: название (две строки) + кнопка «Открыть задачу». + // Работает и при выключенных подписях — название видно по клику. + const popup = `
${esc(st.line1)}${st.line2 ? `
${esc(st.line2)}
` : ''}
`; markers.push({ id: `task-${t.id}`, lat: geo.lat, @@ -240,10 +245,14 @@ export function GpsMapTab({ onShowRoute }: { onShowRoute: (assetId: number) => v color: '#8b5cf6', // Подпись — только при включённом режиме «Названия», в две строки ...(showLabels - ? (() => { const st = shortTitle(t.title); return { label: st.line1, ...(st.line2 ? { label2: st.line2 } : {}) }; })() + ? { label: st.line1, ...(st.line2 ? { label2: st.line2 } : {}) } : {}), - noCluster: true, + // Кластеризация как у машин: при отдалении — кружок с числом, + // клик по кластеру — вертикальный список названий + clusterable: true, + clusterLabel: t.title, zIndex: 40, + popup, onClick: rulerMode ? undefined : () => setLocation(`/forms/${mapFormId}/tasks/${t.id}`), }); }