1299 lines
47 KiB
JavaScript
1299 lines
47 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 { 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 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 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 aus GisStationsStatusDistrict
|
||
const gisStationsStatusDistrict = useSelector(state => state.gisStationsStatusDistrict.data);
|
||
// Unterstützt sowohl Array-Shape (Statis[]) als auch Objekt mit Statis-Array
|
||
const hasActiveAlarm = Array.isArray(gisStationsStatusDistrict)
|
||
? gisStationsStatusDistrict.some(item => item?.Alarm === 1)
|
||
: gisStationsStatusDistrict?.Statis?.some(item => item?.Alarm === 1) || false;
|
||
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 && (
|
||
<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 &&
|
||
showLayersPanel &&
|
||
!showAreaDropdown && (
|
||
<MapLayersControlPanel
|
||
className="z-50"
|
||
handlePolylineCheckboxChange={handlePolylineCheckboxChange}
|
||
/>
|
||
)}
|
||
|
||
{showCoordinateInput && <CoordinateInput onCoordinatesSubmit={handleCoordinatesSubmit} />}
|
||
<div id="map" ref={mapRef} className="z-0" style={{ height: "100vh", width: "100vw" }}></div>
|
||
{/* Top-right controls: layers, info, expand, edit, and base map stack */}
|
||
<div className="absolute top-3 right-3 z-50 pointer-events-auto flex items-center gap-2">
|
||
{/* Alarm-Icon - nur anzeigen wenn Alarm aktiv */}
|
||
{hasActiveAlarm && (
|
||
<button
|
||
onClick={() => {}}
|
||
aria-label="Alarm aktiv"
|
||
className="rounded-full bg-white/90 hover:bg-white shadow p-1"
|
||
title="Alarm aktiv"
|
||
>
|
||
<AlarmIcon className="h-8 w-8 animate-pulse text-red-500" />
|
||
</button>
|
||
)}
|
||
{/* Marker-Icon (line-md) */}
|
||
<button
|
||
onClick={() => setOverlay(prev => (prev === "area" ? null : "area"))}
|
||
aria-label="Marker"
|
||
className="rounded-full bg-white/90 hover:bg-white shadow p-1"
|
||
title="Marker"
|
||
>
|
||
<MapMarkerIcon className="h-8 w-8" />
|
||
</button>
|
||
{overlay === "area" && <AreaDropdown onClose={() => setOverlay(null)} />}
|
||
{/*Lupe: Koordinatensuche ein-/ausblenden */}
|
||
<button
|
||
onClick={() => setOverlay(prev => (prev === "coord" ? null : "coord"))}
|
||
aria-label={
|
||
showCoordinateInput ? "Koordinatensuche ausblenden" : "Koordinatensuche einblenden"
|
||
}
|
||
className="rounded-full bg-white/90 hover:bg-white shadow p-1"
|
||
title={
|
||
showCoordinateInput ? "Koordinatensuche ausblenden" : "Koordinatensuche einblenden"
|
||
}
|
||
>
|
||
<SearchIcon className="h-8 w-8" />
|
||
</button>
|
||
<button
|
||
onClick={toggleEditMode}
|
||
aria-label={editMode ? "Bearbeitungsmodus deaktivieren" : "Bearbeitungsmodus aktivieren"}
|
||
className={`rounded-full shadow p-1 ${
|
||
hasEditRight
|
||
? "bg-white/90 hover:bg-white"
|
||
: "bg-white/60 cursor-not-allowed opacity-50"
|
||
}`}
|
||
title={
|
||
hasEditRight
|
||
? editMode
|
||
? "Bearbeitungsmodus deaktivieren"
|
||
: "Bearbeitungsmodus aktivieren"
|
||
: "Keine Bearbeitungsrechte"
|
||
}
|
||
disabled={!hasEditRight}
|
||
>
|
||
{editMode ? <EditOffIcon className="h-8 w-8" /> : <EditIcon className="h-8 w-8" />}
|
||
</button>
|
||
{/* Expand: Karte auf Standardansicht */}
|
||
<button
|
||
onClick={handleExpandClick}
|
||
aria-label="Karte auf Standardansicht"
|
||
className="rounded-full bg-white/90 hover:bg-white shadow p-1 "
|
||
title="Karte auf Standardansicht"
|
||
>
|
||
<ExpandIcon className="h-8 w-8" />
|
||
</button>
|
||
{/* Lupe: Koordinaten-Suche ein-/ausblenden */}
|
||
<button
|
||
onClick={() => setOverlay(prev => (prev === "layers" ? null : "layers"))}
|
||
aria-label={showLayersPanel ? "Layer-Panel ausblenden" : "Layer-Panel einblenden"}
|
||
className="rounded-full bg-white/90 hover:bg-white shadow p-1"
|
||
title={showLayersPanel ? "Layer-Panel ausblenden" : "Layer-Panel einblenden"}
|
||
>
|
||
<MenuIcon className="h-8 w-8" />
|
||
</button>
|
||
|
||
<button
|
||
onClick={() => setOverlay(prev => (prev === "info" ? null : "info"))}
|
||
aria-label={showAppInfoCard ? "Info ausblenden" : "Info einblenden"}
|
||
className="rounded-full bg-white/90 hover:bg-white shadow p-1"
|
||
title={showAppInfoCard ? "Info ausblenden" : "Info einblenden"}
|
||
>
|
||
<InfoIcon
|
||
className="h-8 w-8 pr-1"
|
||
title={showAppInfoCard ? "Info ausblenden" : "Info einblenden"}
|
||
/>
|
||
</button>
|
||
</div>
|
||
{/* BaseMapPanel entfernt */}
|
||
<CoordinatePopup isOpen={isPopupOpen} coordinates={currentCoordinates} onClose={closePopup} />
|
||
|
||
{showAppInfoCard && (
|
||
<div className="absolute top-16 right-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}>
|
||
<InfoIcon className="h-8 w-8 pr-1" title="Weitere Infos" />
|
||
</button>
|
||
</div>
|
||
</div>
|
||
</div>
|
||
)}
|
||
<VersionInfoModal
|
||
showVersionInfoModal={showVersionInfoModal}
|
||
closeVersionInfoModal={closeVersionInfoModal}
|
||
APP_VERSION={appVersion}
|
||
/>
|
||
</>
|
||
);
|
||
};
|
||
|
||
export default MapComponent;
|