- Add kabelstreckenVisible state with localStorage persistence - Implement dual localStorage variables (kabelstreckenVisible + polylineVisible) for compatibility - Add event system for cross-component polyline visibility updates - Update MapComponent to listen for polylineVisibilityChanged events - Ensure polylines display correctly on browser reload - Migrate from Redux-only state to localStorage-first approach - Add comprehensive debug logging for troubleshooting Fixes issue where Kabelstrecken checkbox state was lost on page reload and polylines were not displayed until manual toggle.
986 lines
35 KiB
JavaScript
986 lines
35 KiB
JavaScript
// 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 { InformationCircleIcon } from "@heroicons/react/20/solid";
|
||
import PoiUpdateModal from "@/components/pois/poiUpdateModal/PoiUpdateModal.js";
|
||
import { ToastContainer, toast } from "react-toastify";
|
||
import plusRoundIcon from "../icons/devices/overlapping/PlusRoundIcon.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 CoordinateInput from "@/components/uiWidgets/CoordinateInput.js";
|
||
import VersionInfoModal from "@/components/uiWidgets/VersionInfoModal.js";
|
||
//----------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 } from "@/redux/slices/mapLayersSlice";
|
||
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,
|
||
} 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";
|
||
//-----------------------------------------------------------------------------------------------------
|
||
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 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)
|
||
: 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
|
||
);
|
||
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 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
|
||
|
||
//-----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([]);
|
||
|
||
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];
|
||
}
|
||
});
|
||
|
||
//--------------------------------------------
|
||
|
||
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----------------
|
||
useInitializeMap(
|
||
map,
|
||
mapRef,
|
||
setMap,
|
||
setOms,
|
||
setMenuItemAdded,
|
||
addItemsToMapContextMenu,
|
||
hasRights,
|
||
value => dispatch(setDisabled(value))
|
||
);
|
||
|
||
//-------------------------React Hooks--------------------------------
|
||
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;
|
||
|
||
// Wenn TALAS nicht erlaubt ist, Polyline-Checkbox und Anzeige deaktivieren
|
||
if (!isTalasAllowed) {
|
||
if (polylineVisible) {
|
||
dispatch(setPolylineVisible(false));
|
||
}
|
||
cleanupPolylinesForMemory(polylines, map);
|
||
setPolylines([]);
|
||
return;
|
||
}
|
||
|
||
// vorherige Marker & Polylinien vollständig bereinigen
|
||
markers.forEach(marker => {
|
||
marker.remove();
|
||
});
|
||
cleanupPolylinesForMemory(polylines, map);
|
||
|
||
// Setze neue Marker und Polylinien mit den aktuellen Daten
|
||
if (polylineVisible) {
|
||
const { markers: newMarkers, polylines: newPolylines } = setupPolylines(
|
||
map,
|
||
linePositions,
|
||
lineColors,
|
||
tooltipContents,
|
||
setNewCoords,
|
||
tempMarker,
|
||
currentZoom,
|
||
currentCenter,
|
||
polylineVisible // kommt aus Redux
|
||
);
|
||
|
||
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);
|
||
} else {
|
||
cleanupPolylinesForMemory(polylines, map);
|
||
setPolylines([]);
|
||
}
|
||
}, [
|
||
map,
|
||
linePositions,
|
||
lineColors,
|
||
tooltipContents,
|
||
newPoint,
|
||
newCoords,
|
||
tempMarker,
|
||
polylineVisible,
|
||
isTalasAllowed,
|
||
]);
|
||
|
||
//--------------------------------------------
|
||
|
||
//Test in useEffect
|
||
useEffect(() => {
|
||
if (map) {
|
||
if (process.env.NEXT_PUBLIC_DEBUG_LOG === "true") {
|
||
console.log("🗺️ Map-Einstellungen werden wiederhergestellt...");
|
||
}
|
||
restoreMapSettings(map);
|
||
}
|
||
}, [map]);
|
||
//--------------------------------------------
|
||
useEffect(() => {
|
||
if (map) {
|
||
if (process.env.NEXT_PUBLIC_DEBUG_LOG === "true") {
|
||
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 (process.env.NEXT_PUBLIC_DEBUG_LOG === "true") {
|
||
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 (process.env.NEXT_PUBLIC_DEBUG_LOG === "true") {
|
||
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 (process.env.NEXT_PUBLIC_DEBUG_LOG === "true") {
|
||
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 (process.env.NEXT_PUBLIC_DEBUG_LOG === "true") {
|
||
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
|
||
|
||
areaMarkers.forEach(marker => {
|
||
if (map.hasLayer(marker)) {
|
||
map.removeLayer(marker);
|
||
}
|
||
});
|
||
} else {
|
||
// Wenn editMode aktiviert ist, füge die Marker hinzu und aktiviere Dragging
|
||
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]);
|
||
|
||
//--------------------------------------------
|
||
// Beim ersten Client-Render den Wert aus localStorage laden
|
||
// Prüfe beide localStorage-Variablen (neue und alte)
|
||
useEffect(() => {
|
||
let storedPolylineVisible = localStorage.getItem("kabelstreckenVisible") === "true";
|
||
|
||
// Fallback auf alte Variable, falls neue nicht existiert
|
||
if (localStorage.getItem("kabelstreckenVisible") === null) {
|
||
const oldValue = localStorage.getItem("polylineVisible") === "true";
|
||
storedPolylineVisible = oldValue;
|
||
// Migriere zur neuen Variable
|
||
localStorage.setItem("kabelstreckenVisible", oldValue.toString());
|
||
}
|
||
|
||
console.log(
|
||
"🔄 MapComponent: Loading polylineVisible from localStorage:",
|
||
storedPolylineVisible
|
||
);
|
||
dispatch(setPolylineVisible(storedPolylineVisible));
|
||
}, [dispatch]);
|
||
//--------------------------------------------
|
||
// Event-Listener für Polyline-Sichtbarkeitsänderungen
|
||
useEffect(() => {
|
||
const handlePolylineVisibilityChange = () => {
|
||
const storedValue = localStorage.getItem("kabelstreckenVisible") === "true";
|
||
console.log("🔄 MapComponent: Received polylineVisibilityChanged event, value:", storedValue);
|
||
dispatch(setPolylineVisible(storedValue));
|
||
};
|
||
|
||
window.addEventListener("polylineVisibilityChanged", handlePolylineVisibilityChange);
|
||
|
||
return () => {
|
||
window.removeEventListener("polylineVisibilityChanged", handlePolylineVisibilityChange);
|
||
};
|
||
}, [dispatch]);
|
||
//--------------------------------------------
|
||
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 (process.env.NEXT_PUBLIC_DEBUG_LOG === "true") {
|
||
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();
|
||
poiTypData.forEach(item => map.set(item.idPoiTyp, item.name));
|
||
setPoiTypMap(map);
|
||
}
|
||
}, [poiTypData, poiTypStatus]);
|
||
//------------------------------------------------
|
||
useEffect(() => {
|
||
if (poiIconsStatus === "succeeded") {
|
||
setPoiData(poiIconsData);
|
||
}
|
||
}, [poiIconsData, poiIconsStatus]);
|
||
//-----------------------------------------------------------------
|
||
useEffect(() => {
|
||
if (!map) return;
|
||
|
||
const editMode = localStorage.getItem("editMode") === "true";
|
||
|
||
Object.entries(markerStates).forEach(([systemName, markers]) => {
|
||
const isVisible = mapLayersVisibility[systemName];
|
||
markers.forEach(marker => {
|
||
const hasLayer = map.hasLayer(marker);
|
||
if (editMode || !isVisible) {
|
||
if (hasLayer) map.removeLayer(marker);
|
||
} else {
|
||
if (!hasLayer) marker.addTo(map);
|
||
}
|
||
});
|
||
});
|
||
|
||
// optional für alle zusammen
|
||
const allMarkers = Object.values(markerStates)
|
||
.filter(entry => Array.isArray(entry))
|
||
.flat();
|
||
|
||
checkOverlappingMarkers(map, allMarkers, plusRoundIcon);
|
||
}, [map, markerStates, mapLayersVisibility]);
|
||
|
||
//----------------------------------------------
|
||
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]);
|
||
//---------------------------------------------
|
||
//--------------------------------------------
|
||
return (
|
||
<>
|
||
{/* Zeigt das POI-Modal, wenn `showPoiModal` true ist */}
|
||
{showPoiModal && (
|
||
<AddPOIModal latlng={popupCoordinates} onClose={() => setShowPoiModal(false)} />
|
||
)}
|
||
<ToastContainer />
|
||
<div>
|
||
{showPoiUpdateModal && (
|
||
<PoiUpdateModal
|
||
onClose={() => setShowPoiUpdateModal(false)}
|
||
poiData={currentPoiData}
|
||
onSubmit={() => {}}
|
||
latlng={popupCoordinates}
|
||
/>
|
||
)}
|
||
</div>
|
||
|
||
<div>
|
||
{showPopup && (
|
||
<div
|
||
className="fixed inset-0 bg-black bg-opacity-10 flex justify-center items-center z-[1000]"
|
||
onClick={closePopup}
|
||
>
|
||
<div
|
||
className="relative bg-white p-6 rounded-lg shadow-lg"
|
||
onClick={e => e.stopPropagation()}
|
||
>
|
||
<button
|
||
onClick={closePopup}
|
||
className="absolute top-0 right-0 mt-2 mr-2 p-1 text-gray-700 hover:text-gray-900 focus:outline-none focus:ring-2 focus:ring-gray-600"
|
||
aria-label="Close"
|
||
>
|
||
<svg
|
||
xmlns="http://www.w3.org/2000/svg"
|
||
className="h-6 w-6"
|
||
fill="none"
|
||
viewBox="0 0 24 24"
|
||
stroke="currentColor"
|
||
>
|
||
<path
|
||
strokeLinecap="round"
|
||
strokeLinejoin="round"
|
||
strokeWidth={2}
|
||
d="M6 18L18 6M6 6l12 12"
|
||
/>
|
||
</svg>
|
||
</button>
|
||
</div>
|
||
</div>
|
||
)}
|
||
</div>
|
||
|
||
{GisStationsStaticDistrict && GisStationsStaticDistrict.Points?.length > 0 && (
|
||
<MapLayersControlPanel className="z-50" />
|
||
)}
|
||
|
||
<CoordinateInput onCoordinatesSubmit={handleCoordinatesSubmit} />
|
||
<div id="map" ref={mapRef} className="z-0" style={{ height: "100vh", width: "100vw" }}></div>
|
||
<CoordinatePopup isOpen={isPopupOpen} coordinates={currentCoordinates} onClose={closePopup} />
|
||
|
||
<div className="absolute bottom-3 left-3 w-72 p-4 bg-white rounded-lg shadow-md z-50">
|
||
<div className="flex justify-between items-center">
|
||
<div>
|
||
<span className="text-black text-lg font-semibold"> TALAS.Map </span>
|
||
<br />
|
||
<span className="text-black text-lg">Version {appVersion}</span>
|
||
</div>
|
||
<div>
|
||
<button onClick={openVersionInfoModal}>
|
||
<InformationCircleIcon className="text-blue-900 h-8 w-8 pr-1" title="Weitere Infos" />
|
||
</button>
|
||
</div>
|
||
</div>
|
||
</div>
|
||
<VersionInfoModal
|
||
showVersionInfoModal={showVersionInfoModal}
|
||
closeVersionInfoModal={closeVersionInfoModal}
|
||
APP_VERSION={appVersion}
|
||
/>
|
||
</>
|
||
);
|
||
};
|
||
|
||
export default MapComponent;
|