// components/mainComponent/MapComponent.js import React, { useEffect, useRef, useState, useCallback } from "react"; import L from "leaflet"; import "leaflet/dist/leaflet.css"; import "leaflet-contextmenu/dist/leaflet.contextmenu.css"; import "leaflet-contextmenu"; import "leaflet.smooth_marker_bouncing"; import "react-toastify/dist/ReactToastify.css"; import { Icon } from "@iconify/react"; import EditIcon from "@/components/icons/material-symbols/EditIcon"; import EditOffIcon from "@/components/icons/material-symbols/EditOffIcon"; import SearchIcon from "@/components/icons/material-symbols/SearchIcon"; import MenuIcon from "@/components/icons/material-symbols/MenuIcon"; import InfoIcon from "@/components/icons/material-symbols/InfoIcon"; import AlarmIcon from "@/components/icons/material-symbols/AlarmIcon"; import MapMarkerIcon from "@/components/icons/material-symbols/MapMarkerIcon"; import ExpandIcon from "@/components/icons/material-symbols/ExpandIcon"; import PlusIcon from "@/components/icons/material-symbols/PlusIcon"; import MinusIcon from "@/components/icons/material-symbols/MinusIcon"; import PoiUpdateModal from "@/components/pois/poiUpdateModal/PoiUpdateModal.js"; import { ToastContainer, toast } from "react-toastify"; import plusRoundIcon from "../icons/devices/overlapping/PlusRoundIcon.js"; import StartIcon from "@/components/gisPolylines/icons/StartIcon.js"; import EndIcon from "@/components/gisPolylines/icons/EndIcon.js"; import CircleIcon from "@/components/gisPolylines/icons/CircleIcon.js"; import { restoreMapSettings, checkOverlappingMarkers } from "../../utils/mapUtils.js"; import addItemsToMapContextMenu from "@/components/contextmenu/useMapContextMenu.js"; import useAreaMarkersLayer from "@/components/area/hooks/useAreaMarkersLayer.js"; import { setupPolylines } from "@/utils/polylines/setupPolylines.js"; import { setupPOIs } from "@/utils/setupPOIs.js"; import useLineData from "@/components/gisPolylines/tooltip/useLineData.js"; import { useMapComponentState } from "@/components/hooks/useMapComponentState.js"; import CoordinatePopup from "@/components/contextmenu/CoordinatePopup.js"; //----------Ui Widgets---------------- import MapLayersControlPanel from "@/components/uiWidgets/mapLayersControlPanel/MapLayersControlPanel.js"; import AlarmIndicator from "@/components/uiWidgets/AlarmIndicator"; import CoordinateInput from "@/components/uiWidgets/CoordinateInput.js"; import VersionInfoModal from "@/components/uiWidgets/VersionInfoModal.js"; import AreaDropdown from "@/components/uiWidgets/AreaDropdown"; //----------Daten aus API-------------------- import { fetchPoiDataService } from "@/services/database/pois/fetchPoiDataByIdService.js"; import AddPOIModal from "@/components/pois/AddPOIModal.js"; import { enablePolylineEvents, disablePolylineEvents } from "@/utils/polylines/eventHandlers"; //----------MapComponent.js hooks-------------------- import useInitializeMap from "@/components/mainComponent/hooks/useInitializeMap"; //-------------------Redux-------------------- import { useSelector, useDispatch } from "react-redux"; //-------------------Redux-Slices-------------------- import { setSelectedPoi } from "@/redux/slices/database/pois/selectedPoiSlice.js"; import { setDisabled } from "@/redux/slices/database/polylines/polylineEventsDisabledSlice.js"; import { setMapId, setUserId } from "@/redux/slices/urlParameterSlice"; import { selectMapLayersState, setLayerVisibility } from "@/redux/slices/mapLayersSlice"; import { setSelectedArea } from "@/redux/slices/selectedAreaSlice"; import { incrementZoomTrigger } from "@/redux/slices/zoomTriggerSlice"; import { setCurrentPoi } from "@/redux/slices/database/pois/currentPoiSlice.js"; import { selectGisLines } from "@/redux/slices/database/polylines/gisLinesSlice"; import { selectGisLinesStatus } from "@/redux/slices/webservice/gisLinesStatusSlice"; import { selectPoiTypData, selectPoiTypStatus } from "@/redux/slices/database/pois/poiTypSlice"; import { selectPriorityConfig } from "@/redux/slices/database/priorityConfigSlice.js"; import { selectPoiIconsData, selectPoiIconsStatus, } from "@/redux/slices/database/pois/poiIconsDataSlice"; import { selectGisLinesStatusFromWebservice } from "@/redux/slices/webservice/gisLinesStatusSlice"; import { selectGisUserRightsFromWebservice } from "@/redux/slices/webservice/userRightsSlice"; import { updateCountdown, closePolylineContextMenu, } from "@/redux/slices/database/polylines/polylineContextMenuSlice.js"; import { selectPolylineVisible, setPolylineVisible, initializePolylineFromLocalStorageThunk, } from "@/redux/slices/database/polylines/polylineLayerVisibleSlice.js"; import { selectGisStationsStaticDistrict } from "@/redux/slices/webservice/gisStationsStaticDistrictSlice.js"; import { selectGisSystemStatic, setGisSystemStatic, } from "@/redux/slices/webservice/gisSystemStaticSlice.js"; //-----------Redux-Thunks------------------- import { fetchGisStationsMeasurementsThunk } from "@/redux/thunks/webservice/fetchGisStationsMeasurementsThunk"; import { fetchGisSystemStaticThunk } from "@/redux/thunks/webservice/fetchGisSystemStaticThunk"; import { fetchGisStationsStaticDistrictThunk } from "@/redux/thunks/webservice/fetchGisStationsStaticDistrictThunk"; import { fetchGisStationsStatusDistrictThunk } from "@/redux/thunks/webservice/fetchGisStationsStatusDistrictThunk"; import { fetchLocationDevicesThunk } from "@/redux/thunks/database/fetchLocationDevicesThunk"; import { fetchPriorityConfigThunk } from "@/redux/thunks/database/fetchPriorityConfigThunk.js"; import { fetchGisLinesThunk } from "@/redux/thunks/database/polylines/fetchGisLinesThunk.js"; import { fetchGisLinesStatusThunk } from "@/redux/thunks/webservice/fetchGisLinesStatusThunk"; import { fetchUserRightsThunk } from "@/redux/thunks/webservice/fetchUserRightsThunk"; import { fetchPoiIconsDataThunk } from "@/redux/thunks/database/pois/fetchPoiIconsDataThunk.js"; import { fetchPoiTypThunk } from "@/redux/thunks/database/pois/fetchPoiTypThunk.js"; import { updateAreaThunk } from "@/redux/thunks/database/area/updateAreaThunk"; import useDynamicDeviceLayers from "@/components/devices/hooks/useDynamicDeviceLayers.js"; import useDataUpdater from "@/components/hooks/useDataUpdater.js"; import { cleanupPolylinesForMemory } from "@/utils/polylines/cleanupPolylinesForMemory"; import { cleanupMarkers } from "@/utils/common/cleanupMarkers"; import { monitorHeapAndReload } from "@/utils/common/monitorMemory"; import { monitorHeapWithRedux } from "@/utils/common/monitorMemory"; import { io } from "socket.io-client"; import { setGisStationsStaticDistrict } from "@/redux/slices/webservice/gisStationsStaticDistrictSlice.js"; import { getDebugLog } from "../../utils/configUtils"; //----------------------------------------------------------------------------------------------------- const MapComponent = ({ locations, onLocationUpdate, lineCoordinates }) => { //------------------------------- const appVersion = process.env.NEXT_PUBLIC_APP_VERSION; const dispatch = useDispatch(); // useDataUpdater(); const [triggerUpdate, setTriggerUpdate] = useState(false); const countdown = useSelector(state => state.polylineContextMenu.countdown); const countdownActive = useSelector(state => state.polylineContextMenu.countdownActive); const isPolylineContextMenuOpen = useSelector(state => state.polylineContextMenu.isOpen); const polylineVisible = useSelector(selectPolylineVisible); const polylineInitialized = useSelector(state => state.polylineLayerVisible.isInitialized); const GisSystemStatic = useSelector(selectGisSystemStatic); // Prüfen, ob TALAS (IdSystem 1) erlaubt ist const isTalasAllowed = Array.isArray(GisSystemStatic) ? GisSystemStatic.some( system => system.IdSystem === 1 && system.Allow === 1 && system.Map === 1 ) : false; const isPoiTypLoaded = useSelector(state => state.poiTypes.status === "succeeded"); const statusMeasurements = useSelector(state => state.gisStationsMeasurements.status); const statusSystem = useSelector(state => state.gisSystemStatic.status); const statusStaticDistrict = useSelector(state => state.gisStationsStaticDistrict.status); const statusStatusDistrict = useSelector(state => state.gisStationsStatusDistrict.status); const priorityConfig = useSelector(selectPriorityConfig); const poiLayerVisible = useSelector(state => state.poiLayerVisible.visible); const zoomTrigger = useSelector(state => state.zoomTrigger.trigger); const poiReadTrigger = useSelector(state => state.poiReadFromDbTrigger.trigger); const GisStationsStaticDistrict = useSelector(selectGisStationsStaticDistrict); const gisSystemStaticStatus = useSelector(state => state.gisSystemStatic.status); const polylineEventsDisabled = useSelector(state => state.polylineEventsDisabled.disabled); const mapLayersVisibility = useSelector(selectMapLayersState) || {}; const selectedArea = useSelector(state => state.selectedArea.area); const linesData = useSelector(state => state.gisLinesFromDatabase.data); const gisLinesStatus = useSelector(state => state.gisLinesStatusFromWebservice.status); const { data: gisLinesStatusData, status: statusGisLinesStatus } = useSelector( selectGisLinesStatusFromWebservice ); // Alarm Status und Link aus GisStationsStatusDistrict const gisStationsStatusDistrict = useSelector(state => state.gisStationsStatusDistrict.data); // Unterstützt sowohl Array-Shape (Statis[]) als auch Objekt mit Statis-Array let hasActiveAlarm = false; let alarmLink = ""; let alarmText = ""; if (Array.isArray(gisStationsStatusDistrict)) { const alarmObj = gisStationsStatusDistrict.find(item => item?.Alarm === 1 && item?.AlarmLink); hasActiveAlarm = !!alarmObj; alarmLink = alarmObj?.AlarmLink || ""; alarmText = alarmObj?.Me || "Alarm aktiv"; } else if (gisStationsStatusDistrict?.Statis) { const alarmObj = gisStationsStatusDistrict.Statis.find( item => item?.Alarm === 1 && item?.AlarmLink ); hasActiveAlarm = !!alarmObj; alarmLink = alarmObj?.AlarmLink || ""; alarmText = alarmObj?.Me || "Alarm aktiv"; } const poiIconsData = useSelector(selectPoiIconsData); const poiIconsStatus = useSelector(selectPoiIconsStatus); const poiTypData = useSelector(selectPoiTypData); const poiTypStatus = useSelector(state => state.poiTypes.status); //const poiTypStatus = useSelector(selectPoiTypStatus); //------------------------------- const { deviceName, setDeviceName } = useMapComponentState(); const [locationDeviceData, setLocationDeviceData] = useState([]); const [menuItemAdded, setMenuItemAdded] = useState(false); const [isPopupOpen, setIsPopupOpen] = useState(false); const closePopup = () => setIsPopupOpen(false); const [currentCoordinates, setCurrentCoordinates] = useState(""); const [showPoiUpdateModal, setShowPoiUpdateModal] = useState(false); const [currentPoiData, setCurrentPoiData] = useState(null); const [showVersionInfoModal, setShowVersionInfoModal] = useState(false); const [poiTypMap, setPoiTypMap] = useState(new Map()); const [showPopup, setShowPopup] = useState(false); const [showAreaDropdown, setShowAreaDropdown] = useState(() => { try { const v = localStorage.getItem("showAreaDropdown"); return v === null ? false : v === "true"; } catch (_) { return false; } }); const poiLayerRef = useRef(null); // Referenz auf die Layer-Gruppe für Datenbank-Marker const mapRef = useRef(null); // Referenz auf das DIV-Element der Karte const [map, setMap] = useState(null); // Zustand der Karteninstanz const [oms, setOms] = useState(null); // State für OMS-Instanz // Sichtbarkeit der App-Info-Karte (unten links) const [showAppInfoCard, setShowAppInfoCard] = useState(() => { try { const v = localStorage.getItem("showAppInfoCard"); return v === null ? true : v === "true"; } catch (_) { return true; } }); // Sichtbarkeit des Layer-Kontrollpanels (oben rechts) const [showLayersPanel, setShowLayersPanel] = useState(() => { try { const v = localStorage.getItem("showLayersPanel"); return v === null ? true : v === "true"; } catch (_) { return true; } }); // Base-Map Panel wurde entfernt // Sichtbarkeit der Koordinaten-Suche (Lupe) const [showCoordinateInput, setShowCoordinateInput] = useState(() => { try { const v = localStorage.getItem("showCoordinateInput"); return v === null ? false : v === "true"; } catch (_) { return false; } }); // Zentrale Steuerung: Nur ein Overlay gleichzeitig // Mögliche Werte: null | 'area' | 'layers' | 'coord' | 'info' const [overlay, setOverlay] = useState(null); // Initiale Bestimmung des aktiven Overlays basierend auf bestehenden Flags useEffect(() => { if (showAreaDropdown) setOverlay("area"); else if (showLayersPanel) setOverlay("layers"); else if (showCoordinateInput) setOverlay("coord"); else if (showAppInfoCard) setOverlay("info"); else setOverlay(null); // nur beim Mount ausführen // eslint-disable-next-line react-hooks/exhaustive-deps }, []); // Flags mit Overlay-State synchronisieren (persistiert weiterhin in bestehenden Effects) useEffect(() => { setShowAreaDropdown(overlay === "area"); setShowLayersPanel(overlay === "layers"); setShowCoordinateInput(overlay === "coord"); setShowAppInfoCard(overlay === "info"); }, [overlay]); // Flag, ob Nutzer die Polyline-Checkbox manuell betätigt hat // Nutzer-Flag global auf window, damit auch Redux darauf zugreifen kann if (typeof window !== "undefined" && window.userToggledPolyline === undefined) { window.userToggledPolyline = false; } //-----userRights---------------- const isRightsLoaded = useSelector( state => state.gisUserRightsFromWebservice.status === "succeeded" ); const userRights = useSelector(selectGisUserRightsFromWebservice); const hasRights = userRights.includes(56); //----------------------------- const openPopupWithCoordinates = e => { const coordinates = `${e.latlng.lat.toFixed(5)}, ${e.latlng.lng.toFixed(5)}`; setCurrentCoordinates(coordinates); setIsPopupOpen(true); setPopupCoordinates(e.latlng); setPopupVisible(true); }; // Konstanten für die URLs //----------------------------------------- const [linePositions, setLinePositions] = useState([]); const { lineColors, tooltipContents } = useLineData(); const [polylines, setPolylines] = useState([]); const [markers, setMarkers] = useState([]); const [newPoint, setNewPoint] = useState(null); const [newCoords, setNewCoords] = useState(null); const [tempMarker, setTempMarker] = useState(null); const [showPoiModal, setShowPoiModal] = useState(false); const [showCoordinatesModal, setShowCoordinatesModal] = useState(false); const [popupCoordinates, setPopupCoordinates] = useState(null); const [popupVisible, setPopupVisible] = useState(false); const [poiData, setPoiData] = useState([]); // Edit mode state mirrors MapLayersControlPanel's behavior const [editMode, setEditMode] = useState(() => { try { return localStorage.getItem("editMode") === "true"; } catch (_) { return false; } }); const openVersionInfoModal = () => { setShowVersionInfoModal(true); }; const closeVersionInfoModal = () => { setShowVersionInfoModal(false); }; const [currentZoom, setCurrentZoom] = useState(() => { const storedZoom = localStorage.getItem("mapZoom"); return storedZoom ? parseInt(storedZoom, 10) : 12; }); const [currentCenter, setCurrentCenter] = useState(() => { const storedCenter = localStorage.getItem("mapCenter"); try { return storedCenter ? JSON.parse(storedCenter) : [53.111111, 8.4625]; } catch (e) { console.error("Error parsing stored map center:", e); return [53.111111, 8.4625]; } }); // Persistiere Sichtbarkeit der App-Info-Karte useEffect(() => { try { localStorage.setItem("showAppInfoCard", String(showAppInfoCard)); } catch (_) {} }, [showAppInfoCard]); // Persistiere Sichtbarkeit des Area-Dropdowns (Marker-Overlay) useEffect(() => { try { localStorage.setItem("showAreaDropdown", String(showAreaDropdown)); } catch (_) {} }, [showAreaDropdown]); // Persistiere Sichtbarkeit des Layer-Panels useEffect(() => { try { localStorage.setItem("showLayersPanel", String(showLayersPanel)); } catch (_) {} }, [showLayersPanel]); // Persist-Logik für Base-Map Panel entfernt // Persistiere Sichtbarkeit der Koordinaten-Suche useEffect(() => { try { localStorage.setItem("showCoordinateInput", String(showCoordinateInput)); } catch (_) {} }, [showCoordinateInput]); //-------------------------------------------- const handleCoordinatesSubmit = coords => { const [lat, lng] = coords.split(",").map(Number); if (map && lat && lng) { map.flyTo([lat, lng], 12); // Zentriere die Karte auf die Koordinaten } }; //-----------------------------Map Initialisierung---------------- // Default map options for Leaflet const mapOptions = { center: currentCenter, zoom: currentZoom, zoomControl: true, contextmenu: true, contextmenuWidth: 180, contextmenuItems: [], }; useInitializeMap( map, mapRef, setMap, setOms, setMenuItemAdded, addItemsToMapContextMenu, hasRights, value => dispatch(setDisabled(value)), mapOptions // pass mapOptions ); //-------------------------React Hooks-------------------------------- // URL-Parameter extrahieren und kartenspezifische localStorage-Keys verwenden useEffect(() => { // Immer beim Umschalten der Kabelstrecken-Checkbox prüfen! if (map) { if (!polylineVisible) { map.eachLayer(layer => { // Entferne alle Marker mit StartIcon, EndIcon oder CircleIcon (Stützpunkt) if ( layer instanceof L.Marker && layer.options && layer.options.icon && (layer.options.icon === StartIcon || layer.options.icon === EndIcon || layer.options.icon === CircleIcon) ) { map.removeLayer(layer); } }); } } // Initialisierung der Layer-Visibility und Polyline-Redux-State nur beim Mount if (typeof window !== "undefined") { const params = new URLSearchParams(window.location.search); const mapId = params.get("m"); const userId = params.get("u"); if (mapId && userId) { // Speichere aktuelle Map- und User-ID localStorage.setItem("currentMapId", mapId); localStorage.setItem("currentUserId", userId); // Kartenspezifischer localStorage-Key const mapStorageKey = `mapLayersVisibility_m${mapId}_u${userId}`; const storedMapLayersVisibility = localStorage.getItem(mapStorageKey); if (storedMapLayersVisibility) { try { const parsedVisibility = JSON.parse(storedMapLayersVisibility); // Nur initial setzen, wenn Nutzer noch nicht manuell eingegriffen hat if (!userToggledPolyline.current) { Object.keys(parsedVisibility).forEach(key => { dispatch(setLayerVisibility({ layer: key, visibility: parsedVisibility[key] })); }); console.log( `🔄 mapLayersVisibility für Map ${mapId}/User ${userId} geladen:`, parsedVisibility ); } } catch (error) { console.error("❌ Fehler beim Laden von mapLayersVisibility:", error); } } else { console.log( `📝 Keine gespeicherten Einstellungen für Map ${mapId}/User ${userId} gefunden` ); } // Redux Polyline Sichtbarkeit initialisieren (map/user spezifisch) if (!userToggledPolyline.current) { dispatch(initializePolylineFromLocalStorageThunk()); } } } }, [dispatch, polylineVisible, map]); // Callback für Checkbox-Umschaltung (Kabelstrecken) const handlePolylineCheckboxChange = useCallback( checked => { if (typeof window !== "undefined") { window.userToggledPolyline = true; } dispatch(setPolylineVisible(checked)); }, [dispatch] ); useEffect(() => { if (linesData && Array.isArray(linesData)) { const transformed = linesData.map(item => ({ coordinates: item.points.map(point => [point.x, point.y]), idModul: item.idModul, idLD: item.idLD, })); setLinePositions(transformed); } }, [linesData]); //-------------------------------------------- useEffect(() => { dispatch(fetchPoiIconsDataThunk()); dispatch(fetchPoiTypThunk()); }, [dispatch]); //-------------------------------------------- // POIs auf die Karte zeichnen useEffect(() => { if (map && !poiLayerRef.current) { poiLayerRef.current = new L.LayerGroup().addTo(map); } return () => { if (map && poiLayerRef.current) { map.removeLayer(poiLayerRef.current); poiLayerRef.current = new L.LayerGroup().addTo(map); } locations.forEach(location => {}); }; }, [map, locations, poiReadTrigger]); //-------------------------------------------- const { Points: gisDevices = [] } = useSelector(selectGisStationsStaticDistrict); // POIs auf die Karte zeichnen useEffect(() => { if (poiData.length === 0) return; setupPOIs( map, locations, poiData, poiTypMap, userRights, poiLayerRef, setSelectedPoi, setLocationDeviceData, setDeviceName, setCurrentPoi, poiLayerVisible, fetchPoiDataService, toast, setShowPoiUpdateModal, setCurrentPoiData, gisDevices, dispatch ); }, [ map, locations, onLocationUpdate, poiReadTrigger, isPoiTypLoaded, userRights, poiLayerVisible, poiData, poiTypMap, dispatch, ]); //--------------------------------------------- useEffect(() => { if (map) { } }, [map, GisSystemStatic, priorityConfig]); //-------------------------------------------- useEffect(() => { if (map) { var x = 51.41321407879154; var y = 7.739617925303934; var zoom = 7; if (map && map.flyTo) { map.flyTo([x, y], zoom); } else { console.error("Map object is not ready or does not have flyTo method"); } } }, [map, zoomTrigger]); //-------------------------------------------- //----------------------------------------------------------------- //Tooltip an mouse position anzeigen für die Linien useEffect(() => { if (!map) return; console.log( "[MapComponent/useEffect] polylineVisible:", polylineVisible, "isTalasAllowed:", isTalasAllowed, "poiLayerVisible:", poiLayerVisible ); // Wenn TALAS nicht erlaubt ist, Polyline-Checkbox und Anzeige deaktivieren if (!isTalasAllowed) { cleanupPolylinesForMemory(polylines, map); setPolylines([]); return; } // Die Sichtbarkeit der Polylines hängt nur noch vom Redux-Slice ab // vorherige Marker & Polylinien vollständig bereinigen (Array.isArray(markers) ? markers : []).forEach(marker => { marker.remove(); }); cleanupPolylinesForMemory(polylines, map); console.log("[MapComponent/useEffect] Nach cleanupPolylinesForMemory, polylines:", polylines); // Setze neue Marker und Polylinien mit den aktuellen Daten (asynchron!) const updatePolylines = async () => { if (polylineVisible) { const { markers: newMarkers, polylines: newPolylines } = await setupPolylines( map, linePositions, lineColors, tooltipContents, setNewCoords, tempMarker, currentZoom, currentCenter, polylineVisible ); (Array.isArray(newPolylines) ? newPolylines : []).forEach((polyline, index) => { const tooltipContent = tooltipContents[`${linePositions[index].idLD}-${linePositions[index].idModul}`] || "Die Linie ist noch nicht in Webservice vorhanden oder bekommt keine Daten"; polyline.bindTooltip(tooltipContent, { permanent: false, direction: "auto", sticky: true, offset: [20, 0], pane: "tooltipPane", }); polyline.on("mouseover", e => { const tooltip = polyline.getTooltip(); if (tooltip) { const mousePos = e.containerPoint; const mapSize = map.getSize(); let direction = "right"; if (mousePos.x > mapSize.x - 100) { direction = "left"; } else if (mousePos.x < 100) { direction = "right"; } if (mousePos.y > mapSize.y - 100) { direction = "top"; } else if (mousePos.y < 100) { direction = "bottom"; } tooltip.options.direction = direction; polyline.openTooltip(e.latlng); } }); polyline.on("mouseout", () => { polyline.closeTooltip(); }); }); cleanupMarkers(markers, oms); setMarkers(newMarkers); setPolylines(newPolylines); console.log("[MapComponent/useEffect] setPolylines (sichtbar):", newPolylines); } else { // Entferne wirklich alle Polylinien-Layer von der Karte if (map) { map.eachLayer(layer => { if (layer instanceof L.Polyline) { map.removeLayer(layer); } }); } cleanupPolylinesForMemory(polylines, map); setPolylines([]); console.log("[MapComponent/useEffect] setPolylines ([]), alle Polylinien entfernt"); } }; updatePolylines(); }, [ map, linePositions, lineColors, tooltipContents, newPoint, newCoords, tempMarker, polylineVisible, isTalasAllowed, poiLayerVisible, ]); //-------------------------------------------- //Test in useEffect useEffect(() => { if (map) { if (getDebugLog()) { console.log("🗺️ Map-Einstellungen werden wiederhergestellt..."); } restoreMapSettings(map); } }, [map]); //-------------------------------------------- useEffect(() => { if (map) { if (getDebugLog()) { console.log("map in MapComponent: ", map); } const handleMapMoveEnd = event => { const newCenter = map.getCenter(); const newZoom = map.getZoom(); setCurrentCenter([newCenter.lat, newCenter.lng]); setCurrentZoom(newZoom); localStorage.setItem("mapCenter", JSON.stringify([newCenter.lat, newCenter.lng])); localStorage.setItem("mapZoom", newZoom); }; map.on("moveend", handleMapMoveEnd); map.on("zoomend", handleMapMoveEnd); return () => { map.off("moveend", handleMapMoveEnd); map.off("zoomend", handleMapMoveEnd); }; } }, [map]); //-------------------------------------------- // Area in DataSheet ->dropdownmenu useEffect(() => { // Sicherstellen, dass `Points` existiert und ein Array ist const points = GisStationsStaticDistrict?.Points; if (selectedArea && map) { const station = points.find(s => s.Area_Name === selectedArea); if (station) { if (getDebugLog()) { console.log("📌 Gefundene Station:", station); } map.flyTo([station.X, station.Y], 14); } else { console.warn("⚠️ Keine passende Station für die Area gefunden:", selectedArea); } } }, [selectedArea, map, GisStationsStaticDistrict]); //------------------------------------- useEffect(() => { if (zoomTrigger && map) { map.flyTo([51.41321407879154, 7.739617925303934], 7); } }, [zoomTrigger, map]); //-------------------------------------------- useEffect(() => { if (map && poiLayerRef.current && isPoiTypLoaded && !menuItemAdded && isRightsLoaded) { addItemsToMapContextMenu( map, menuItemAdded, setMenuItemAdded, hasRights, setShowPopup, setPopupCoordinates ); } }, [map, poiLayerRef, isPoiTypLoaded, menuItemAdded, hasRights, isRightsLoaded]); //-------------------------------------------- // rote Marker ganz oben wenn überlappen sind //-------------------------------------------- useEffect(() => { if (map) { if (polylineEventsDisabled) { disablePolylineEvents(window.polylines); } else { enablePolylineEvents(window.polylines, window.lineColors); } } }, [map, polylineEventsDisabled]); //-------------------------------------------- useEffect(() => { if (map) { if (getDebugLog()) { console.log("6- Karteninstanz (map) wurde jetzt erfolgreich initialisiert"); } } }, [map]); //-------------------------------------------- let timeoutId; useEffect(() => { const initializeContextMenu = () => { if (map) { map.whenReady(() => { timeoutId = setTimeout(() => { if (map.contextmenu) { if (getDebugLog()) { console.log("Contextmenu ist vorhanden"); } } else { console.warn("Contextmenu ist nicht verfügbar."); } }, 500); }); } }; initializeContextMenu(); return () => { clearTimeout(timeoutId); // Aufräumen }; }, [map]); //--------------------------------------- // Initialisiere Leaflet-Karte // Rufe useAreaMarkersLayer direkt auf const urlParams = new URLSearchParams(window.location.search); // URL-Parameter parsen const mValue = urlParams.get("m"); // Wert von "m" holen const hostname = window.location.hostname; // Dynamischer Hostname const port = 3000; // Definiere den gewünschten Port const areaUrl = `http://${hostname}:${port}/api/talas_v5_DB/area/readArea?m=${mValue}`; // Dynamischer Hostname und Port // Areas-Marker basierend auf dynamischer URL laden const handleLocationUpdate = async (idLocation, idMap, newCoords) => { try { await dispatch(updateAreaThunk({ idLocation, idMap, newCoords })).unwrap(); if (getDebugLog()) { console.log("Koordinaten erfolgreich aktualisiert:", result); } } catch (error) { console.error("Fehler beim Aktualisieren der Location:", error); } }; // Areas-Marker basierend auf dynamischer URL laden const areaMarkers = useAreaMarkersLayer(map, oms, areaUrl, handleLocationUpdate); /* Areamarker werden jetzt nur angezeigt, wenn der editMode aktiviert ist. Marker werden bei deaktiviertem editMode aus der Karte entfernt. Dynamische Überwachung von Änderungen im editMode über localStorage und Event Listener implementiert. Dragging für Marker im editMode aktiviert und Z-Index angepasst. */ useEffect(() => { const editMode = localStorage.getItem("editMode") === "true"; // Prüfe, ob der editMode deaktiviert ist if (!editMode) { // Entferne alle Marker aus der Karte if (!map) return; // Sicherstellen, dass map existiert (Array.isArray(areaMarkers) ? areaMarkers : []).forEach(marker => { if (map.hasLayer(marker)) { map.removeLayer(marker); } }); } else { // Wenn editMode aktiviert ist, füge die Marker hinzu und aktiviere Dragging (Array.isArray(areaMarkers) ? areaMarkers : []).forEach(marker => { if (!map.hasLayer(marker)) { marker.addTo(map); // Layer hinzufügen } marker.dragging.enable(); marker.setZIndexOffset(1000); // Marker nach oben setzen }); } }, [areaMarkers, map]); //---------------------------------- const { markerStates, layerRefs } = useDynamicDeviceLayers( map, GisSystemStatic, mapLayersVisibility, priorityConfig, oms ); useEffect(() => { const handleVisibilityChange = () => { if (!map) return; const allMarkers = Object.values(markerStates).filter(Array.isArray).flat(); checkOverlappingMarkers(map, allMarkers, plusRoundIcon, oms); }; window.addEventListener("visibilityChanged", handleVisibilityChange); return () => { window.removeEventListener("visibilityChanged", handleVisibilityChange); }; }, [map, markerStates]); //--------------------------------------- useEffect(() => { if (map) { // initGeocoderFeature(map); // Geocoder-Feature initialisieren, kann von .env.local ausgeschaltet werden } }, [map]); //-------------------------------------------- useEffect(() => { if (map && !menuItemAdded) { addItemsToMapContextMenu( map, menuItemAdded, setMenuItemAdded, setShowCoordinatesModal, setShowPoiModal, setPopupCoordinates, openPopupWithCoordinates // Diese Funktion wird jetzt übergeben! ); } }, [map, menuItemAdded]); //-------------------------------------------- useEffect(() => { dispatch(fetchUserRightsThunk()); }, [dispatch]); //-------------------------------------------- // (Initialisierung erfolgt in MapLayersControlPanel) //-------------------------------------------- // MapComponent reagiert nicht mehr direkt auf localStorage-Events für polylineVisible //-------------------------------------------- useEffect(() => { if (statusStaticDistrict === "idle") { dispatch(fetchGisStationsStaticDistrictThunk()); } }, [statusStaticDistrict, dispatch]); useEffect(() => { if (statusStatusDistrict === "idle") { dispatch(fetchGisStationsStatusDistrictThunk()); } }, [statusStatusDistrict, dispatch]); useEffect(() => { if (statusMeasurements === "idle") { dispatch(fetchGisStationsMeasurementsThunk()); } }, [statusMeasurements, dispatch]); useEffect(() => { if (statusSystem === "idle") { dispatch(fetchGisSystemStaticThunk()); } }, [statusSystem, dispatch]); useEffect(() => { dispatch(fetchGisSystemStaticThunk()); }, [dispatch]); useEffect(() => { dispatch(fetchLocationDevicesThunk()); }, [dispatch]); //--------------------------------------------------------------- useEffect(() => { const params = new URL(window.location.href).searchParams; dispatch(setMapId(params.get("m"))); dispatch(setUserId(params.get("u"))); }, [dispatch]); //--------------------------------------------------------------- useEffect(() => { dispatch(fetchPriorityConfigThunk()); }, [dispatch]); //-------------------------------------------------------- useEffect(() => { if (gisLinesStatus === "idle") { dispatch(fetchGisLinesThunk()); } }, [gisLinesStatus, dispatch]); //-------------------------------------------------------- useEffect(() => { dispatch(fetchGisLinesStatusThunk()); }, [dispatch]); //--------------------------------------------------------- const rights = useSelector(state => state.gisUserRightsFromWebservice.rights); useEffect(() => { dispatch(fetchUserRightsThunk()); }, [dispatch]); //---------------------------------------------------- useEffect(() => { if (poiTypStatus === "idle") { dispatch(fetchPoiTypThunk()); } }, [poiTypStatus, dispatch]); //-------------------------------------- useEffect(() => { if (isPolylineContextMenuOpen && countdownActive) { const interval = setInterval(() => { dispatch(updateCountdown()); // console.log(`⏳ Redux Countdown: ${countdown} Sekunden`); if (countdown <= 2) { if (getDebugLog()) { console.log("🚀 Kontextmenü wird wegen Countdown < 2 geschlossen."); } dispatch(closePolylineContextMenu()); if (window.map?.contextmenu) { window.map.contextmenu.hide(); } clearInterval(interval); } }, 1000); return () => { clearInterval(interval); }; } }, [isPolylineContextMenuOpen, countdown, countdownActive, dispatch, window.map]); //---------------------------------- // **Fehlerbehandlung für `contextmenu`** // damit den Fehler mit contextmenu nicht angezeigt wird und überspringt wird und die Seite neu geladen wird useEffect(() => { let timeoutId; window.onerror = function (message, source, lineno, colno, error) { if (message.includes("Cannot read properties of null (reading 'contextmenu')")) { console.warn("⚠️ Fehler mit `contextmenu` erkannt – Neuladen der Seite."); timeoutId = setTimeout(() => { window.location.reload(); }, 0); return true; // Fehler unterdrücken } }; return () => { clearTimeout(timeoutId); window.onerror = null; // Aufräumen beim Unmount }; }, []); //------------------------------------------------ useEffect(() => { if (poiTypStatus === "succeeded" && Array.isArray(poiTypData)) { const map = new Map(); (Array.isArray(poiTypData) ? poiTypData : []).forEach(item => map.set(item.idPoiTyp, item.name) ); setPoiTypMap(map); } }, [poiTypData, poiTypStatus]); //------------------------------------------------ useEffect(() => { if (poiIconsStatus === "succeeded") { setPoiData(poiIconsData); } }, [poiIconsData, poiIconsStatus]); //----------------------------------------------------------------- //---------------------------------------------- useEffect(() => { if (process.env.NODE_ENV === "development") { console.log("🚧 Development Mode aktiviert – Mock-Daten werden verwendet!"); } else { console.log("Production Mode aktiviert"); } }, []); //------------------------------------------- useEffect(() => { return () => { cleanupMarkers(markers, oms); }; }, []); //-------------------------------------------- // Überwacht den Speicherverbrauch und lädt die Seite neu, wenn er zu hoch ist, 8GB ist das Limit dann lädt die Seite neu useEffect(() => { const interval = monitorHeapAndReload(8, 10000); return () => clearInterval(interval); // wichtig: aufräumen! }, []); //-------------------------------------------- useEffect(() => { const interval = monitorHeapWithRedux(10000); // alle 10s return () => clearInterval(interval); }, []); //-------------------------------------------- useEffect(() => { const params = new URLSearchParams(window.location.search); const m = params.get("m"); const u = params.get("u"); const socket = io(process.env.NEXT_PUBLIC_SOCKET_URL, { query: { m, u }, }); socket.on("connect", () => { console.log("🔗 WebSocket verbunden "); }); socket.on("GisLinesStatusUpdated", data => { dispatch(fetchGisLinesStatusThunk(data)); }); socket.on("GisStationsMeasurementsUpdated", data => { dispatch(fetchGisStationsMeasurementsThunk(data)); }); socket.on("GisStationsStaticDistrictUpdated", () => { console.log("🛰 WebSocket-Trigger empfangen → Thunk wird erneut ausgeführt"); dispatch(fetchGisStationsStaticDistrictThunk()); console.log("🛰 WebSocket-Trigger empfangen → Trigger wird gesetzt"); setTriggerUpdate(prev => !prev); // Trigger toggeln }); socket.on("GisStationsStatusDistrictUpdated", data => { dispatch(fetchGisStationsStatusDistrictThunk(data)); }); socket.on("GisSystemStaticUpdated", data => { dispatch(fetchGisSystemStaticThunk(data)); }); return () => { socket.disconnect(); }; }, [dispatch]); //-------------------------------------------- useEffect(() => { console.log("📦 Neue Daten empfangen:", GisStationsStaticDistrict); }, [GisStationsStaticDistrict]); const { Points = [] } = useSelector(selectGisStationsStaticDistrict); useEffect(() => {}, [triggerUpdate]); //-------------------------------------------------------------------------------- useEffect(() => { console.log("📊 GisSystemStatic:", GisSystemStatic); }, [GisSystemStatic]); useEffect(() => { if (Array.isArray(GisSystemStatic)) { (Array.isArray(GisSystemStatic) ? GisSystemStatic : []).forEach(system => { const key = `system-${system.IdSystem}`; if (!(key in mapLayersVisibility)) { dispatch(setLayerVisibility({ key, value: true })); // Sichtbarkeit aktivieren } }); } }, [GisSystemStatic, mapLayersVisibility, dispatch]); //--------------------------------------------- //-------------------------------------------- // Expand handler (same behavior as MapLayersControlPanel expand icon) const handleExpandClick = () => { dispatch(setSelectedArea("Station wählen")); dispatch(incrementZoomTrigger()); }; // Toggle edit mode (same logic as EditModeToggle component) const hasEditRight = Array.isArray(userRights) ? userRights.includes?.(56) || userRights.some?.(r => r?.IdRight === 56) : false; const toggleEditMode = () => { if (!hasEditRight) return; const next = !editMode; setEditMode(next); try { localStorage.setItem("editMode", String(next)); } catch (_) {} if (typeof window !== "undefined") { window.location.reload(); } }; //-------------------------------------------- return ( <> {/* Zeigt das POI-Modal, wenn `showPoiModal` true ist */} {showPoiModal && ( setShowPoiModal(false)} /> )}
{showPoiUpdateModal && ( setShowPoiUpdateModal(false)} poiData={currentPoiData} onSubmit={() => {}} latlng={popupCoordinates} /> )}
{showPopup && (
e.stopPropagation()} >
)}
{GisStationsStaticDistrict && GisStationsStaticDistrict.Points?.length > 0 && showLayersPanel && !showAreaDropdown && ( )} {showCoordinateInput && }
{/* Top-right controls: layers, info, expand, edit, and base map stack */}
{/* Alarm-Icon - nur anzeigen wenn Alarm aktiv und Link vorhanden */} {/* Marker-Icon (line-md) */} {/*Lupe: Koordinatensuche ein-/ausblenden */} {/* Expand: Karte auf Standardansicht */} {/* Lupe: Koordinaten-Suche ein-/ausblenden */}
{/* Custom Zoom Controls bottom-right, styled in littwin-blue to match app icons */}
{/* Marker/AreaDropdown Panel außerhalb der Button-Leiste platzieren, damit die Position mit den anderen Panels identisch ist */} {overlay === "area" && setOverlay(null)} />} {/* BaseMapPanel entfernt */} {showAppInfoCard && (
TALAS.Map
Version {appVersion}
)} ); }; export default MapComponent;