56 Commits

Author SHA1 Message Date
ISA
4d2a94ffea chore(mocks): sync all mock JSON with WebService (10.10.0.13) 2025-09-12 13:23:26 +02:00
ISA
239ad82e46 fix(dev): add missing Map flags in GisSystemStatic to match production
Add/restore Map: 1 attributes in GisSystemStatic (dev)
Ensures mapLayers initialization creates visibility keys
Fixes missing area markers and layer control state in dev
Behavior now consistent with production
2025-09-12 13:09:21 +02:00
ISA
a2d3338624 fix: kein DB Verbindung von der Anwendung
Der Fehler war, dass im Code die Funktion getDebugLog() verwendet wurde, die nicht definiert war.
Dadurch ist beim Erstellen des Datenbank-Pools ein Fehler aufgetreten, bevor überhaupt eine Verbindung zur Datenbank aufgebaut werden konnte.

Erst nachdem die Debug-Logik entfernt wurde, konnte die Verbindung erfolgreich hergestellt werden.
Das Problem lag also nicht an der Datenbank oder an den Zugangsdaten, sondern an einem fehlenden bzw. nicht importierten Hilfsfunktion im Pool-Code.
2025-09-12 12:19:51 +02:00
ISA
598acb8441 doc: TODO Icons 2025-09-12 09:33:58 +02:00
ISA
f8512c485e doc: TODO 2025-09-12 09:26:59 +02:00
ISA
fff2754b14 fix: fehlende Icons 2025-09-10 10:23:43 +02:00
ISA
61ed542ea4 feat: osm von server als proxy für den client 2025-09-09 16:11:23 +02:00
ISA
4befddd440 fix: Station öffnen in Tab 2025-08-26 11:33:24 +02:00
ISA
5b7868145c add: Leaflet | © OpenStreetMap contributors 2025-08-25 06:44:37 +02:00
ISA
0a3c4c208f fix: Leistung etwas verbessert wegen Kabelstrecken anzeigen und ausblenden 2025-08-22 14:44:54 +02:00
ISA
8cf520bb2c Fix: Kabelstrecken-Checkbox überschreibt Nutzeraktion nach Initialisierung nicht mehr
- Nutzeraktion (Deaktivieren der Kabelstrecken) wird jetzt durch Initialisierung nicht mehr überschrieben
- Initialisierung prüft, ob der Nutzer die Checkbox bereits betätigt hat
- Verhindert, dass Kabelstrecken nach dem Laden unerwartet
2025-08-22 12:28:40 +02:00
ISA
3896381a8f Debug-Logging zentralisiert: Nutzung von process.env.NEXT_PUBLIC_DEBUG_LOG entfernt und auf getDebugLog() mit config.json umgestellt
- Alle Vorkommen von process.env.NEXT_PUBLIC_DEBUG_LOG entfernt
- Debug-Konfiguration erfolgt jetzt ausschließlich über public/config.json
- getDebugLog()-Utility überall verwendet
- .env-Dateien werden für Debug-Logging nicht mehr benötigt
- Alle betroffenen Komponenten, Services und API
2025-08-22 11:10:40 +02:00
ISA
a013c07394 fix(map): entferne Start-, End- und Stützpunkt-Icons sofort beim Ausblenden der Kabelstrecken
- Alle Marker mit StartIcon, EndIcon und CircleIcon werden jetzt direkt entfernt, wenn die Kabelstrecken-Checkbox deaktiviert wird
- Kein Browser-Reload mehr nötig, Icons verschwinden sofort von der Karte
2025-08-22 10:12:03 +02:00
ISA
f2a322a91b fix: Kabelstrecken werden ausgeblendet 2025-08-22 09:46:31 +02:00
ISA
bf7b62d110 del: [DeviceLayers] 2025-08-21 17:10:32 +02:00
ISA
c44a755077 WIP: Kabelstrecken 2025-08-21 15:34:52 +02:00
ISA
aea439f135 WIP: Kabelstrecken 2025-08-21 15:12:25 +02:00
ISA
c6871692aa WIP: Kabelstrecken localStorage polylineVisible_m12_u484 Wert bleibt stabil aber wenn true ist un dich die Seite neu ladee dann werden die Kabelstrecken nicht mehr ausgeblendet 2025-08-21 14:31:55 +02:00
ISA
e7192a7623 WIP: Kabelstrecken wird deaktiviert beim neuladen der Seite 2025-08-21 14:00:09 +02:00
ISA
f11f64d4d7 WIP: erster aufruf beim wechesln der Karten 2025-08-21 11:06:27 +02:00
ISA
da21cba186 Die ursprüngliche Logik für die Abhängigkeit und Synchronisation der Checkboxen (TALAS/Kabelstrecken) mit localStorage, Redux und lokalem State wurde wiederhergestellt. 2025-08-21 10:03:47 +02:00
ISA
d179c152c0 fix: Die Anwendung verwendet jetzt ausschließlich den Redux-Slice (polylineLayerVisible.visible) für die Sichtbarkeit der Kabelstrecken (Polylines). Die Checkbox und die Anzeige der Linien sind damit immer synchron und reaktiv – unabhängig von localStorage. 2025-08-21 09:52:22 +02:00
ISA
2da79c9318 WIP. polylines visiblity 2025-08-21 09:47:03 +02:00
ISA
2066cbb9e8 fix: Area-Marker (Bereiche) können jetzt nur noch verschoben werden, wenn in localStorage der Wert "editMode" auf "true" gesetzt ist. Andernfalls sind sie nicht verschiebbar. 2025-08-21 08:23:36 +02:00
ISA
c8a14ee873 Fix: Polyline-Layer-Visibility reagiert jetzt zuverlässig auf Redux-State
- setupPolylines wird im useEffect asynchron aufgerufen
- Marker und Polylinien werden erst nach Abschluss der async-Operation gesetzt
- Sichtbarkeit der Kabelstrecken (Kabel-Layer) wird korrekt auf der Karte
2025-08-20 15:52:56 +02:00
ISA
44b29469b9 WIP: polyline Slice visiblity 2025-08-20 15:36:34 +02:00
ISA
ee5319a928 fix: Geräte-Marker werden nur noch über LayerGroups verwaltet, doppelte Marker auf Karte verhindert
Geräte-Marker werden vor dem Hinzufügen aus LayerGroups entfernt und nur dort hinzugefügt
Keine direkte .addTo(map) mehr für Geräte-Marker
Debug-Ausgabe für Marker-Anzahl pro LayerGroup
Problem mit mehrfachen Markern auf der Karte behoben
2025-08-20 14:54:50 +02:00
ISA
d68b17c382 feat: public/config.json 2025-08-20 14:45:47 +02:00
ISA
22692f8153 fix: leere Array 2025-08-20 14:41:57 +02:00
ISA
819fde9605 Safeguard: prevent errors when GisStationsMeasurements is empty or missing
- Ensure createAndSetDevices.js always uses an array for measurements
- Prevents crashes if measurement data is empty
2025-08-20 14:03:38 +02:00
ISA
ded0a9d5da feat: to fix npm install 2025-08-20 13:29:52 +02:00
ISA
4161bfa948 fix: playwright in dev mode in package.json 2025-08-20 13:18:01 +02:00
ISA
680c643ea5 feat: install playwright 2025-08-20 11:30:40 +02:00
ISA
7c525c11a1 del: cypress deinstall 2025-08-20 11:16:45 +02:00
ISA
9f05a4f63b feat(config): Map-Parameter (Zoom, Center, etc.) in config.json ausgelagert
- minZoom, maxZoom, center, zoomOutCenter und basePath in config.json konfigurierbar gemacht
- Kommentare zur Erklärung als _comment-Felder ergänzt
- initializeMap.js und zoomAndCenterUtils.js angepasst, um Werte aus config.json zu lesen
- OSM-Standards für Zoom-Stufen dokumentiert
2025-08-20 10:03:21 +02:00
ISA
26d869f029 fix: Geräte-Marker werden nur noch über LayerGroups verwaltet, doppelte Marker auf Karte verhindert
Geräte-Marker werden vor dem Hinzufügen aus LayerGroups entfernt und nur dort hinzugefügt
Keine direkte .addTo(map) mehr für Geräte-Marker
Debug-Ausgabe für Marker-Anzahl pro LayerGroup
Problem mit mehrfachen Markern auf der Karte behoben
2025-08-20 09:37:49 +02:00
ISA
0ea8e090d2 fix: Area (Bereich) im dropdown nur einmal anzeigen 2025-08-20 09:20:22 +02:00
ISA
9a2b438eaf feat: basePath-Konfiguration von .env in config.json verschoben
basePath wird jetzt in config.json gepflegt statt als NEXT_PUBLIC_BASE_PATH in .env.*
Alle relevanten Code-Stellen lesen basePath dynamisch aus config.json
Dokumentation und Beispiele in Markdown-Dateien entsprechend angepasst
Erhöhte Flexibilität für Deployments ohne Rebuild
2025-08-20 08:42:24 +02:00
ISA
bf4fc95b8e Fix Leaflet map initialization: prevent DOM errors and ensure robust container checks
- Refactored initializeMap to accept the DOM node instead of the ref object
- Updated all checks to use the DOM node directly
- Improved useInitializeMap to only call initializeMap when the container is ready
- Prevents "mapRef.current ist nicht definiert oder nicht im DOM" errors
- Ensures map is only initialized when the container is attached
2025-08-19 15:56:24 +02:00
ISA
db147543d9 docs: Hinweise zu public/config.json für Kartenquellen in README und Entwicklerdokumentation ergänzt
- Beispiel und Erklärung zur Konfiguration von tileSources in public/config.json hinzugefügt
- Dokumentation für Entwickler und Nutzer verständlicher
2025-08-19 14:55:45 +02:00
ISA
418651a2af Fix: Leaflet "Map container is already initialized" error is now handled gracefully
- Fehler beim mehrfachen Initialisieren der Leaflet-Map wird nun abgefangen
- Anwendung zeigt keine störende Fehlermeldung mehr im Frontend
- Verbesserte Nutzererfahrung beim Laden und Aktualisieren der Karte
2025-08-19 14:34:50 +02:00
ISA
bf5ee377a4 del: remove unused health check API 2025-08-19 08:41:17 +02:00
ISA
5c06abc85e Systeme mit Map === 0 vollständig ausgeblenden 2025-08-11 10:17:09 +02:00
ISA
7c89d8dae5 In Redux Slice mapLayersSlice
state[key] = system.Allow === 1 && system.Map === 1;
2025-08-11 10:04:03 +02:00
ISA
85266aa1ed GisSystemStytic system => system.IdSystem === 1 && system.Allow === 1 && system.Map === 1 dann anzeigen in controll panel 2025-08-11 08:52:46 +02:00
ISA
854eff668a cleanup after merge 2025-07-30 15:37:17 +02:00
ISA
8073c787bc Merge branch 'v306' into develop 2025-07-30 15:35:05 +02:00
ISA
4ee6d42a61 fix: Systemtyp Berechtigung der User werden nicht beachtet 2025-07-30 15:19:53 +02:00
ISA
53c670feba fix: Geräte-Marker für Systeme ohne Statusdaten anzeigen (z. B. GMA)
- `createAndSetDevices.js` angepasst, sodass Marker auch ohne `statusDistrictData` erzeugt werden.
- Problem behoben, dass Marker wie GMA trotz vorhandener Koordinaten und Sichtbarkeit nicht gezeichnet wurden.
- Sicherheitsprüfung für Statusdaten optional gemacht, um Systems ohne Messdaten darzustellen.
2025-07-30 11:13:34 +02:00
ISA
d8567a9928 feat: implement map-specific localStorage and TALAS-Kabelstrecken dependency logic
- Add map-specific localStorage keys using URL parameters (m=mapId, u=userId)
- Implement kartenspezifische Sichtbarkeitseinstellungen per Map/User
- Fix localStorage priority over GisSystemStatic Allow values to preserve user settings
- Add bidirectional TALAS ↔ Kabelstrecken dependency logic:
  * Kabelstrecken aktiviert → TALAS automatisch aktiviert
  * TALAS deaktiviert → Kabelstrecken automatisch deaktiviert
- Update mapLayersSlice.js to respect existing localStorage values over system defaults
- Modify MapComponent.js to load map-specific visibility settings on mount
- Update MapLayersControlPanel.js with kartenspezifische localStorage handling
- Fix useDynamicDeviceLayers.js visibility logic (corrected boolean conditions)
- Update useAreaMarkersLayer.js for map-specific localStorage keys

BREAKING CHANGES:
- localStorage structure changed from "mapLayersVisibility" to "mapLayersVisibility_m{mapId}_u{userId}"
- User visibility preferences now have priority over GisSystemStatic Allow values
- TALAS and Kabelstrecken are now logically linked (dependency relationship)

This resolves issues with:
- Map switching losing visibility settings
- Browser reload overriding user preferences with system defaults
- Missing logical connection between TALAS stations and their cable routes
2025-07-29 10:12:56 +02:00
ISA
6d33be56c0 feat: add automated deployment ZIP creation with PowerShell script
- Add create-deployment-zip.ps1 script for automated ZIP packaging
- Include npm scripts: build:deploy and create-zip for streamlined deployment
- Fix PowerShell encoding issues by replacing emojis with text labels
- Automatically package production-ready files: .next, public, .env.production, server.js, etc.
- ZIP filename uses package.json name and version (e.g., nodemap-1.1.305.zip)
- Add file existence validation and detailed logging for deployment process
- Clean temporary files after ZIP creation

The script creates a complete deployment package (84MB) containing all necessary
files for production deployment, streamlining the build and packaging workflow.
2025-07-29 08:35:13 +02:00
ISA
4755cefdd7 feat: implement device filtering based on GisSystemStatic Allow property
- Add Allow=1 filter in createAndSetDevices.js to hide devices with Allow=0
- Update mapLayersSlice.js setInitialLayers to respect Allow values for visibility
- Modify MapLayersControlPanel.js localStorage initialization to set visibility based on Allow property
- Only systems with Allow=1 are now visible by default, Allow=0 systems are hidden
- Ensures consistent filtering across device markers and layer visibility controls

This change addresses the requirement to hide stations/devices when their corresponding
IdSystem in GisSystemStatic.json has Allow=0, improving the map display by showing
only authorized/allowed systems to users.
2025-07-29 08:11:31 +02:00
ISA
37aec649ee feat: Geräte/Stations-Marker werden nur für Systeme mit Allow=1 angezeigt
- Geräte mit Allow=0 in GisSystemStatic werden auf der Karte ausgeblendet
- Daten bleiben unverändert, nur die Anzeige/Zeichnung ist
2025-07-28 16:21:48 +02:00
ISA
9d7a696f91 feat: Add persistent localStorage for Kabelstrecken (polylines) visibility
- 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.
2025-07-25 13:09:18 +02:00
ISA
56a2e305f1 feat: Dienst Dateien hinzugefügt 2025-07-25 10:15:38 +02:00
ISA
e1bfe7496b fix: Kabelstrecken-Checkbox und Anzeige an TALAS Allow-Status gekoppelt
- Checkbox für Kabelstrecken (polylines) wird deaktiviert, wenn TALAS (IdSystem: 1) Allow: 0 ist
- Polylinien werden auf der Karte nur angezeigt, wenn Allow: 1 für TALAS gesetzt ist
- Synchronisation zwischen UI
2025-07-25 10:08:45 +02:00
88 changed files with 4115 additions and 2804 deletions

View File

@@ -7,7 +7,6 @@ DB_NAME=talas_v5
DB_PORT=3306
# Public Settings (Client braucht IP/Domain) , Variablen mit dem Präfix "NEXT_PUBLIC" ist in Browser sichtbar
NEXT_PUBLIC_DEBUG_LOG=true
@@ -20,9 +19,8 @@ NEXT_PUBLIC_USE_MOCKS=true
# Ein Unterordner in der dort hinter liegenden Ordnerstruktur (z.B. http://talasserver/talas5/nodemap/api/talas_v5_DB/ usw.)
# kann bleiben da der Kunde diesen Unterordner talas:v5_db nicht ändert.
#Füge in deiner .env.local Datei die folgende Zeile hinzu wenn du einen Unterordner verwenden möchtest mit entsprechende Bezeichnung.
# z.B. http://10.10.0.13/talas5/index.aspx -> NEXT_PUBLIC_BASE_PATH=/talas5
# z.B. http://10.10.0.13/xyz/index.aspx -> NEXT_PUBLIC_BASE_PATH=/xyz
NEXT_PUBLIC_BASE_PATH=/talas5
# Oder leer lassen für direkten Zugriff -> NEXT_PUBLIC_BASE_PATH=
# z.B. http://10.10.0.13/talas5/index.aspx -> basePath in config.json auf /talas5 setzen
# z.B. http://10.10.0.13/xyz/index.aspx -> basePath in config.json auf /xyz setzen
# basePath wird jetzt in public/config.json gepflegt
# App-Versionsnummer
NEXT_PUBLIC_APP_VERSION=1.1.300
NEXT_PUBLIC_APP_VERSION=1.1.355

View File

@@ -7,7 +7,6 @@ DB_NAME=talas_v5
DB_PORT=3306
# Public Settings (Client braucht IP/Domain) , Variablen mit dem Präfix "NEXT_PUBLIC" ist in Browser sichtbar
NEXT_PUBLIC_DEBUG_LOG=false
@@ -20,10 +19,9 @@ NEXT_PUBLIC_USE_MOCKS=false
# Ein Unterordner in der dort hinter liegenden Ordnerstruktur (z.B. http://talasserver/talas5/nodemap/api/talas_v5_DB/ usw.)
# kann bleiben da der Kunde diesen Unterordner talas:v5_db nicht ändert.
#Füge in deiner .env.local Datei die folgende Zeile hinzu wenn du einen Unterordner verwenden möchtest mit entsprechende Bezeichnung.
# z.B. http://10.10.0.13/talas5/index.aspx -> NEXT_PUBLIC_BASE_PATH=/talas5
# z.B. http://10.10.0.13/xyz/index.aspx -> NEXT_PUBLIC_BASE_PATH=/xyz
NEXT_PUBLIC_BASE_PATH=/talas5
# Oder leer lassen für direkten Zugriff -> NEXT_PUBLIC_BASE_PATH=
# z.B. http://10.10.0.13/talas5/index.aspx -> basePath in config.json auf /talas5 setzen
# z.B. http://10.10.0.13/xyz/index.aspx -> basePath in config.json auf /xyz setzen
# basePath wird jetzt in public/config.json gepflegt
# App-Versionsnummer
NEXT_PUBLIC_APP_VERSION=1.1.300
NEXT_PUBLIC_APP_VERSION=1.1.355

View File

@@ -250,7 +250,7 @@ das Objekt selbst
### ♻️ Refactor
- Alle hartkodierten `/talas5/`-Pfadangaben entfernt
- Dynamischer `basePath` eingeführt über `.env.local → NEXT_PUBLIC_BASE_PATH`
- Dynamischer `basePath` eingeführt über `public/config.json → basePath`
- Unterstützt jetzt auch den Betrieb ohne Unterverzeichnis
### 🧠 Architektur

View File

@@ -48,6 +48,30 @@ User-ID.
---
## ⚙️ Kartenquellen-Konfiguration (public/config.json)
Die Datei `public/config.json` steuert, welche Kartenquelle (z.B. OSM oder lokale Tiles) für die
Leaflet-Karte verwendet wird.
**Beispiel:**
```json
{
"//info": "tileSources: 'local' für offline, 'osm' für online",
"tileSources": {
"local": "http://localhost/talas5/TileMap/mapTiles/{z}/{x}/{y}.png",
"osm": "https://{s}.tile.openstreetmap.org/{z}/{x}/{y}.png"
},
"active": "osm"
}
```
- Mit `active` kann zwischen Online- und Offline-Karten umgeschaltet werden.
- Die Datei wird beim Start der App automatisch geladen.
- Für Offline-Betrieb muss das lokale Kartenmaterial vorhanden sein (siehe Installationsanleitung).
---
## 🧰 Erstinstallation auf Server
### Voraussetzungen
@@ -61,8 +85,8 @@ User-ID.
(Server-IP mit Port 3000)
- Browser: Chrome ab Version 125.0.6420.142 empfohlen
- Karten Material vorhanden in: `C:\inetpub\wwwroot\talas5\TileMap\mapTiles`
![mapTiles](docs/screenshots/mapTiles.png)
Falls nicht vorhanden hier downloaden: http://10.10.0.28/produkte/TALAS.map/mapTiles.zip
![mapTiles](docs/screenshots/mapTiles.png) Falls nicht vorhanden hier downloaden:
http://10.10.0.28/produkte/TALAS.map/mapTiles.zip
---

6
Start-Dev.ps1 Normal file
View File

@@ -0,0 +1,6 @@
# Navigiere zum Verzeichnis deines Projekts
cd 'C:\inetpub\wwwroot\talas5\nodeMap'
# F<>hre den npm Befehl aus
npm start

1
StartNodeApp.bat Normal file
View File

@@ -0,0 +1 @@
PowerShell -ExecutionPolicy Bypass -File "C:\inetpub\wwwroot\talas5\nodeMap\Start-Dev.ps1"

View File

@@ -1,16 +0,0 @@
describe("setupPOIs Icon-Mapping intern", () => {
it("ordnet korrektes Icon anhand idPoi zu", () => {
const mockPoiData = [{ idPoi: 7, path: "poi-marker-icon-2.png" }];
const iconMap = new Map();
mockPoiData.forEach((item) => iconMap.set(item.idPoi, item.path));
const result = iconMap.get(7);
expect(result).toBe("poi-marker-icon-2.png");
});
it("gibt undefined zurück wenn idPoi nicht existiert", () => {
const iconMap = new Map();
iconMap.set(1, "icon-1.png");
const result = iconMap.get(99);
expect(result).toBeUndefined();
});
});

View File

@@ -1,3 +1,4 @@
import { getDebugLog } from "@/utils/configUtils.js";
// /hooks/layers/useAreaMarkersLayer.js
import { useEffect, useState, useRef } from "react";
import L from "leaflet";
@@ -22,7 +23,13 @@ const useAreaMarkersLayer = (map, oms, apiUrl, onUpdateSuccess) => {
const updateMarkersVisibility = () => {
if (!map || areaMarkers.length === 0) return;
const mapLayersVisibility = JSON.parse(localStorage.getItem("mapLayersVisibility")) || {};
// Kartenspezifischer localStorage-Key verwenden
const mapId = localStorage.getItem("currentMapId");
const userId = localStorage.getItem("currentUserId");
const mapStorageKey =
mapId && userId ? `mapLayersVisibility_m${mapId}_u${userId}` : "mapLayersVisibility";
const mapLayersVisibility = JSON.parse(localStorage.getItem(mapStorageKey)) || {};
const areAllLayersInvisible = Object.values(mapLayersVisibility).every(v => !v);
if (areAllLayersInvisible === prevVisibility.current) return;
@@ -42,7 +49,8 @@ const useAreaMarkersLayer = (map, oms, apiUrl, onUpdateSuccess) => {
updateMarkersVisibility();
const handleStorageChange = event => {
if (event.key === "mapLayersVisibility") {
// Überwache sowohl den alten als auch kartenspezifische Keys
if (event.key === "mapLayersVisibility" || event.key?.startsWith("mapLayersVisibility_")) {
updateMarkersVisibility();
}
};
@@ -64,10 +72,11 @@ const useAreaMarkersLayer = (map, oms, apiUrl, onUpdateSuccess) => {
const data = await response.json();
const editMode = localStorage.getItem("editMode") === "true";
const markers = data.map(item => {
const marker = L.marker([item.x, item.y], {
icon: customIcon,
draggable: true,
draggable: editMode,
customType: "areaMarker",
});
@@ -81,24 +90,26 @@ const useAreaMarkersLayer = (map, oms, apiUrl, onUpdateSuccess) => {
}
);
marker.on("dragend", async e => {
const { lat, lng } = e.target.getLatLng();
try {
await dispatch(
updateAreaThunk({
idLocation: item.idLocation,
idMap: item.idMaps,
newCoords: { x: lat, y: lng },
})
).unwrap();
if (process.env.NEXT_PUBLIC_DEBUG_LOG === "true") {
console.log("✔️ Koordinaten erfolgreich aktualisiert:", { lat, lng });
if (editMode) {
marker.on("dragend", async e => {
const { lat, lng } = e.target.getLatLng();
try {
await dispatch(
updateAreaThunk({
idLocation: item.idLocation,
idMap: item.idMaps,
newCoords: { x: lat, y: lng },
})
).unwrap();
if (getDebugLog()) {
console.log("✔️ Koordinaten erfolgreich aktualisiert:", { lat, lng });
}
onUpdateSuccess?.(); // optionaler Callback
} catch (error) {
console.error("❌ Fehler beim Aktualisieren der Koordinaten:", error);
}
onUpdateSuccess?.(); // optionaler Callback
} catch (error) {
console.error("❌ Fehler beim Aktualisieren der Koordinaten:", error);
}
});
});
}
return marker;
});

View File

@@ -1,3 +1,4 @@
import { getDebugLog } from "@/utils/configUtils.js";
// components/contextmenu/useMapContextMenu.js
import { toast } from "react-toastify";
import { zoomIn, zoomOut, centerHere } from "../../utils/zoomAndCenterUtils";
@@ -71,7 +72,7 @@ const addItemsToMapContextMenu = (
if (!menuItemAdded && map && map.contextmenu) {
const editMode = localStorage.getItem("editMode") === "true";
if (editMode) {
if (process.env.NEXT_PUBLIC_DEBUG_LOG === "true") {
if (getDebugLog()) {
console.log("editMode localStorage:", localStorage.getItem("editMode"));
}

View File

@@ -30,30 +30,30 @@ const useDynamicDeviceLayers = (map, GisSystemStatic, mapLayersVisibility, prior
if (!map || GisSystemStatic.length === 0) return;
GisSystemStatic.forEach(({ Name, IdSystem }) => {
const key = `system-${IdSystem}`; // Einheitlicher Key
if (!layerRefs.current[key]) {
layerRefs.current[key] = new L.LayerGroup().addTo(map);
const key = `system-${IdSystem}`;
// LayerGroup immer komplett neu erstellen, um doppelte Marker zu verhindern
if (layerRefs.current[key]) {
if (map.hasLayer(layerRefs.current[key])) {
map.removeLayer(layerRefs.current[key]);
}
layerRefs.current[key].clearLayers();
delete layerRefs.current[key];
}
layerRefs.current[key] = new L.LayerGroup();
layerRefs.current[key].addTo(map);
createAndSetDevices(
IdSystem,
newMarkers => {
const oldMarkers = markerStates[key];
// Entferne alte Marker aus Karte und OMS
if (oldMarkers && Array.isArray(oldMarkers)) {
oldMarkers.forEach(marker => {
if (map.hasLayer(marker)) {
map.removeLayer(marker);
}
if (oms) {
oms.removeMarker(marker);
}
// Füge neue Marker der LayerGroup hinzu (nur Geräte-Marker)
if (layerRefs.current[key]) {
layerRefs.current[key].clearLayers();
// Nur eindeutige Marker hinzufügen
const uniqueMarkers = Array.isArray(newMarkers) ? Array.from(new Set(newMarkers)) : [];
uniqueMarkers.forEach(marker => {
marker.addTo(layerRefs.current[key]);
});
}
// Neue Marker setzen
setMarkerStates(prev => ({ ...prev, [key]: newMarkers }));
},
GisSystemStatic,
@@ -69,20 +69,21 @@ const useDynamicDeviceLayers = (map, GisSystemStatic, mapLayersVisibility, prior
if (!map) return;
const editMode = localStorage.getItem("editMode") === "true";
Object.entries(markerStates).forEach(([key, markers]) => {
const isVisible = mapLayersVisibility[key];
markers.forEach(marker => {
const hasLayer = map.hasLayer(marker);
if (editMode || !isVisible) {
if (hasLayer) map.removeLayer(marker);
} else {
if (!hasLayer) marker.addTo(map);
Object.entries(layerRefs.current).forEach(([key, layerGroup]) => {
const isVisible = mapLayersVisibility[key] ?? true;
if (editMode || isVisible === false) {
if (map.hasLayer(layerGroup)) {
map.removeLayer(layerGroup);
}
});
} else if (isVisible === true) {
if (!map.hasLayer(layerGroup)) {
layerGroup.addTo(map);
}
}
});
// Overlapping-Check bleibt wie gehabt
const allMarkers = Object.values(markerStates).filter(Array.isArray).flat();
checkOverlappingMarkers(map, allMarkers, plusRoundIcon);
}, [map, markerStates, mapLayersVisibility]);

View File

View File

@@ -10,6 +10,9 @@ 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 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";
@@ -35,7 +38,7 @@ import { useSelector, useDispatch } from "react-redux";
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 { selectMapLayersState, setLayerVisibility } 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";
@@ -54,6 +57,7 @@ import {
import {
selectPolylineVisible,
setPolylineVisible,
initializePolylineFromLocalStorageThunk,
} from "@/redux/slices/database/polylines/polylineLayerVisibleSlice.js";
import { selectGisStationsStaticDistrict } from "@/redux/slices/webservice/gisStationsStaticDistrictSlice.js";
import {
@@ -84,6 +88,7 @@ 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 }) => {
//-------------------------------
@@ -96,6 +101,14 @@ const MapComponent = ({ locations, onLocationUpdate, lineCoordinates }) => {
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);
@@ -107,7 +120,6 @@ const MapComponent = ({ locations, onLocationUpdate, lineCoordinates }) => {
const zoomTrigger = useSelector(state => state.zoomTrigger.trigger);
const poiReadTrigger = useSelector(state => state.poiReadFromDbTrigger.trigger);
const GisStationsStaticDistrict = useSelector(selectGisStationsStaticDistrict);
const GisSystemStatic = useSelector(selectGisSystemStatic);
const gisSystemStaticStatus = useSelector(state => state.gisSystemStatic.status);
const polylineEventsDisabled = useSelector(state => state.polylineEventsDisabled.disabled);
const mapLayersVisibility = useSelector(selectMapLayersState) || {};
@@ -140,6 +152,12 @@ const MapComponent = ({ locations, onLocationUpdate, lineCoordinates }) => {
const [map, setMap] = useState(null); // Zustand der Karteninstanz
const [oms, setOms] = useState(null); // State für OMS-Instanz
// 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"
@@ -200,6 +218,16 @@ const MapComponent = ({ locations, onLocationUpdate, lineCoordinates }) => {
}
};
//-----------------------------Map Initialisierung----------------
// Default map options for Leaflet
const mapOptions = {
center: currentCenter,
zoom: currentZoom,
zoomControl: true,
contextmenu: true,
contextmenuWidth: 180,
contextmenuItems: [],
};
useInitializeMap(
map,
mapRef,
@@ -208,10 +236,85 @@ const MapComponent = ({ locations, onLocationUpdate, lineCoordinates }) => {
setMenuItemAdded,
addItemsToMapContextMenu,
hasRights,
value => dispatch(setDisabled(value))
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 => ({
@@ -222,6 +325,7 @@ const MapComponent = ({ locations, onLocationUpdate, lineCoordinates }) => {
setLinePositions(transformed);
}
}, [linesData]);
//--------------------------------------------
useEffect(() => {
dispatch(fetchPoiIconsDataThunk());
@@ -309,72 +413,106 @@ const MapComponent = ({ locations, onLocationUpdate, lineCoordinates }) => {
//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
markers.forEach(marker => {
(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
const { markers: newMarkers, polylines: newPolylines } = setupPolylines(
map,
linePositions,
lineColors,
tooltipContents,
setNewCoords,
tempMarker,
currentZoom,
currentCenter,
polylineVisible // kommt aus Redux
);
// 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
);
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";
(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.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();
polyline.on("mouseover", e => {
const tooltip = polyline.getTooltip();
if (tooltip) {
const mousePos = e.containerPoint;
const mapSize = map.getSize();
let direction = "right";
let direction = "right";
if (mousePos.x > mapSize.x - 100) {
direction = "left";
} else if (mousePos.x < 100) {
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";
}
if (mousePos.y > mapSize.y - 100) {
direction = "top";
} else if (mousePos.y < 100) {
direction = "bottom";
}
tooltip.options.direction = direction;
polyline.openTooltip(e.latlng);
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);
}
});
}
});
polyline.on("mouseout", () => {
polyline.closeTooltip();
});
});
cleanupMarkers(markers, oms);
setMarkers(newMarkers);
setPolylines(newPolylines);
cleanupPolylinesForMemory(polylines, map);
setPolylines([]);
console.log("[MapComponent/useEffect] setPolylines ([]), alle Polylinien entfernt");
}
};
updatePolylines();
}, [
map,
linePositions,
@@ -384,6 +522,8 @@ const MapComponent = ({ locations, onLocationUpdate, lineCoordinates }) => {
newCoords,
tempMarker,
polylineVisible,
isTalasAllowed,
poiLayerVisible,
]);
//--------------------------------------------
@@ -391,7 +531,7 @@ const MapComponent = ({ locations, onLocationUpdate, lineCoordinates }) => {
//Test in useEffect
useEffect(() => {
if (map) {
if (process.env.NEXT_PUBLIC_DEBUG_LOG === "true") {
if (getDebugLog()) {
console.log("🗺️ Map-Einstellungen werden wiederhergestellt...");
}
restoreMapSettings(map);
@@ -400,7 +540,7 @@ const MapComponent = ({ locations, onLocationUpdate, lineCoordinates }) => {
//--------------------------------------------
useEffect(() => {
if (map) {
if (process.env.NEXT_PUBLIC_DEBUG_LOG === "true") {
if (getDebugLog()) {
console.log("map in MapComponent: ", map);
}
const handleMapMoveEnd = event => {
@@ -433,7 +573,7 @@ const MapComponent = ({ locations, onLocationUpdate, lineCoordinates }) => {
const station = points.find(s => s.Area_Name === selectedArea);
if (station) {
if (process.env.NEXT_PUBLIC_DEBUG_LOG === "true") {
if (getDebugLog()) {
console.log("📌 Gefundene Station:", station);
}
map.flyTo([station.X, station.Y], 14);
@@ -479,7 +619,7 @@ const MapComponent = ({ locations, onLocationUpdate, lineCoordinates }) => {
//--------------------------------------------
useEffect(() => {
if (map) {
if (process.env.NEXT_PUBLIC_DEBUG_LOG === "true") {
if (getDebugLog()) {
console.log("6- Karteninstanz (map) wurde jetzt erfolgreich initialisiert");
}
}
@@ -492,7 +632,7 @@ const MapComponent = ({ locations, onLocationUpdate, lineCoordinates }) => {
map.whenReady(() => {
timeoutId = setTimeout(() => {
if (map.contextmenu) {
if (process.env.NEXT_PUBLIC_DEBUG_LOG === "true") {
if (getDebugLog()) {
console.log("Contextmenu ist vorhanden");
}
} else {
@@ -523,7 +663,7 @@ const MapComponent = ({ locations, onLocationUpdate, lineCoordinates }) => {
const handleLocationUpdate = async (idLocation, idMap, newCoords) => {
try {
await dispatch(updateAreaThunk({ idLocation, idMap, newCoords })).unwrap();
if (process.env.NEXT_PUBLIC_DEBUG_LOG === "true") {
if (getDebugLog()) {
console.log("Koordinaten erfolgreich aktualisiert:", result);
}
} catch (error) {
@@ -547,14 +687,14 @@ const MapComponent = ({ locations, onLocationUpdate, lineCoordinates }) => {
// Entferne alle Marker aus der Karte
if (!map) return; // Sicherstellen, dass map existiert
areaMarkers.forEach(marker => {
(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
areaMarkers.forEach(marker => {
(Array.isArray(areaMarkers) ? areaMarkers : []).forEach(marker => {
if (!map.hasLayer(marker)) {
marker.addTo(map); // Layer hinzufügen
}
@@ -615,11 +755,10 @@ const MapComponent = ({ locations, onLocationUpdate, lineCoordinates }) => {
}, [dispatch]);
//--------------------------------------------
// Beim ersten Client-Render den Wert aus localStorage laden
useEffect(() => {
const storedPolylineVisible = localStorage.getItem("polylineVisible") === "true";
dispatch(setPolylineVisible(storedPolylineVisible));
}, [dispatch]);
// (Initialisierung erfolgt in MapLayersControlPanel)
//--------------------------------------------
// MapComponent reagiert nicht mehr direkt auf localStorage-Events für polylineVisible
//--------------------------------------------
useEffect(() => {
if (statusStaticDistrict === "idle") {
@@ -693,7 +832,7 @@ const MapComponent = ({ locations, onLocationUpdate, lineCoordinates }) => {
// console.log(`⏳ Redux Countdown: ${countdown} Sekunden`);
if (countdown <= 2) {
if (process.env.NEXT_PUBLIC_DEBUG_LOG === "true") {
if (getDebugLog()) {
console.log("🚀 Kontextmenü wird wegen Countdown < 2 geschlossen.");
}
dispatch(closePolylineContextMenu());
@@ -738,7 +877,9 @@ const MapComponent = ({ locations, onLocationUpdate, lineCoordinates }) => {
useEffect(() => {
if (poiTypStatus === "succeeded" && Array.isArray(poiTypData)) {
const map = new Map();
poiTypData.forEach(item => map.set(item.idPoiTyp, item.name));
(Array.isArray(poiTypData) ? poiTypData : []).forEach(item =>
map.set(item.idPoiTyp, item.name)
);
setPoiTypMap(map);
}
}, [poiTypData, poiTypStatus]);
@@ -749,30 +890,6 @@ const MapComponent = ({ locations, onLocationUpdate, lineCoordinates }) => {
}
}, [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(() => {
@@ -848,6 +965,21 @@ const MapComponent = ({ locations, onLocationUpdate, lineCoordinates }) => {
}, [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]);
//---------------------------------------------
//--------------------------------------------
return (
@@ -904,7 +1036,10 @@ const MapComponent = ({ locations, onLocationUpdate, lineCoordinates }) => {
</div>
{GisStationsStaticDistrict && GisStationsStaticDistrict.Points?.length > 0 && (
<MapLayersControlPanel className="z-50" />
<MapLayersControlPanel
className="z-50"
handlePolylineCheckboxChange={handlePolylineCheckboxChange}
/>
)}
<CoordinateInput onCoordinatesSubmit={handleCoordinatesSubmit} />

View File

@@ -2,12 +2,58 @@
import { useEffect } from "react";
import { initializeMap } from "../../../utils/initializeMap";
const useInitializeMap = (map, mapRef, setMap, setOms, setMenuItemAdded, addItemsToMapContextMenu, hasRights, setPolylineEventsDisabled) => {
const useInitializeMap = (
map,
mapRef,
setMap,
setOms,
setMenuItemAdded,
addItemsToMapContextMenu,
hasRights,
setPolylineEventsDisabled,
mapOptions
) => {
useEffect(() => {
if (mapRef.current && !map) {
initializeMap(mapRef, setMap, setOms, setMenuItemAdded, addItemsToMapContextMenu, hasRights, setPolylineEventsDisabled);
let cancelled = false;
function tryInit(firstAttempt = true) {
if (cancelled) return;
// Only try to initialize if mapRef.current is ready and in DOM
if (
mapRef.current &&
mapRef.current instanceof HTMLElement &&
document.body.contains(mapRef.current) &&
!map &&
!mapRef.current._leaflet_id
) {
try {
const result = initializeMap(
mapRef.current, // pass DOM node, not ref
setMenuItemAdded,
addItemsToMapContextMenu,
hasRights,
setPolylineEventsDisabled,
firstAttempt // log error only on first real attempt
);
if (result && result.map && result.oms) {
setMap(result.map);
setOms(result.oms);
}
} catch (error) {
if (process.env.NODE_ENV === "development") {
// eslint-disable-next-line no-console
console.warn("Map initialization error:", error);
}
}
} else if (!map && !cancelled) {
// If not ready, just retry after a short delay, do not call initializeMap at all
setTimeout(() => tryInit(false), 50);
}
}
}, [mapRef, map, hasRights, setPolylineEventsDisabled]);
tryInit(true);
return () => {
cancelled = true;
};
}, [mapRef, map, hasRights, setPolylineEventsDisabled, mapOptions]);
};
export default useInitializeMap;

View File

@@ -1,4 +1,5 @@
// /components/uiWidgets/mapLayersControlPanel/MapLayersControlPanel.js
import { getDebugLog } from "../../../utils/configUtils";
import React, { useEffect, useState } from "react";
import { setSelectedArea } from "@/redux/slices/selectedAreaSlice";
import EditModeToggle from "@/components/uiWidgets/mapLayersControlPanel/EditModeToggle";
@@ -13,8 +14,10 @@ import { selectMapLayersState, setLayerVisibility } from "@/redux/slices/mapLaye
import { setVisible } from "@/redux/slices/database/pois/poiLayerVisibleSlice";
import { incrementZoomTrigger } from "@/redux/slices/zoomTriggerSlice";
function MapLayersControlPanel() {
function MapLayersControlPanel({ handlePolylineCheckboxChange }) {
const [editMode, setEditMode] = useState(false); // Zustand für editMode
const [localStorageLoaded, setLocalStorageLoaded] = useState(false); // Tracking ob localStorage geladen wurde
const kabelstreckenVisible = useSelector(selectPolylineVisible); // Nur noch Redux
const poiVisible = useSelector(state => state.poiLayerVisible.visible);
const setPoiVisible = value => dispatch(setVisible(value));
const dispatch = useDispatch();
@@ -24,46 +27,60 @@ function MapLayersControlPanel() {
const GisStationsStaticDistrict = useSelector(selectGisStationsStaticDistrict) || [];
const GisSystemStatic = useSelector(selectGisSystemStatic) || [];
const polylineVisible = useSelector(selectPolylineVisible);
// Debug: Kabelstrecken state verfolgen
useEffect(() => {
console.log("🎯 kabelstreckenVisible state changed to:", kabelstreckenVisible);
}, [kabelstreckenVisible]);
const handlePolylineCheckboxChange = event => {
const checked = event.target.checked;
dispatch(setPolylineVisible(checked));
localStorage.setItem("polylineVisible", checked);
// Prüfen, ob TALAS (IdSystem 1) erlaubt & sichtbar auf Karte (Allow + Map)
const isTalasAllowed = Array.isArray(GisSystemStatic)
? GisSystemStatic.some(
system => system.IdSystem === 1 && system.Allow === 1 && system.Map === 1
)
: false;
if (checked) {
dispatch(setLayerVisibility({ layer: "TALAS", visibility: true }));
localStorage.setItem(
"mapLayersVisibility",
JSON.stringify({ ...mapLayersVisibility, TALAS: true })
);
}
};
// handlePolylineCheckboxChange kommt jetzt als Prop von MapComponent
useEffect(() => {
// LocalStorage Werte laden
// LocalStorage Werte beim ersten Laden der Komponente wiederherstellen (nur für POI und mapLayersVisibility, nicht mehr für Kabelstrecken)
const storedPoiVisible = localStorage.getItem("poiVisible");
if (storedPoiVisible !== null) {
setPoiVisible(storedPoiVisible === "true");
}
const storedPolylineVisible = localStorage.getItem("polylineVisible");
if (storedPolylineVisible !== null) {
dispatch(setPolylineVisible(storedPolylineVisible === "true"));
}
// Layer-Sichtbarkeiten aus localStorage laden
const storedMapLayersVisibility = localStorage.getItem("mapLayersVisibility");
// Kartenspezifischer localStorage-Key verwenden
const mapId = localStorage.getItem("currentMapId");
const userId = localStorage.getItem("currentUserId");
const mapStorageKey =
mapId && userId ? `mapLayersVisibility_m${mapId}_u${userId}` : "mapLayersVisibility";
const storedMapLayersVisibility = localStorage.getItem(mapStorageKey);
if (storedMapLayersVisibility) {
const parsedVisibility = JSON.parse(storedMapLayersVisibility);
Object.keys(parsedVisibility).forEach(key => {
dispatch(setLayerVisibility({ layer: key, visibility: parsedVisibility[key] }));
});
} else {
// Initialisiere mapLayersVisibility basierend auf Allow & Map (nur Systeme mit Map===1 anzeigen)
if (Array.isArray(GisSystemStatic)) {
const initialVisibility = {};
GisSystemStatic.forEach(system => {
const systemKey = `system-${system.IdSystem}`;
const visibility = system.Allow === 1 && system.Map === 1;
if (visibility) {
initialVisibility[systemKey] = visibility;
dispatch(setLayerVisibility({ layer: systemKey, visibility }));
}
});
localStorage.setItem(mapStorageKey, JSON.stringify(initialVisibility));
}
}
// EditMode lesen
const storedEditMode = localStorage.getItem("editMode");
setEditMode(storedEditMode === "true");
}, [setPoiVisible, dispatch]); // ✅ `setMapLayersVisibility` entfernt
setLocalStorageLoaded(true);
}, []); // Läuft nur beim Mount
const handleAreaChange = event => {
const selectedIndex = event.target.options.selectedIndex;
@@ -72,8 +89,13 @@ function MapLayersControlPanel() {
};
useEffect(() => {
// Allowed systems jetzt nach Allow && Map filtern
const allowedSystems = Array.isArray(GisSystemStatic)
? new Set(GisSystemStatic.filter(system => system.Allow === 1).map(system => system.IdSystem))
? new Set(
GisSystemStatic.filter(system => system.Allow === 1 && system.Map === 1).map(
system => system.IdSystem
)
)
: new Set();
const seenNames = new Set();
@@ -97,7 +119,7 @@ function MapLayersControlPanel() {
const seenSystemNames = new Set();
const filteredSystems = Array.isArray(GisSystemStatic)
? GisSystemStatic.filter(item => {
const isUnique = !seenSystemNames.has(item.Name) && item.Allow === 1;
const isUnique = !seenSystemNames.has(item.Name) && item.Allow === 1 && item.Map === 1; // <— Map Bedingung hinzugefügt
if (isUnique) {
seenSystemNames.add(item.Name);
}
@@ -108,8 +130,8 @@ function MapLayersControlPanel() {
setSystemListing(
filteredSystems.map((system, index) => ({
id: index + 1,
name: system.Name, // Verwende den Originalnamen für die Anzeige
key: `system-${system.IdSystem}`, // Internen Schlüssel für die MapLayersVisibility-Logik
name: system.Name, // Anzeige
key: `system-${system.IdSystem}`, // interner Schlüssel
}))
);
}, [GisStationsStaticDistrict, GisSystemStatic]);
@@ -117,13 +139,38 @@ function MapLayersControlPanel() {
const handleCheckboxChange = (key, event) => {
if (editMode) return;
const { checked } = event.target;
dispatch(setLayerVisibility({ layer: key, visibility: checked }));
localStorage.setItem(
"mapLayersVisibility",
JSON.stringify({ ...mapLayersVisibility, [key]: checked })
// Debug-Ausgabe
const params = new URLSearchParams(window.location.search);
const mapId = params.get("m");
const userId = params.get("u");
const polylineKey =
mapId && userId ? `polylineVisible_m${mapId}_u${userId}` : "polylineVisible";
console.log(
"[UI/handleCheckboxChange] key:",
key,
"checked:",
checked,
"Redux:",
kabelstreckenVisible,
"localStorage:",
localStorage.getItem(polylineKey)
);
dispatch(setLayerVisibility({ layer: key, visibility: checked }));
const mapId2 = localStorage.getItem("currentMapId");
const userId2 = localStorage.getItem("currentUserId");
const mapStorageKey =
mapId2 && userId2 ? `mapLayersVisibility_m${mapId2}_u${userId2}` : "mapLayersVisibility";
localStorage.setItem(mapStorageKey, JSON.stringify({ ...mapLayersVisibility, [key]: checked }));
// Wenn TALAS (system-1) deaktiviert wird, Kabelstrecken deaktivieren
if (key === "system-1" && !checked) {
localStorage.setItem("kabelstreckenVisible", "false");
localStorage.setItem("polylineVisible", "false");
dispatch(setPolylineVisible(false));
setTimeout(() => {
const polylineEvent = new Event("polylineVisibilityChanged");
window.dispatchEvent(polylineEvent);
}, 50);
}
setTimeout(() => {
const event = new Event("visibilityChanged");
window.dispatchEvent(event);
@@ -133,7 +180,7 @@ function MapLayersControlPanel() {
const handlePoiCheckboxChange = event => {
const { checked } = event.target;
setPoiVisible(checked);
localStorage.setItem("poiVisible", checked); // Store POI visibility in localStorage
localStorage.setItem("poiVisible", checked.toString()); // Store POI visibility in localStorage
};
const handleIconClick = () => {
@@ -143,7 +190,7 @@ function MapLayersControlPanel() {
//------------------------------
useEffect(() => {
if (process.env.NEXT_PUBLIC_DEBUG_LOG === "true") {
if (getDebugLog()) {
window.__debug = window.__debug || {};
window.__debug.gisStations = GisStationsStaticDistrict;
}
@@ -175,7 +222,7 @@ function MapLayersControlPanel() {
}
return isUnique;
});
if (process.env.NEXT_PUBLIC_DEBUG_LOG === "true") {
if (getDebugLog()) {
console.log("📌 stationListing aktualisiert:", filteredAreas);
}
}, [GisStationsStaticDistrict, GisSystemStatic]);
@@ -209,13 +256,20 @@ function MapLayersControlPanel() {
style={{ minWidth: "150px", maxWidth: "200px" }}
>
<option value="Station wählen">Station wählen</option>
{/*
...new Map(
(GisStationsStaticDistrict.Points || [])
.filter(p => !!p.Area_Name)
.map(p => [p.Area_Name, p])
).values(),
*/}
{[
...new Map(
(GisStationsStaticDistrict.Points || [])
.filter(p => !!p.Area_Name)
.map(p => [p.Area_Name, p])
).values(),
].map((item, index) => (
].map(item => (
<option key={item.Area_Name} value={item.IdLD}>
{item.Area_Name}
</option>
@@ -255,9 +309,10 @@ function MapLayersControlPanel() {
<div className="flex items-center">
<input
type="checkbox"
checked={polylineVisible} // Zustand für Kabelstrecken
onChange={handlePolylineCheckboxChange}
checked={kabelstreckenVisible}
onChange={e => handlePolylineCheckboxChange(e.target.checked)}
id="polyline-checkbox"
disabled={!isTalasAllowed || editMode}
/>
<label htmlFor="polyline-checkbox" className="text-sm ml-2">
Kabelstrecken
@@ -279,25 +334,6 @@ function MapLayersControlPanel() {
POIs
</label>
</div>
{/* Areas
<div className="flex items-center">
<input type="checkbox" checked={areaVisible} onChange={handleAreaCheckboxChange} id="area-checkbox" />
<label htmlFor="area-checkbox" className="text-sm ml-2">
Bereiche
</label>
</div>
*/}
{/* Standorte
<div className="flex items-center">
<input type="checkbox" checked={standordVisible} onChange={handleStandorteCheckboxChange} id="area-checkbox" />
<label htmlFor="area-checkbox" className="text-sm ml-2">
Standorte
</label>
</div>
*/}
</div>
</div>
</div>

View File

@@ -1,4 +1,11 @@
// config/paths.js
const basePathRaw = process.env.NEXT_PUBLIC_BASE_PATH || "";
const BASE_PATH = basePathRaw.replace(/^\/|\/$/g, "");
export const BASE_URL = BASE_PATH ? `/${BASE_PATH}` : "";
let __configCache;
export async function getBaseUrl() {
if (__configCache) return __configCache;
const res = await fetch("/config.json");
if (!res.ok) throw new Error("config.json konnte nicht geladen werden");
const config = await res.json();
const basePath = (config.basePath || "").replace(/^\/|\/$/g, "");
__configCache = basePath ? `/${basePath}` : "";
return __configCache;
}

View File

@@ -1,17 +0,0 @@
const { defineConfig } = require("cypress");
module.exports = defineConfig({
e2e: {
setupNodeEvents(on, config) {
// Node-Event-Listeners hier konfigurieren
},
experimentalStudio: true, // Studio aktivieren
},
component: {
devServer: {
framework: "next",
bundler: "webpack",
},
},
});

View File

@@ -1,63 +0,0 @@
describe("contextmenuTest", () => {
it("contetmenu Station öffnen (Tab)", () => {
cy.log("Test startet jetzt");
// 1. Viewport einstellen
cy.viewport(1920, 1080);
cy.log("Viewport eingestellt auf 1920x1080");
// 2. Seite besuchen
cy.visit("http://10.10.0.13:3000/?m=12&u=484");
cy.wait(5000); // Wartezeit nach dem Laden
cy.log("Seite geöffnet");
// 3. Sicherstellen, dass die Karte geladen ist
cy.get("#map", { timeout: 15000 }).should("be.visible");
cy.log("Karte geladen");
// 4. Wartezeit zum Stabilisieren der Karte
cy.wait(2000);
// 5. Marker suchen und Rechtsklick simulieren
cy.get('img[src*="img/icons/marker-icon-20.svg"]') // Marker suchen
.filter(":visible") // Nur sichtbare Marker
.first() // Ersten Marker auswählen
.scrollIntoView() // Marker in den sichtbaren Area scrollen
.should("be.visible") // Sicherstellen, dass Marker sichtbar ist
.trigger("mouseover") // Mouseover simulieren
.wait(500) // Wartezeit nach Mouseover
.rightclick({ force: true }); // Rechtsklick auf den Marker
cy.log("Rechtsklick auf Marker ausgeführt");
// Screenshot nach Rechtsklick zum Debugging
// cy.screenshot("nach-rechtsklick");
// 6. Kontextmenü prüfen mit explizitem Selektor
cy.get(".leaflet-contextmenu-item") // Suche alle Menüeinträge mit der Klasse
.contains("Station öffnen (Tab)", { timeout: 5000 }) // Prüfe Text innerhalb des Eintrags
.should("be.visible"); // Sichtbarkeit sicherstellen
cy.log("Menüeintrag gefunden");
// 7. URL abfangen und testen, bevor der Tab geöffnet wird
const targetUrl = "http://10.10.0.13/talas5/devices/cpl.aspx?ver=35&kue=24&id=50922";
// HTTP-Anfrage zur Überprüfung des Status
cy.request(targetUrl).then((response) => {
expect(response.status).to.eq(200); // Erwartet HTTP 200 OK
cy.log("URL ist erreichbar, Status 200");
});
// 8. Menüeintrag auswählen (öffnet den neuen Tab)
cy.get(".leaflet-contextmenu-item") // Explizit Menüeintrag mit Klasse auswählen
.contains("Station öffnen (Tab)")
.click(); // Menüeintrag anklicken
cy.log("Menüeintrag ausgewählt");
// 9. Klick auf die Karte, um Kontextmenü zu schließen
cy.get("#map").click(100, 100); // Klick auf eine leere Stelle
cy.log("Kontextmenü geschlossen");
// 10. Optionaler Screenshot nach Abschluss
// cy.screenshot("test-abgeschlossen");
});
});

View File

@@ -1,54 +0,0 @@
describe("GMA Markers Layer", () => {
before(() => {});
it("Der Test stellt sicher, dass das GMA Tooltip-Element für 'Rastede' angezeigt ist und die erwarteten Werte wie LT:, FBT:, GT: und RLF: enthält.", () => {
// Testbeschreibung: Dieser Test überprüft, ob der Tooltip selbst korrekt dargestellt wird und den erwarteten Inhalt anzeigt.
// Besuche die Map-Seite
//cy.visit("http://10.10.0.13:3000/?m=12&u=484"); // Passe die URL an
cy.visit("http://127.0.0.1:3000/?m=12&u=484");
cy.contains(".leaflet-tooltip", "Rastede")
// Wählt das Tooltip-Element mit der Klasse `leaflet-tooltip`, das den Text "Rastede" enthält.
.first();
cy.get(".leaflet-tooltip")
// Wählt das Tooltip-Element erneut aus, um weitere Überprüfungen durchzuführen.
.should("be.visible")
// Überprüft, ob das Tooltip sichtbar ist.
.and("contain", "LT:")
// Stellt sicher, dass der Tooltip den Text "LT :" enthält.
.and("contain", "FBT:")
// Stellt sicher, dass der Tooltip auch den Text "FBT:" enthält.
.and("contain", "GT:")
// Stellt sicher, dass der Tooltip auch den Text "GT:" enthält.
.and("contain", "RLF:");
// Stellt sicher, dass der Tooltip auch den Text "RLF:" enthält.
});
it("should open context menu on right-click on tooltip", () => {
// Testbeschreibung: Dieser Test überprüft, ob ein Rechtsklick auf den Tooltip das Kontextmenü öffnet.
// Besuche die Map-Seite
cy.visit("http://127.0.0.1:3000/?m=12&u=484"); // Passe die URL an
//warte 2 Sekunden
cy.wait(2000);
cy.contains(".leaflet-tooltip", "Rastede")
// Wählt das Tooltip-Element mit der Klasse `leaflet-tooltip`, das den Text "Rastede" enthält.
.first()
.should("be.visible") // Überprüft, ob das Tooltip sichtbar ist.
.trigger("contextmenu"); // Führt einen Rechtsklick (Kontextmenü-Ereignis) auf das Tooltip aus.
cy.get(".custom-context-menu")
// Wählt das Element aus, das das Kontextmenü darstellt.
.should("be.visible") // Überprüft, ob das Kontextmenü sichtbar ist.
.and("contain", "Station öffnen (Tab)") // Überprüft, ob der Eintrag "Station öffnen (Tab)" vorhanden ist.
.and("contain", "Koordinaten anzeigen") // Überprüft, ob der Eintrag "Koordinaten anzeigen" vorhanden ist.
.and("contain", "Reinzoomen") // Überprüft, ob der Eintrag "Reinzoomen" vorhanden ist.
.and("contain", "Rauszoomen") // Überprüft, ob der Eintrag "Rauszoomen" vorhanden ist.
.and("contain", "Hier zentrieren"); // Überprüft, ob der Eintrag "Hier zentrieren" vorhanden ist.
});
//-----------------------------------------------
});

View File

@@ -1,29 +0,0 @@
//TDD Test Driven Development
// Dieser Test überprüft die Karteninteraktion: Eingabe von Koordinaten und Zentrieren der Karte
// Schritte:
// 1. Öffnen der Karte auf einer bestimmten Seite.
// 2. Eingeben von Koordinaten in ein Eingabefeld.
// 3. Klicken auf einen Button, um die Karte zu den Koordinaten zu zoomen.
// 4. Überprüfen, ob die Karte korrekt zentriert wurde.
//--------------------------------------------------------------Test4 git config --global user.email "ismailali1553@gmail.com" in Terminal eingegeben und
// benutzer email adresse eingegeben git config --global user.name "ismailali1553"
describe("Karteninteraktion", () => {
it("zoomt zu den eingegebenen Koordinaten", () => {
// Öffne die Seite mit der Karte
cy.visit("http://127.0.0.1:3000/?m=12&u=484"); // Passe den Pfad an deine Karte an
// Gebe Koordinaten in das Eingabefeld ein
cy.get('input[placeholder="Koordinaten eingeben (lat,lng)"]').type("52.52,13.405");
// Klicke auf den Button "Zu Marker zoomen"
cy.get("button").contains("Zu Marker zoomen").click();
// Überprüfe, ob die Karte die eingegebenen Koordinaten korrekt zentriert hat
cy.window().then((win) => {
const map = win.map; // Zugriff auf die Leaflet-Instanz
const center = map.getCenter(); // Aktuelles Zentrum der Karte abrufen
expect(center.lat).to.be.closeTo(52.52, 0.01); // Latitude überprüfen
expect(center.lng).to.be.closeTo(13.405, 0.01); // Longitude überprüfen
});
});
});

View File

@@ -1,20 +0,0 @@
//cypress/e2e/poiUpdateModal.cy.js
describe("POI bearbeiten Typ-Auswahl prüfen", () => {
beforeEach(() => {
cy.visit("http://localhost:3000/?m=12&u=484");
cy.get(".leaflet-container", { timeout: 10000 }).should("be.visible");
cy.get(".leaflet-marker-icon", { timeout: 10000 }).should("have.length.greaterThan", 0);
});
it("sollte beim Öffnen des Modals den richtigen POI-Typ anzeigen", () => {
cy.get('svg[aria-label="Bearbeitungsmodus aktivieren"]').click({ force: true });
cy.wait(5000);
cy.get('img[src="/img/icons/pois/poi-marker-icon-4.png"]', { timeout: 10000 }).should("be.visible");
cy.get('img[src="/img/icons/pois/poi-marker-icon-4.png"]').first().rightclick({ force: true });
cy.contains("POI Bearbeiten").click({ force: true });
cy.get("#idPoiTyp", { timeout: 10000 }).should("exist").find("[class*='singleValue']").should("not.contain.text", "Typ auswählen");
});
});

View File

@@ -1,35 +0,0 @@
describe("TK-Komponenten", () => {
before(() => {
// Lade die Seite nur einmal vor allen Tests
cy.visit("http://10.10.0.13:3000/?m=12&u=484");
//cy.wait(5000); // Wartezeit, bis die Seite vollständig geladen ist, cypress macht automatisch , alsobrauchen wir im moment kein wait() wenn cy. schafft
});
it("soll alle Tests in Reihenfolge ausführen", () => {
// Test 1: Sicherstellen, dass die Checkbox vorhanden und sichtbar ist
cy.get("input[type='checkbox'][id='system-10']")
.should("exist")
.and("be.visible")
.then(() => {
cy.log("Die Checkbox mit ID 'system-10' ist vorhanden und sichtbar.");
});
// Test 2: Sicherstellen, dass die Checkbox aktiviert ist
cy.get("input[type='checkbox'][id='system-10']").then(($checkbox) => {
if (!$checkbox.prop("checked")) {
// Falls die Checkbox nicht aktiviert ist, aktiviere sie
cy.wrap($checkbox).check({ force: true });
cy.log("Die Checkbox war deaktiviert und wurde jetzt aktiviert.");
} else {
cy.log("Die Checkbox ist bereits aktiviert.");
}
});
// Test 3: Checkbox deaktivieren und Marker verschwinden lassen
cy.get("input[type='checkbox'][id='system-10']")
.uncheck({ force: true })
.then(() => {
cy.log("Die Checkbox wurde deaktiviert.");
});
});
});

View File

@@ -1,5 +0,0 @@
{
"name": "Using fixtures to represent data",
"email": "hello@cypress.io",
"body": "Fixtures are a great way to mock data for responses to routes"
}

View File

@@ -1,20 +0,0 @@
describe("Map Initial Load Test", () => {
it("should load the map with the correct center and zoom", () => {
// Besuche die Seite, auf der die Karte angezeigt wird
cy.visit("http://192.168.10.167:3000/?m=12&u=485");
// Überprüfe, ob das Kartenelement existiert
cy.get("#map").should("be.visible");
// Überprüfe, ob die Karte das korrekte Zentrum und den korrekten Zoom hat
cy.window().then((win) => {
const map = win.L.map;
const center = map.getCenter();
const zoom = map.getZoom();
expect(center.lat).to.be.closeTo(53.111111, 0.0001);
expect(center.lng).to.be.closeTo(8.4625, 0.0001);
expect(zoom).to.eq(12);
});
});
});

View File

@@ -1,25 +0,0 @@
// ***********************************************
// This example commands.js shows you how to
// create various custom commands and overwrite
// existing commands.
//
// For more comprehensive examples of custom
// commands please read more here:
// https://on.cypress.io/custom-commands
// ***********************************************
//
//
// -- This is a parent command --
// Cypress.Commands.add('login', (email, password) => { ... })
//
//
// -- This is a child command --
// Cypress.Commands.add('drag', { prevSubject: 'element'}, (subject, options) => { ... })
//
//
// -- This is a dual command --
// Cypress.Commands.add('dismiss', { prevSubject: 'optional'}, (subject, options) => { ... })
//
//
// -- This will overwrite an existing command --
// Cypress.Commands.overwrite('visit', (originalFn, url, options) => { ... })

View File

@@ -1,14 +0,0 @@
<!DOCTYPE html>
<html>
<head>
<meta charset="utf-8">
<meta http-equiv="X-UA-Compatible" content="IE=edge">
<meta name="viewport" content="width=device-width,initial-scale=1.0">
<title>Components App</title>
<!-- Used by Next.js to inject CSS. -->
<div id="__next_css__DO_NOT_USE__"></div>
</head>
<body>
<div data-cy-root></div>
</body>
</html>

View File

@@ -1,27 +0,0 @@
// ***********************************************************
// This example support/component.js is processed and
// loaded automatically before your test files.
//
// This is a great place to put global configuration and
// behavior that modifies Cypress.
//
// You can change the location of this file or turn off
// automatically serving support files with the
// 'supportFile' configuration option.
//
// You can read more here:
// https://on.cypress.io/configuration
// ***********************************************************
// Import commands.js using ES2015 syntax:
import './commands'
// Alternatively you can use CommonJS syntax:
// require('./commands')
import { mount } from 'cypress/react18'
Cypress.Commands.add('mount', mount)
// Example use:
// cy.mount(<MyComponent />)

View File

@@ -1,56 +0,0 @@
// ***********************************************************
// Diese Datei wird automatisch geladen, bevor Tests ausgeführt werden.
//
// Dies ist ein guter Platz für globale Konfigurationen
// und Änderungen, die Cypress beeinflussen.
//
// Weitere Infos: https://on.cypress.io/configuration
// ***********************************************************
// Importiere zusätzliche Befehle
import "./commands";
// Alternativ: CommonJS-Syntax verwenden
// require('./commands')
// Verstecke unnötige Logs für XHR- und Fetch-Anfragen
const app = window.top;
if (!app.document.head.querySelector("[data-hide-command-log-request]")) {
const style = app.document.createElement("style");
style.innerHTML = `
.command-name-request, .command-name-xhr {
display: none; /* Verstecke Fetch- und XHR-Logs */
}
.runnable-pass .collapsible-header {
display: none; /* Verstecke den Header erfolgreicher Tests */
}
`;
style.setAttribute("data-hide-command-log-request", "");
app.document.head.appendChild(style);
}
// Globale Ereignisse für Cypress konfigurieren
Cypress.on("test:after:run", (test, runnable) => {
// Minimiert die Logs für erfolgreiche Tests
if (test.state === "passed") {
runnable._testConfigBody = null; // Löscht den Test Body
}
});
// Schließe automatisch erfolgreiche Tests in der GUI
Cypress.on("log:added", (log) => {
if (log.state === "passed") {
log.displayName = ""; // Versteckt Log-Details für erfolgreiche Schritte
}
});
// Screenshot nach jedem fehlgeschlagenen Test
Cypress.on("fail", (error, runnable) => {
cy.screenshot(`error-${runnable.title}`); // Screenshot für Fehler erstellen
throw error; // Fehler weitergeben
});
// Logge die Dauer jedes Tests
Cypress.on("test:after:run", (test) => {
cy.log(`Test "${test.title}" abgeschlossen in ${test.duration} ms`);
});

View File

@@ -6,10 +6,6 @@ DB_PASSWORD="root#$"
DB_NAME=talas_v5
DB_PORT=3306
# Public Settings (Client braucht IP/Domain) , Variablen mit dem Präfix "NEXT_PUBLIC" ist in Browser sichtbar
NEXT_PUBLIC_DEBUG_LOG=false
#auf dem Entwicklungsrechner dev läuft auf Port 3000 und auf dem Server prod auf Port 80, aber der WebService ist immer auf PORT 80
NEXT_PUBLIC_API_PORT_MODE=prod

View File

@@ -25,6 +25,31 @@ Entwicklung, Architekturverständnis und Erweiterung.
### ⚙️ Konfiguration
- [Allgemeine Übersicht](config/README.md)
- [Kartenquellen-Konfiguration (public/config.json)](#kartenquellen-konfiguration-publicconfigjson)
---
## ⚙️ Kartenquellen-Konfiguration (public/config.json)
Die Datei `public/config.json` steuert, welche Kartenquelle (z.B. OSM oder lokale Tiles) für die
Leaflet-Karte verwendet wird.
**Beispiel:**
```json
{
"//info": "tileSources: 'local' für offline, 'osm' für online",
"tileSources": {
"local": "http://localhost/talas5/TileMap/mapTiles/{z}/{x}/{y}.png",
"osm": "https://{s}.tile.openstreetmap.org/{z}/{x}/{y}.png"
},
"active": "osm"
}
```
- Mit `active` kann zwischen Online- und Offline-Karten umgeschaltet werden.
- Die Datei wird beim Start der App automatisch geladen.
- Für Offline-Betrieb muss das lokale Kartenmaterial vorhanden sein (siehe Installationsanleitung).
### 🧩 Hauptkomponenten

View File

@@ -69,3 +69,36 @@ die Daten von DB auch mit WebSocket gelöst werden
- [ ] Redundante Kontextmenülogik auflösen
- [ ] Bessere Trennung zwischen Mock- und Live-API in Service-Funktionen
---
28.07.2025 IdSystem 11 GMA Glätemeldeanlagen, werden neu neu laden das Browser nich mehr geladen in
DB maps idsystem ändern und testen
# 12.09.2025
Die aktuelle Ansicht ist bei kleineren Auflösungen unübersichtlich bzw. es wird zuviel von der
eigentlichen Karte verdeckt. Unquittierter Alarm, critical
Zu Marker zoomen
Station suchen
- [ ] TODO: Unquittierter Alarm, critical 🚨 Alarm
- [ ] TODO: Zu Marker zoomen: Dropdown-Menu Station auswählen und hinein zoomen bis zu ausgewählte
in einem betimmten Zoom-Stufe der Leaflet (OSM) Station mit flyto in Leaflet 📍 POI
- [ ] TODO: Station suchen: CoordinateInput.js Modal soll über einem Icon 'Suche / Lupe' oben rechts
eingeblendet und ausgeblendet um mehr von der Karte zu sehen 🔍 Suche
- [ ] TODO: Editiermodus: EditMode Stift Icon aktivieren und deaktivieren um POI Position ändern zu
können wenn der User Berechtigung hat ✏️ Edit
- [ ] TODO: Vergrössern: Maximieren Icon Button um rauszoomen zu einem bestimmten Bereich, z.B.
Deutschland Karte im Fenster sichtbar ⬜ Fenster maximieren
- [ ] TODO: Ebenen (Openstreetmap): Stack/Stapel/Ebenen Icon um den Ansicht der Karten zu ändern 🗂️
Stapel
- [ ] TODO: Menü öffenen mit Kiste der Systeme: MapLayerControlPanel.js Modal soll über einem Icon
'Hamburger menu' oben rechts eingeblendet und ausgeblendet um mehr von der Karte zu sehen ☰
Hamburger-Menü
- [ ] TODO: Info Karte: VersionInfoModal.js Modal soll über einem Icon 'Info' oben rechts
eingeblendet und ausgeblendet um mehr von der Karte zu sehen Info

View File

@@ -87,7 +87,7 @@ sequenceDiagram
- **Konfigurierbarer basePath:**
- **Konfigurierbarer basePath:**
Pfad wie `/talas5` ist optional und kann per Umgebungsvariable `NEXT_PUBLIC_BASE_PATH` gesetzt
Pfad wie `/talas5` ist optional und wird jetzt in `public/config.json` als `basePath` gepflegt
werden.
Die Konfiguration erfolgt je nach Umgebung über:

View File

@@ -3,12 +3,12 @@
# 📁 paths.js
Berechnet den sauberen `BASE_URL`-Pfad basierend auf `.env.production` oder
`.env.development → NEXT_PUBLIC_BASE_PATH`.
`public/config.json → basePath`.
Entfernt führende und abschließende Slashes.
## Beispiel
Wenn `NEXT_PUBLIC_BASE_PATH = "/talas5/"`, wird `BASE_URL = "/talas5"` gesetzt.
Wenn `basePath = "/talas5/"` in config.json gesetzt ist, wird `BASE_URL = "/talas5"` verwendet.
```js
const BASE_PATH = basePathRaw.replace(/^\/|\/$/g, "");

View File

@@ -12,17 +12,17 @@ NodeMap verwendet Umgebungsvariablen zur Steuerung von API-Verhalten, Serverpfad
## 🔧 Wichtige Variablen
| Variable | Beispielwert | Beschreibung |
| --------------------------- | ------------------- | --------------------------------------------------------------------- |
| `DB_HOST` | `localhost` | Adresse des Datenbankservers (MySQL) |
| `DB_PORT` | `3306` | Port für die Datenbankverbindung |
| `DB_NAME` | `talas` | Datenbankname |
| `DB_USER` | `root` | Benutzername für MySQL |
| `DB_PASSWORD` | `geheim` | Passwort für MySQL |
| `NEXT_PUBLIC_API_PORT_MODE` | `prod` oder `dev` | Steuert API-Routing bei Services (z.B. Portwechsel für lokal) |
| `NEXT_PUBLIC_USE_MOCKS` | `true` oder `false` | Aktiviert den Mockdaten-Modus über `/api/mocks/...` |
| `NEXT_PUBLIC_BASE_PATH` | `/talas5` oder leer | Optionaler Pfad, falls App unter Subpfad läuft (z.B. IIS) |
| `NEXT_PUBLIC_DEBUG` | `true` oder `false` | Aktiviert zusätzliche `console.log` Ausgaben für Debugging im Browser |
| Variable | Beispielwert | Beschreibung |
| --------------------------- | ------------------- | -------------------------------------------------------------------------------------------------------- |
| `DB_HOST` | `localhost` | Adresse des Datenbankservers (MySQL) |
| `DB_PORT` | `3306` | Port für die Datenbankverbindung |
| `DB_NAME` | `talas` | Datenbankname |
| `DB_USER` | `root` | Benutzername für MySQL |
| `DB_PASSWORD` | `geheim` | Passwort für MySQL |
| `NEXT_PUBLIC_API_PORT_MODE` | `prod` oder `dev` | Steuert API-Routing bei Services (z.B. Portwechsel für lokal) |
| `NEXT_PUBLIC_USE_MOCKS` | `true` oder `false` | Aktiviert den Mockdaten-Modus über `/api/mocks/...` |
| `basePath` (in config.json) | `/talas5` oder leer | Optionaler Pfad, falls App unter Subpfad läuft (z.B. IIS). Wird jetzt in `public/config.json` gepflegt. |
| `NEXT_PUBLIC_DEBUG` | `true` oder `false` | Aktiviert zusätzliche `console.log` Ausgaben für Debugging im Browser |
## 📦 Beispiel `.env.production`
@@ -34,7 +34,11 @@ DB_USER=root
DB_PASSWORD=geheim
NEXT_PUBLIC_API_PORT_MODE=prod
NEXT_PUBLIC_USE_MOCKS=false
NEXT_PUBLIC_BASE_PATH=/talas5
// public/config.json
{
...
"basePath": "/talas5"
}
NEXT_PUBLIC_DEBUG=false
```
@@ -48,7 +52,11 @@ DB_USER=root
DB_PASSWORD=geheim
NEXT_PUBLIC_API_PORT_MODE=dev
NEXT_PUBLIC_USE_MOCKS=true
NEXT_PUBLIC_BASE_PATH=/talas5
// public/config.json
{
...
"basePath": "/talas5"
}
NEXT_PUBLIC_DEBUG=true
```

View File

@@ -1,4 +1,5 @@
import L from "leaflet";
import { getDebugLog } from "../utils/configUtils";
export class OverlappingMarkerSpiderfier {
constructor(map, options = {}) {
@@ -118,7 +119,7 @@ export class OverlappingMarkerSpiderfier {
// 🔥 Künstliches Click-Event auslösen, um die UI zu aktualisieren
setTimeout(() => {
this.map.fire("click");
if (process.env.NEXT_PUBLIC_DEBUG_LOG === "true") {
if (getDebugLog()) {
console.log("Click-Event ausgelöst in OverlappingMarkerspiderfier.js in unspiderfy ");
}
}, 10); // Kurze Verzögerung, um sicherzustellen, dass die UI neu gerendert wird

BIN
nssm.exe Normal file

Binary file not shown.

48
nssm.exe Installation.md Normal file
View File

@@ -0,0 +1,48 @@
```markdown
- Als Administrator Eingabeaufforderung oder PowerShell öffnen
- Navigiere zu dem NodeMap Projekt Verzeichnis:
```shell
C:\Users\Administrator>cd C:\inetpub\wwwroot\talas5\nodeMap
```
- Befehl zum Erstellen eines Dienstes:
Führen Sie den folgenden Befehl aus, um einen neuen Dienst zu erstellen:
```shell
nssm.exe install NodeMapService
```
Nachdem Sie diesen Befehl ausgeführt haben, öffnet sich ein NSSM-Dialogfenster.
**Dienstkonfiguration:**
In dem geöffneten NSSM-Dialogfenster müssen Sie einige Parameter angeben:
- **Path:** Der Pfad zur ausführbaren Datei, die der Dienst ausführen soll.
```shell
C:\inetpub\wwwroot\talas5\nodeMap\StartNodeApp.bat
```
- **Startup directory:** Das Verzeichnis, in dem die Anwendung gestartet werden soll.
```shell
C:\inetpub\wwwroot\talas5\nodeMap
```
- **Arguments:** kann leer gelassen werden.
- Dienst starten:
Sobald der Dienst erstellt wurde, können Sie ihn starten.
Das können Sie entweder über die Eingabeaufforderung oder über die Diensteverwaltung von Windows tun.
Um den Dienst über die Eingabeaufforderung zu starten, verwenden Sie den folgenden Befehl:
```shell
nssm.exe start DienstName
```
---
- **Dienst bearbeiten:**
```shell
nssm.exe edit NodeMapService
```
- **Dienst entfernen:**
```shell
nssm.exe remove NodeMapService confirm
```
dauert bis 1 Minute
```

2142
package-lock.json generated

File diff suppressed because it is too large Load Diff

View File

@@ -1,6 +1,6 @@
{
"name": "nodemap",
"version": "1.1.300",
"version": "1.1.355",
"dependencies": {
"@emotion/react": "^11.13.3",
"@emotion/styled": "^11.13.0",
@@ -39,17 +39,17 @@
"scripts": {
"dev": "node server.js",
"build": "next build",
"build:deploy": "next build && npm run create-zip",
"create-zip": "powershell -ExecutionPolicy Bypass -File ./scripts/create-deployment-zip.ps1",
"start": "cross-env NODE_ENV=production node server.js",
"export": "next export",
"test": "jest",
"cypress": "cypress open",
"cypress:run": "cypress run",
"prepare": "husky",
"bump-version": "node ./scripts/bumpVersion.js"
},
"devDependencies": {
"@playwright/test": "^1.54.2",
"cross-env": "^7.0.3",
"cypress": "^13.17.0",
"husky": "^9.1.7",
"identity-obj-proxy": "^3.0.0",
"jest-environment-jsdom": "^29.7.0",

View File

@@ -1,133 +0,0 @@
// /pages/api/health.js
export default async function handler(req, res) {
const basePath = "talas5";
const protocol = "http";
const hostname = "10.10.0.70";
const port = "80";
const idMap = "12";
const idUser = "484";
const idLD = "50922";
const buildUrl = method =>
`${protocol}://${hostname}:${port}/${basePath}/ClientData/WebServiceMap.asmx/${method}?idMap=${idMap}&idUser=${idUser}`;
const externalUrls = {
GisStationsStaticDistrict: buildUrl("GisStationsStaticDistrict"),
GisLinesStatus: `${protocol}://${hostname}:${port}/${basePath}/ClientData/WebServiceMap.asmx/GisLinesStatus?idMap=${idMap}`,
GisStationsMeasurements: buildUrl("GisStationsMeasurements"),
GisStationsStatusDistrict: buildUrl("GisStationsStatusDistrict"),
GisSystemStatic: buildUrl("GisSystemStatic"),
};
const internalApiBase = "http://localhost:3000";
const internalApis = {
//area
readArea: `${internalApiBase}/api/talas_v5_DB/area/readArea?m=${idMap}`,
//device
getAllStationsNames: `${internalApiBase}/api/talas_v5_DB/device/getAllStationsNames`,
getDevices: `${internalApiBase}/api/talas_v5_DB/device/getDevices`,
//gisLines
readGisLines: `${internalApiBase}/api/talas_v5_DB/gisLines/readGisLines`,
//locationDevice
getDeviceId: `${internalApiBase}/api/talas_v5_DB/locationDevice/getDeviceId`,
locationDeviceNameById: `${internalApiBase}/api/talas_v5_DB/locationDevice/locationDeviceNameById?idLD=${idLD}`,
locationDevices: `${internalApiBase}/api/talas_v5_DB/locationDevice/locationDevices`,
//pois
addPoi: `${internalApiBase}/api/talas_v5_DB/pois/addPoi`,
deletePoi: `${internalApiBase}/api/talas_v5_DB/pois/deletePoi`,
getPoiById: `${internalApiBase}/api/talas_v5_DB/pois/getPoiById`,
poiIcons: `${internalApiBase}/api/talas_v5_DB/pois/poi-icons`,
readAllPOIs: `${internalApiBase}/api/talas_v5_DB/pois/readAllPOIs`,
updateLocation: `${internalApiBase}/api/talas_v5_DB/pois/updateLocation`,
updatePoi: `${internalApiBase}/api/talas_v5_DB/pois/updatePoi`,
//poiTyp
readPoiTyp: `${internalApiBase}/api/talas_v5_DB/poiTyp/readPoiTyp`,
//station
getAllStationsNames: `${internalApiBase}/api/talas_v5_DB/station/getAllStationsNames`,
getDevices: `${internalApiBase}/api/talas_v5_DB/station/getDevices`,
//
priorityConfig: `${internalApiBase}/api/talas_v5_DB/priorityConfig`,
};
const results = {};
// Prüfe externe Webservices
await Promise.all(
Object.entries(externalUrls).map(async ([name, url]) => {
try {
const response = await fetch(url);
results[name] = {
ok: response.ok,
status: response.status,
url,
};
if (name === "GisSystemStatic" && response.ok) {
const data = await response.json();
results["UserRights"] = {
ok: Array.isArray(data.Rights),
length: data.Rights?.length || 0,
};
}
} catch (error) {
results[name] = {
ok: false,
error: error.message,
url,
};
}
})
);
// Prüfe interne API-Routen
await Promise.all(
Object.entries(internalApis).map(async ([name, url]) => {
try {
const response = await fetch(url);
results[`API_${name}`] = {
ok: response.ok,
status: response.status,
url,
};
} catch (error) {
results[`API_${name}`] = {
ok: false,
error: error.message,
url,
};
}
})
);
// Prüfe Mock-Status
const useMocksEnv = process.env.NEXT_PUBLIC_USE_MOCKS;
results["MockMode"] = {
expected: "false",
actual: useMocksEnv,
ok: useMocksEnv === "false",
info:
useMocksEnv === "false"
? "✅ Mockdaten deaktiviert Live-Daten aktiv."
: "⚠️ Mockdaten aktiv nicht für Produktion geeignet!",
};
// Prüfe Konfiguration der .env.production
results["envConfig"] = {
NODE_ENV: process.env.NODE_ENV || "undefined",
DB_HOST: process.env.DB_HOST || "❌ fehlt",
NEXT_PUBLIC_USE_MOCKS: useMocksEnv || "❌ fehlt",
status:
process.env.NODE_ENV === "production" && useMocksEnv === "false" && process.env.DB_HOST
? "✅ .env.production scheint korrekt."
: "⚠️ Bitte .env.production prüfen möglicherweise fehlt etwas.",
};
const allOk = Object.values(results).every(r => r.ok);
res.status(allOk ? 200 : 207).json({
status: allOk ? "ok" : "partial",
version: "1.0.0",
services: results,
});
}

View File

@@ -1,8 +1,9 @@
// /pages/api/talas_v5_DB/area/updateArea.js
import getPool from "../../../../utils/mysqlPool";
import { getDebugLog } from "../../../../utils/configUtils";
export default async function handler(req, res) {
if (process.env.NEXT_PUBLIC_DEBUG_LOG === "true") {
if (getDebugLog()) {
console.log("Request erhalten:", req.method, req.body); // Debugging
}

View File

@@ -1,5 +1,6 @@
// /pages/api/talas_v5_DB/gisLines/updateLineCoordinates.js
import getPool from "../../../../utils/mysqlPool"; // Singleton-Pool importieren
import { getDebugLog } from "../../../../utils/configUtils";
export default async function handler(req, res) {
const pool = getPool(); // Singleton-Pool verwenden
@@ -34,7 +35,7 @@ export default async function handler(req, res) {
// Commit der Transaktion
await connection.commit();
if (process.env.NEXT_PUBLIC_DEBUG_LOG === "true") {
if (getDebugLog()) {
console.log("Transaction Complete.");
}
res.status(200).json({

View File

@@ -1,12 +1,13 @@
// pages/api/talas_v5_DB/pois/addPoi.js
import getPool from "../../../../utils/mysqlPool"; // Singleton-Pool importieren
import { getDebugLog } from "../../../../utils/configUtils";
export default async function handler(req, res) {
const pool = getPool(); // Singleton-Pool verwenden
if (req.method === "POST") {
const { name, poiTypeId, latitude, longitude, idLD } = req.body;
if (process.env.NEXT_PUBLIC_DEBUG_LOG === "true") {
if (getDebugLog()) {
console.log("Received data:", req.body); // Überprüfen der empfangenen Daten
}

View File

@@ -0,0 +1,18 @@
// pages/api/testDbConnection.js
import getPool from "../../utils/mysqlPool";
export default async function handler(req, res) {
const pool = getPool();
let connection;
try {
connection = await pool.getConnection();
const [rows] = await connection.query("SELECT 1 AS test");
console.log("DB-Verbindung erfolgreich! Ergebnis:", rows);
res.status(200).json({ success: true, result: rows });
} catch (error) {
console.error("DB-Verbindungsfehler:", error);
res.status(500).json({ success: false, error: error.message });
} finally {
if (connection) connection.release();
}
}

View File

@@ -0,0 +1,7 @@
// example.spec.js
const { test, expect } = require("@playwright/test");
test("simple test", async ({ page }) => {
await page.goto("https://playwright.dev");
await expect(page).toHaveTitle(/Playwright/);
});

34
public/config.json Normal file
View File

@@ -0,0 +1,34 @@
{
"_comment": [
"tileSources: 'local' für offline, 'osm' für online",
"center: Startmittelpunkt der Karte (lat, lng)",
"zoomOutCenter: Zielkoordinaten für Herauszoomen (lat, lng)",
"minZoom/maxZoom: erlaubte Zoomstufen pro Quelle"
],
"tileSources": {
"local": {
"url": "http://localhost/talas5/TileMap/mapTiles/{z}/{x}/{y}.png",
"_comment": "Offline-Kartenquelle (lokal)",
"minZoom": 5,
"maxZoom": 15
},
"osm": {
"url": "/tiles/{z}/{x}/{y}.png",
"_comment": "OpenStreetMap Online-Kartenquelle über Server-Proxy (relativ)",
"minZoom": 0,
"maxZoom": 19
}
},
"active": "osm",
"_comment_active": "Aktive Kartenquelle: 'local' oder 'osm'",
"center": [53.111111, 8.4625],
"_comment_center": "Startmittelpunkt der Karte (lat, lng)",
"zoomOutCenter": [51.41321407879154, 7.739617925303934],
"_comment_zoomOutCenter": "Zielkoordinaten für Herauszoomen (lat, lng)",
"basePath": "/talas5",
"_comment_basePath": "Basis-URL für API und Routing",
"debugLog": false,
"_comment_debugLog": "Debug-Logging für Client "
}

Binary file not shown.

After

Width:  |  Height:  |  Size: 2.9 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 2.8 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 2.9 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 3.0 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 2.8 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 2.7 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 2.7 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 2.8 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 3.2 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 2.9 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 3.2 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 2.7 KiB

View File

@@ -1,8 +1,39 @@
// /redux/slices/database7polylines/polylineLayerVisibleSlice.js
import { createSlice } from "@reduxjs/toolkit";
// Thunk to initialize polyline visibility from localStorage using mapId/userId from URL
export const initializePolylineFromLocalStorageThunk = () => dispatch => {
try {
// Prüfe globales Nutzer-Flag
if (typeof window !== "undefined" && window.userToggledPolyline) {
console.log(
"[Redux] Initialisierung abgebrochen: Nutzer hat Polyline bereits manuell geändert."
);
return;
}
const params = new URLSearchParams(window.location.search);
const mapId = params.get("m");
const userId = params.get("u");
if (mapId && userId) {
const key = `polylineVisible_m${mapId}_u${userId}`;
const stored = localStorage.getItem(key);
const visible = stored === "true";
dispatch(initializePolylineFromLocalStorage(visible));
// Optional: log for debugging
console.log(
`Redux: Initialized polyline visibility from localStorage key '${key}':`,
visible
);
}
} catch (e) {
console.error("Error initializing polyline visibility from localStorage:", e);
}
};
const initialState = {
visible: false, // oder Standardwert
visible: false, // Standardwert - wird in der Komponente aus localStorage überschrieben
isInitialized: false, // Flag um zu verfolgen, ob der Wert aus localStorage geladen wurde
};
const polylineLayerVisibleSlice = createSlice({
@@ -11,11 +42,51 @@ const polylineLayerVisibleSlice = createSlice({
reducers: {
setPolylineVisible: (state, action) => {
state.visible = action.payload;
localStorage.setItem("polylineVisible", action.payload);
state.isInitialized = true;
// Save to localStorage using mapId/userId key
try {
const params = new URLSearchParams(window.location.search);
const mapId = params.get("m");
const userId = params.get("u");
if (mapId && userId) {
const key = `polylineVisible_m${mapId}_u${userId}`;
localStorage.setItem(key, action.payload.toString());
console.log(
"[Redux/setPolylineVisible] payload:",
action.payload,
"key:",
key,
"localStorage:",
localStorage.getItem(key)
);
}
} catch (e) {
// fallback: do nothing
}
console.log("💾 Redux: setPolylineVisible called with:", action.payload);
},
initializePolylineFromLocalStorage: (state, action) => {
// Diese Action wird nur beim Initialisieren aus localStorage verwendet
if (typeof window !== "undefined" && window.userToggledPolyline) {
console.log(
"[Redux] Initialisierung im Reducer abgebrochen: Nutzer hat Polyline bereits manuell geändert."
);
return;
}
state.visible = action.payload;
state.isInitialized = true;
console.log(
"🔧 Redux: initializePolylineFromLocalStorage called with:",
action.payload,
"visible:",
state.visible
);
},
},
});
export const { setPolylineVisible } = polylineLayerVisibleSlice.actions;
export const selectPolylineVisible = (state) => state.polylineLayerVisible.visible;
export const { setPolylineVisible, initializePolylineFromLocalStorage } =
polylineLayerVisibleSlice.actions;
export const selectPolylineVisible = state => state.polylineLayerVisible.visible;
export const selectPolylineInitialized = state => state.polylineLayerVisible.isInitialized;
export default polylineLayerVisibleSlice.reducer;

View File

@@ -15,20 +15,68 @@ const mapLayersSlice = createSlice({
},
setLayerVisibility: (state, action) => {
const { layer, visibility } = action.payload;
if (state[layer] !== undefined) {
state[layer] = visibility;
}
state[layer] = visibility; // Sicher setzen
},
setInitialLayers: (state, action) => {
const systems = action.payload; // Array of GisSystem
systems.forEach((system) => {
const mapId =
typeof localStorage !== "undefined" ? localStorage.getItem("currentMapId") : null;
const userId =
typeof localStorage !== "undefined" ? localStorage.getItem("currentUserId") : null;
const mapStorageKey =
mapId && userId ? `mapLayersVisibility_m${mapId}_u${userId}` : "mapLayersVisibility";
let existingVisibility = {};
if (typeof localStorage !== "undefined") {
try {
const stored = localStorage.getItem(mapStorageKey);
if (stored) {
existingVisibility = JSON.parse(stored) || {};
}
} catch (error) {
console.error("Error loading stored visibility:", error);
}
}
// Baue ein Set der gültigen Keys (nur Systeme mit Map===1)
const validKeys = new Set();
systems.forEach(system => {
if (system.Map !== 1) return; // Komplett überspringen, wenn Map==0
const key = `system-${system.IdSystem}`;
state[key] = true; // oder false, je nach Default
validKeys.add(key);
if (Object.prototype.hasOwnProperty.call(existingVisibility, key)) {
state[key] = existingVisibility[key];
} else {
state[key] = system.Allow === 1 && system.Map === 1; // Allow + Map Bedingung
}
});
// Entferne aus dem State alle Keys, die nicht mehr gültig sind (Map wurde evtl. auf 0 gesetzt)
Object.keys(state).forEach(k => {
if (!validKeys.has(k)) {
delete state[k];
}
});
// Bereinige auch den gespeicherten localStorage-Eintrag
if (typeof localStorage !== "undefined") {
const cleaned = {};
Object.keys(state).forEach(k => {
cleaned[k] = state[k];
});
try {
localStorage.setItem(mapStorageKey, JSON.stringify(cleaned));
} catch (e) {
console.warn("Could not persist cleaned map layer visibility", e);
}
}
},
},
});
export const { toggleLayer, setLayerVisibility, setInitialLayers } = mapLayersSlice.actions;
export const selectMapLayersState = (state) => state.mapLayers || initialState;
export const selectMapLayersState = state => state.mapLayers || initialState;
export default mapLayersSlice.reducer;

View File

@@ -0,0 +1,105 @@
# PowerShell Script to create deployment ZIP after successful build
param(
[string]$ProjectRoot = $PSScriptRoot + "\.."
)
# Read package.json to get name and version
$packageJsonPath = Join-Path $ProjectRoot "package.json"
$packageJson = Get-Content $packageJsonPath | ConvertFrom-Json
$projectName = $packageJson.name
$version = $packageJson.version
# Create ZIP filename
$zipFileName = "${projectName}-${version}.zip"
$zipPath = Join-Path $ProjectRoot $zipFileName
# Remove existing ZIP if it exists
if (Test-Path $zipPath) {
Remove-Item $zipPath -Force
Write-Host "[DELETE] Removed existing ZIP: $zipFileName" -ForegroundColor Yellow
}
# Define files and directories to include
$itemsToInclude = @(
".next",
"public",
".env.production",
"server.js",
"nssm.exe",
"nssm.exe Installation.md",
"package.json",
"package-lock.json",
"Start-Dev.ps1",
"StartNodeApp.bat"
)
# Check which items exist
$existingItems = @()
foreach ($item in $itemsToInclude) {
$itemPath = Join-Path $ProjectRoot $item
if (Test-Path $itemPath) {
$existingItems += $item
Write-Host "[FOUND] $item" -ForegroundColor Green
} else {
Write-Host "[MISSING] $item" -ForegroundColor Yellow
}
}
if ($existingItems.Count -eq 0) {
Write-Host "[ERROR] No items found to include in ZIP!" -ForegroundColor Red
exit 1
}
# Create temporary directory for ZIP contents
$tempDir = Join-Path $ProjectRoot "temp-deployment"
if (Test-Path $tempDir) {
Remove-Item $tempDir -Recurse -Force
}
New-Item -ItemType Directory -Path $tempDir | Out-Null
# Copy items to temporary directory
foreach ($item in $existingItems) {
$sourcePath = Join-Path $ProjectRoot $item
$destPath = Join-Path $tempDir $item
if (Test-Path $sourcePath -PathType Container) {
# It's a directory
Copy-Item $sourcePath $destPath -Recurse -Force
Write-Host "[COPY DIR] $item" -ForegroundColor Cyan
} else {
# It's a file
Copy-Item $sourcePath $destPath -Force
Write-Host "[COPY FILE] $item" -ForegroundColor Cyan
}
}
# Create ZIP file
try {
Write-Host "[CREATE] Creating ZIP: $zipFileName..." -ForegroundColor Blue
# Use .NET compression to create ZIP
Add-Type -AssemblyName System.IO.Compression.FileSystem
[System.IO.Compression.ZipFile]::CreateFromDirectory($tempDir, $zipPath)
# Get ZIP file size
$zipSize = (Get-Item $zipPath).Length
$zipSizeMB = [math]::Round($zipSize / 1MB, 2)
Write-Host "[SUCCESS] Successfully created deployment ZIP!" -ForegroundColor Green
Write-Host "[FILE] $zipFileName" -ForegroundColor White
Write-Host "[SIZE] $zipSizeMB MB" -ForegroundColor White
Write-Host "[LOCATION] $zipPath" -ForegroundColor White
} catch {
Write-Host "[ERROR] Failed to create ZIP: $($_.Exception.Message)" -ForegroundColor Red
exit 1
} finally {
# Clean up temporary directory
if (Test-Path $tempDir) {
Remove-Item $tempDir -Recurse -Force
Write-Host "[CLEANUP] Cleaned up temporary files" -ForegroundColor Gray
}
}
Write-Host ""
Write-Host "[COMPLETE] Deployment ZIP ready for production deployment!" -ForegroundColor Green

View File

@@ -27,16 +27,46 @@ const extractData = (json, name) => {
};
app.prepare().then(() => {
const server = createServer((req, res) => {
const express = require("express");
const expressApp = express();
// Proxy-Route für Karten-Tiles
expressApp.get("/tiles/:z/:x/:y.png", async (req, res) => {
const { z, x, y } = req.params;
// OSM-Subdomain (a, b, c) zufällig wählen
const subdomains = ["a", "b", "c"];
const s = subdomains[Math.floor(Math.random() * subdomains.length)];
const tileUrl = `https://${s}.tile.openstreetmap.org/${z}/${x}/${y}.png`;
try {
const response = await fetch(tileUrl);
if (!response.ok) {
res.status(response.status).send("Tile not found");
return;
}
res.set("Content-Type", "image/png");
response.body.pipe(res);
} catch (err) {
res.status(500).send("Error fetching tile");
}
});
// Alle anderen Routen an Next.js
expressApp.all("*", (req, res) => {
handle(req, res);
});
const server = createServer(expressApp);
const io = new Server(server);
// ✅ Globaler Cache für alle aktuellen Daten
const globalDataCache = new Map();
io.on("connection", socket => {
const { m: idMap, u: idUser } = socket.handshake.query;
console.log(`🔌 WebSocket verbunden (idMap=${idMap}, idUser=${idUser})`);
const cacheKey = `${idMap}_${idUser}`;
const endpoints = [
{
name: "GisLinesStatus",
@@ -69,6 +99,22 @@ app.prepare().then(() => {
const lastDataMap = {};
// ✅ Funktion um sofort alle verfügbaren Daten zu senden (für Browser-Reload)
const sendAllCurrentData = () => {
const cachedData = globalDataCache.get(cacheKey);
if (cachedData && Object.keys(cachedData).length > 0) {
console.log(
`📦 Sending all current data to client (${Object.keys(cachedData).length} endpoints)`
);
Object.entries(cachedData).forEach(([name, data]) => {
socket.emit(`${name}Updated`, data);
console.log(`🔄 Browser-Reload: ${name} data sent`);
});
} else {
console.log(`📭 No cached data available for ${cacheKey}, will fetch fresh data`);
}
};
const fetchData = async () => {
for (const { name, getUrl, mock } of endpoints) {
try {
@@ -99,6 +145,14 @@ app.prepare().then(() => {
}
const newDataStr = JSON.stringify(statis);
// ✅ Cache aktualisieren
if (!globalDataCache.has(cacheKey)) {
globalDataCache.set(cacheKey, {});
}
globalDataCache.get(cacheKey)[name] = statis;
// ✅ Nur bei Änderungen senden (setInterval-Logik)
if (newDataStr !== lastDataMap[name]) {
lastDataMap[name] = newDataStr;
socket.emit(`${name}Updated`, statis);
@@ -113,9 +167,15 @@ app.prepare().then(() => {
}
};
// fetchData immer ausführen unabhängig vom Modus
// ✅ Beim Connect: Sofort alle aktuellen Daten senden (für Browser-Reload)
sendAllCurrentData();
// ✅ Dann erste Datenabfrage durchführen
fetchData();
// ✅ setInterval für regelmäßige Updates (nur bei Änderungen)
const interval = setInterval(fetchData, 5000); // 5 Sekunden ,TALAS.web nutzt 12 Sekunden
socket.on("disconnect", () => {
clearInterval(interval);
console.log("❌ WebSocket getrennt");

View File

@@ -1,12 +1,23 @@
import { getDebugLog } from "../../utils/configUtils";
// /services/webservice/fetchGisLinesStatusService.js
let __configCache;
async function getConfig() {
if (__configCache) return __configCache;
const res = await fetch("/config.json");
if (!res.ok) throw new Error("config.json konnte nicht geladen werden");
__configCache = await res.json();
return __configCache;
}
export const fetchGisLinesStatusService = async () => {
const useMocks = process.env.NEXT_PUBLIC_USE_MOCKS === "true";
const basePath = process.env.NEXT_PUBLIC_BASE_PATH || "";
const config = await getConfig();
const basePath = config.basePath || "";
if (useMocks) {
const mockBasePath = "/api/mocks/webservice/gisLinesStatus";
const mockURL = `${window.location.origin}${mockBasePath}`;
if (process.env.NEXT_PUBLIC_DEBUG_LOG === "true") {
if (getDebugLog()) {
console.log("🧪 Mock-Modus aktiviert: fetchGisLinesStatusService ", mockURL);
}
@@ -16,11 +27,9 @@ export const fetchGisLinesStatusService = async () => {
}
const mockData = await response.json();
if (!Array.isArray(mockData.Statis)) {
throw new Error("Ungültige Struktur: 'Status' fehlt im Mock");
}
return mockData.Statis;
} else {
const baseUrl = `${window.location.protocol}//${window.location.hostname}:80${basePath}/ClientData/WebServiceMap.asmx`;
@@ -29,7 +38,7 @@ export const fetchGisLinesStatusService = async () => {
const idMap = params.get("m");
const url = `${baseUrl}/GisLinesStatus?idMap=${idMap}`;
if (process.env.NEXT_PUBLIC_DEBUG_LOG === "true") {
if (getDebugLog()) {
console.log("📡 fetchGisLinesStatusService URL:", url);
}

View File

@@ -1,11 +1,22 @@
import { getDebugLog } from "../../utils/configUtils";
let __configCache;
async function getConfig() {
if (__configCache) return __configCache;
const res = await fetch("/config.json");
if (!res.ok) throw new Error("config.json konnte nicht geladen werden");
__configCache = await res.json();
return __configCache;
}
export const fetchGisStationsMeasurementsService = async () => {
const useMocks = process.env.NEXT_PUBLIC_USE_MOCKS === "true";
const basePath = process.env.NEXT_PUBLIC_BASE_PATH || "";
const config = await getConfig();
const basePath = config.basePath || "";
if (useMocks) {
const mockBasePath = "/api/mocks/webservice/gisStationsMeasurements";
const mockURL = `${window.location.origin}${mockBasePath}`;
if (process.env.NEXT_PUBLIC_DEBUG_LOG === "true") {
if (getDebugLog()) {
console.log("🧪 Mock-Modus aktiviert: fetchGisStationsMeasurementsService ", mockURL);
}
@@ -28,7 +39,7 @@ export const fetchGisStationsMeasurementsService = async () => {
const idUser = params.get("u");
const url = `${baseUrl}/GisStationsMeasurements?idMap=${idMap}&idUser=${idUser}`;
if (process.env.NEXT_PUBLIC_DEBUG_LOG === "true") {
if (getDebugLog()) {
console.log("📡 fetchGisStationsMeasurementsService URL:", url);
}

View File

@@ -5,14 +5,18 @@
* @returns {Promise<Array>} Liste mit Points[]
* @throws {Error} bei Fehler oder ungültiger Antwortstruktur
*/
import { getDebugLog, getConfig } from "../../utils/configUtils";
export const fetchGisStationsStaticDistrictService = async () => {
const useMocks = process.env.NEXT_PUBLIC_USE_MOCKS === "true";
const basePath = process.env.NEXT_PUBLIC_BASE_PATH || "";
const config = await getConfig();
const basePath = config.basePath || "";
if (useMocks) {
const mockBasePath = "/api/mocks/webservice/gisStationsStaticDistrict";
const mockURL = `${window.location.origin}${mockBasePath}`;
if (process.env.NEXT_PUBLIC_DEBUG_LOG === "true") {
if (getDebugLog()) {
console.log("🧪 Mock-Modus aktiviert: fetchGisStationsStaticDistrictService ", mockURL);
}
@@ -35,7 +39,7 @@ export const fetchGisStationsStaticDistrictService = async () => {
const idUser = params.get("u");
const url = `${baseUrl}/GisStationsStaticDistrict?idMap=${idMap}&idUser=${idUser}`;
if (process.env.NEXT_PUBLIC_DEBUG_LOG === "true") {
if (getDebugLog()) {
console.log("📡 fetchGisStationsStaticDistrictService URL:", url);
}

View File

@@ -5,14 +5,18 @@
* @returns {Promise<Array>} Liste mit Statis[]
* @throws {Error} bei Fehler oder ungültiger Antwortstruktur
*/
import { getDebugLog, getConfig } from "../../utils/configUtils";
export const fetchGisStationsStatusDistrictService = async () => {
const useMocks = process.env.NEXT_PUBLIC_USE_MOCKS === "true";
const basePath = process.env.NEXT_PUBLIC_BASE_PATH || "";
const config = await getConfig();
const basePath = config.basePath || "";
if (useMocks) {
const mockBasePath = "/api/mocks/webservice/gisStationsStatusDistrict";
const mockURL = `${window.location.origin}${mockBasePath}`;
if (process.env.NEXT_PUBLIC_DEBUG_LOG === "true") {
if (getDebugLog()) {
console.log("🧪 Mock-Modus aktiviert: fetchGisStationsStatusDistrictService ", mockURL);
}
@@ -35,7 +39,7 @@ export const fetchGisStationsStatusDistrictService = async () => {
const idUser = params.get("u");
const url = `${baseUrl}/GisStationsStatusDistrict?idMap=${idMap}&idUser=${idUser}`;
if (process.env.NEXT_PUBLIC_DEBUG_LOG === "true") {
if (getDebugLog()) {
console.log("📡 fetchGisStationsStatusDistrictService URL:", url);
}

View File

@@ -4,14 +4,18 @@
* @returns {Promise<Array>} Liste mit Systems[]
* @throws {Error} bei Fehler oder ungültiger Antwortstruktur
*/
import { getDebugLog, getConfig } from "../../utils/configUtils";
export const fetchGisSystemStaticService = async () => {
const useMocks = process.env.NEXT_PUBLIC_USE_MOCKS === "true";
const basePath = process.env.NEXT_PUBLIC_BASE_PATH || "";
const config = await getConfig();
const basePath = config.basePath || "";
if (useMocks) {
const mockBasePath = "/api/mocks/webservice/gisSystemStatic";
const mockURL = `${window.location.origin}${mockBasePath}`;
if (process.env.NEXT_PUBLIC_DEBUG_LOG === "true") {
if (getDebugLog()) {
console.log("🧪 Mock-Modus aktiviert: fetchGisSystemStaticService ", mockURL);
}
@@ -34,7 +38,7 @@ export const fetchGisSystemStaticService = async () => {
const idUser = params.get("u");
const url = `${baseUrl}/GisSystemStatic?idMap=${idMap}&idUser=${idUser}`;
if (process.env.NEXT_PUBLIC_DEBUG_LOG === "true") {
if (getDebugLog()) {
console.log("📡 fetchGisSystemStaticService von service URL:", url);
}

View File

@@ -4,14 +4,18 @@
* @returns {Promise<Array>} Rechte-Array
* @throws {Error} bei Lade- oder Strukturfehler
*/
import { getDebugLog, getConfig } from "../../utils/configUtils";
export const fetchUserRightsService = async () => {
const useMocks = process.env.NEXT_PUBLIC_USE_MOCKS === "true";
const basePath = process.env.NEXT_PUBLIC_BASE_PATH || "";
const config = await getConfig();
const basePath = config.basePath || "";
if (useMocks) {
const mockBasePath = "/api/mocks/webservice/gisSystemStatic";
const mockURL = `${window.location.origin}${mockBasePath}`;
if (process.env.NEXT_PUBLIC_DEBUG_LOG === "true") {
if (getDebugLog()) {
console.log("🧪 Mock-Modus aktiviert: fetchUserRightsService ", mockURL);
}
@@ -30,7 +34,7 @@ export const fetchUserRightsService = async () => {
const idUser = params.get("u");
const url = `${baseUrl}/GisSystemStatic?idMap=${idMap}&idUser=${idUser}`;
if (process.env.NEXT_PUBLIC_DEBUG_LOG === "true") {
if (getDebugLog()) {
console.log("🔍 Rechte-Fetch URL:", url);
}
@@ -46,7 +50,7 @@ export const fetchUserRightsService = async () => {
}
const json = await response.json();
if (process.env.NEXT_PUBLIC_DEBUG_LOG === "true") {
if (getDebugLog()) {
console.log("👤 Rechte-Response JSON:", json);
}

View File

@@ -0,0 +1,4 @@
{
"status": "passed",
"failedTests": []
}

26
utils/configUtils.js Normal file
View File

@@ -0,0 +1,26 @@
// utils/configUtils.js
let __configCache;
export async function getConfig() {
if (__configCache) return __configCache;
const res = await fetch("/config.json");
if (!res.ok) throw new Error("config.json konnte nicht geladen werden");
__configCache = await res.json();
return __configCache;
}
// Sync helper for debugLog (for use in event handlers etc.)
let debugLogValue;
export function getDebugLog() {
if (debugLogValue !== undefined) return debugLogValue;
// Try to read from window.__appConfig if available (set at app start)
if (
typeof window !== "undefined" &&
window.__appConfig &&
typeof window.__appConfig.debugLog !== "undefined"
) {
debugLogValue = !!window.__appConfig.debugLog;
return debugLogValue;
}
// Fallback: default false
return false;
}

View File

@@ -26,19 +26,38 @@ export const createAndSetDevices = async (
measurements,
oms // 🔁 OMS für Spiderfy hinzugefügt
) => {
const basePath = process.env.NEXT_PUBLIC_BASE_PATH || "";
// Prüfe, ob das System erlaubt ist (Allow === 1)
const systemConfig = Array.isArray(GisSystemStatic)
? GisSystemStatic.find(sys => sys.IdSystem === systemId)
: null;
if (!systemConfig) {
console.warn(`🚫 Kein Marker für System ${systemId}, Allow = ${systemConfig?.Allow}`);
setMarkersFunction([]);
return;
}
let basePath = "";
try {
const res = await fetch("/config.json");
if (res.ok) {
const config = await res.json();
basePath = config.basePath || "";
}
} catch (e) {}
try {
const state = store.getState();
const staticDistrictData = selectGisStationsStaticDistrict(state);
const statusDistrictData = selectGisStationsStatusDistrict(state);
const measurementData = selectGisStationsMeasurements(state);
// Sicherstellen, dass measurementData immer ein Array ist
const safeMeasurementData = Array.isArray(measurementData) ? measurementData : [];
if (!staticDistrictData?.Points?.length || !statusDistrictData?.length) return;
if (!staticDistrictData?.Points?.length) return;
const hasStatus = Array.isArray(statusDistrictData) && statusDistrictData.length > 0;
const statisMap = new Map(statusDistrictData.map(s => [s.IdLD, s]));
const measurementsMap = new Map();
measurementData?.forEach(m => {
safeMeasurementData.forEach(m => {
if (!measurementsMap.has(m.IdLD)) {
measurementsMap.set(m.IdLD, m);
}
@@ -101,10 +120,10 @@ export const createAndSetDevices = async (
marker.bindPopup(popupContent);
// ✅ Tooltip (nur für System 11)
if (station.System === 11 && messung) {
const gmaMap = new Map();
measurementData?.forEach(m => {
safeMeasurementData.forEach(m => {
if (!gmaMap.has(m.IdLD)) {
gmaMap.set(m.IdLD, {});
}

View File

@@ -6,50 +6,147 @@ import "leaflet-contextmenu/dist/leaflet.contextmenu.css";
import "overlapping-marker-spiderfier-leaflet";
export const initializeMap = (
mapRef,
setMap,
setOms,
mapContainer,
setMenuItemAdded,
addItemsToMapContextMenu,
hasRights,
setPolylineEventsDisabled
setPolylineEventsDisabled,
logError = false
) => {
const basePath = process.env.NEXT_PUBLIC_BASE_PATH;
// basePath wird aus config.json geladen (siehe unten)
let basePath = undefined;
if (!mapRef.current) {
console.error("❌ Fehler: mapRef.current ist nicht definiert.");
return;
}
if (mapRef.current._leaflet_id) {
if (process.env.NEXT_PUBLIC_DEBUG_LOG === "true") {
console.log("⚠️ Karte bereits initialisiert");
if (
!mapContainer ||
!(mapContainer instanceof HTMLElement) ||
!document.body.contains(mapContainer)
) {
if (logError) {
console.error("❌ Fehler: map container ist nicht definiert oder nicht im DOM.");
}
return;
return null;
}
const url = new URL(window.location.origin);
const originWithoutPort = `${url.protocol}//${url.hostname}`;
const tileLayerUrl = `${originWithoutPort}${basePath}/TileMap/mapTiles/{z}/{x}/{y}.png`;
// Robuste Entfernung einer evtl. alten Leaflet-Instanz und Reset des DOM-Elements
if (mapContainer) {
if (mapContainer._leaflet_id) {
try {
// Leaflet-Instanz entfernen
if (mapContainer._leaflet_map && typeof mapContainer._leaflet_map.remove === "function") {
mapContainer._leaflet_map.remove();
}
// Leaflet 1.7+ speichert die Map-Instanz in L.Map._instances
if (L && L.Map && L.Map._instances && mapContainer._leaflet_id) {
delete L.Map._instances[mapContainer._leaflet_id];
}
// Auch in L.DomUtil._store ggf. entfernen
if (L && L.DomUtil && L.DomUtil._store && mapContainer._leaflet_id) {
delete L.DomUtil._store[mapContainer._leaflet_id];
}
// _leaflet_id vom DOM-Element entfernen
delete mapContainer._leaflet_id;
// Alle weiteren _leaflet-Properties entfernen
for (const key in mapContainer) {
if (key.startsWith("_leaflet")) {
try {
delete mapContainer[key];
} catch (e) {}
}
}
} catch (e) {
console.warn("Fehler beim Entfernen der alten Leaflet-Instanz:", e);
}
}
// Container leeren (immer, auch wenn keine Map-Instanz)
mapContainer.innerHTML = "";
}
const initMap = L.map(mapRef.current, {
center: [53.111111, 8.4625],
zoom: 12,
minZoom: 5,
maxZoom: 15,
zoomControl: false,
dragging: true,
contextmenu: true,
layers: [],
});
// --- CONFIG LOADING ---
let config = null;
let tileLayerUrl = "https://{s}.tile.openstreetmap.org/{z}/{x}/{y}.png";
let mapCenter = [53.111111, 8.4625];
let mapZoom = 12;
let minZoom = 5;
let maxZoom = 15;
try {
if (window && window.__leafletConfig) {
config = window.__leafletConfig;
} else {
const xhr = new XMLHttpRequest();
xhr.open("GET", "/config.json", false); // false = synchronous
xhr.send(null);
if (xhr.status === 200) {
config = JSON.parse(xhr.responseText);
window.__leafletConfig = config;
}
}
if (config) {
// basePath aus config.json
if (typeof config.basePath === "string") {
basePath = config.basePath;
}
// Tile source
if (config.tileSources && config.active && config.tileSources[config.active]) {
const tileSource = config.tileSources[config.active];
tileLayerUrl = tileSource.url || tileLayerUrl;
// Dynamische URL für Server-Tiles
if (tileLayerUrl.startsWith("/tiles") || tileLayerUrl.startsWith("tiles")) {
tileLayerUrl = `${window.location.origin.replace(/\/$/, "")}${
tileLayerUrl.startsWith("/") ? tileLayerUrl : "/" + tileLayerUrl
}`;
}
minZoom = tileSource.minZoom ?? minZoom;
maxZoom = tileSource.maxZoom ?? maxZoom;
}
// Center
if (Array.isArray(config.center)) {
mapCenter = config.center;
}
// Zoom (optional, fallback to 12)
if (typeof config.zoom === "number") {
mapZoom = config.zoom;
}
// minZoom/maxZoom global fallback
if (typeof config.minZoom === "number") {
minZoom = config.minZoom;
}
if (typeof config.maxZoom === "number") {
maxZoom = config.maxZoom;
}
}
} catch (e) {
// Fallback bleibt OSM und Defaults
}
let initMap;
try {
initMap = L.map(mapContainer, {
center: mapCenter,
zoom: mapZoom,
minZoom: minZoom,
maxZoom: maxZoom,
zoomControl: false,
dragging: true,
contextmenu: true,
layers: [],
});
} catch (e) {
if (e.message && e.message.includes("Map container is already initialized")) {
console.warn(
"Leaflet: Map container is already initialized. Map wird nicht erneut initialisiert."
);
return;
}
throw e;
}
initMap.dragging.enable();
L.tileLayer(tileLayerUrl, {
attribution: "&copy; Eigene Kartenquelle (offline)",
attribution: "&copy; OpenStreetMap contributors",
tileSize: 256,
minZoom: 5,
maxZoom: 15,
minZoom: minZoom,
maxZoom: maxZoom,
noWrap: true,
errorTileUrl: "/img/empty-tile.png", // Optional
}).addTo(initMap);
@@ -58,10 +155,8 @@ export const initializeMap = (
nearbyDistance: 20,
});
setMap(initMap);
setOms(overlappingMarkerSpiderfier);
if (typeof addItemsToMapContextMenu === "function") {
addItemsToMapContextMenu(initMap, setMenuItemAdded, setPolylineEventsDisabled);
}
return { map: initMap, oms: overlappingMarkerSpiderfier };
};

View File

@@ -62,7 +62,7 @@ export const checkOverlappingMarkers = (map, markers, plusIcon, oms) => {
export const handlePlusIconClick = (map, markers, oms, clickedLatLng) => {
// Debugging-Ausgabe
if (process.env.NEXT_PUBLIC_DEBUG_LOG === "true") {
if (getDebugLog()) {
console.log("Plus-Icon Position:", clickedLatLng);
}
@@ -72,7 +72,7 @@ export const handlePlusIconClick = (map, markers, oms, clickedLatLng) => {
);
// Debugging-Ausgabe
if (process.env.NEXT_PUBLIC_DEBUG_LOG === "true") {
if (getDebugLog()) {
console.log("Gefundene Marker in der Nähe:", nearbyMarkers);
}
@@ -80,7 +80,7 @@ export const handlePlusIconClick = (map, markers, oms, clickedLatLng) => {
// Spiderfy die gefundenen Marker
oms.spiderfy(nearbyMarkers);
} else {
if (process.env.NEXT_PUBLIC_DEBUG_LOG === "true") {
if (getDebugLog()) {
console.log("Keine überlappenden Marker gefunden.");
}
}

View File

@@ -1,9 +1,7 @@
import mysql from "mysql2/promise";
// Variablen für den Singleton-Pool
let cachedPool;
let connectionCount = 0; // Zähler für die aktiven Verbindungen
let connectionCount = 0;
// Funktion zum Abrufen des Pools
function getPool() {
if (!cachedPool) {
cachedPool = mysql.createPool({
@@ -12,12 +10,11 @@ function getPool() {
password: process.env.DB_PASSWORD,
database: process.env.DB_NAME,
port: process.env.DB_PORT,
connectionLimit: 20, // Setze ein Limit für gleichzeitige Verbindungen
connectionLimit: 20,
waitForConnections: true,
queueLimit: 10, // Warteschlangenlimit für Verbindungen
connectTimeout: 5000, // Timeout für Verbindungsversuche (5 Sekunden)
//acquireTimeout: 10000, // Timeout für Verbindungsanforderungen aus dem Pool (10 Sekunden)
idleTimeout: 60000, // 1 Minute
queueLimit: 10,
connectTimeout: 5000,
idleTimeout: 60000,
});
// Ereignisse für das Protokollieren der Verbindungsstatistiken
@@ -25,46 +22,27 @@ function getPool() {
cachedPool.on("acquire", () => {
connectionCount++;
if (process.env.NODE_ENV === "development") {
if (process.env.NEXT_PUBLIC_DEBUG_LOG === "true") {
console.log("\x1b[36m%s\x1b[0m", ` Connection acquired (${connectionCount} total)`);
}
if (connectionCount > maxUsed) {
maxUsed = connectionCount;
if (process.env.NEXT_PUBLIC_DEBUG_LOG === "true") {
console.log(`📈 Neue Höchstzahl aktiver gleichzeitiger Verbindungen: ${maxUsed}`);
}
}
// Debug-Logging entfernt
if (connectionCount > maxUsed) {
maxUsed = connectionCount;
}
});
cachedPool.on("release", () => {
connectionCount--;
if (process.env.NODE_ENV === "development") {
if (process.env.NEXT_PUBLIC_DEBUG_LOG === "true") {
console.log("\x1b[32m%s\x1b[0m", ` Connection released (${connectionCount} total)`);
}
}
});
cachedPool.on("enqueue", () => {
if (process.env.NODE_ENV === "development") {
if (process.env.NEXT_PUBLIC_DEBUG_LOG === "true") {
console.warn("\x1b[33m%s\x1b[0m", "⏳ Pool voll Anfrage in Warteschlange");
}
}
// Debug-Logging entfernt
});
}
return cachedPool;
}
// Optionale Funktion zum Schließen aller Verbindungen im Pool (manuell aufzurufen)
export function closePool() {
if (cachedPool) {
cachedPool.end(() => {
console.log("All pool connections closed.");
});
cachedPool = null; // Setze den Pool auf null, um sicherzustellen, dass er neu erstellt wird, falls benötigt.
cachedPool = null;
}
}

View File

@@ -1,7 +1,9 @@
// utils/openInNewTab.js
export function openInNewTab(e, target) {
const basePath = process.env.NEXT_PUBLIC_BASE_PATH || "";
export async function openInNewTab(e, target) {
const res = await fetch("/config.json");
const config = await res.json();
const basePath = config.basePath || "";
const url = new URL(window.location.origin);
const originWithoutPort = `${url.protocol}//${url.hostname}`; // ohne Port!
@@ -28,9 +30,7 @@ export function openInNewTab(e, target) {
}
if (link) {
if (process.env.NEXT_PUBLIC_DEBUG_LOG === "true") {
console.log("🟢 Öffne Link:", link);
}
console.log("🟢 Öffne Link:", link);
window.open(link, "_blank");
} else {
console.error("❌ Kein gültiger Link gefunden.");

View File

@@ -6,7 +6,7 @@ export function subscribeToPolylineContextMenu() {
store.subscribe(() => {
const state = store.getState(); // Redux-Toolkit empfohlene Methode
if (state.polylineContextMenu.forceClose) {
if (process.env.NEXT_PUBLIC_DEBUG_LOG === "true") {
if (getDebugLog()) {
console.log("🚀 Redux-Event erkannt - Kontextmenü wird geschlossen.");
}
store.dispatch(closePolylineContextMenu());

View File

@@ -17,7 +17,7 @@ import { updatePolylineCoordinatesThunk } from "../../redux/thunks/database/poly
import { openInNewTab } from "../../utils/openInNewTab";
//--------------------------------------------
export const setupPolylines = (
export const setupPolylines = async (
map,
linePositions,
lineColors,
@@ -29,20 +29,23 @@ export const setupPolylines = (
polylineVisible
) => {
const mode = process.env.NEXT_PUBLIC_API_PORT_MODE;
const basePath = process.env.NEXT_PUBLIC_BASE_PATH || "";
if (!polylineVisible) {
//console.warn("Polylines deaktiviert - keine Zeichnung");
return { markers: [], polylines: [] };
}
let basePath = "";
try {
const res = await fetch("/config.json");
if (res.ok) {
const config = await res.json();
basePath = config.basePath || "";
}
} catch (e) {}
if (!polylineVisible) {
// Entferne alle Polylinien, wenn sie ausgeblendet werden sollen
if (window.polylines) {
window.polylines.forEach(polyline => {
if (map.hasLayer(polyline)) {
if (map && map.hasLayer(polyline)) {
map.removeLayer(polyline);
}
});
window.polylines = [];
}
return { markers: [], polylines: [] };
}
@@ -110,7 +113,7 @@ export const setupPolylines = (
.dispatch(updatePolylineCoordinatesThunk(requestData))
.unwrap()
.then(data => {
if (process.env.NEXT_PUBLIC_DEBUG_LOG === "true") {
if (getDebugLog()) {
console.log("Koordinaten erfolgreich aktualisiert:", data);
}
})

View File

@@ -1,17 +1,19 @@
// utils/setupDevices.js
import { setSelectedDevice, clearSelectedDevice } from "../redux/slices/selectedDeviceSlice";
import { getDebugLog } from "./configUtils";
export const setupDevices = async (map, deviceMarkers, dispatch) => {
for (const marker of deviceMarkers) {
marker.on("mouseover", function () {
if (process.env.NEXT_PUBLIC_DEBUG_LOG === "true") {
if (getDebugLog()) {
console.log("✅ Gerät ausgewählt:", marker);
}
dispatch(setSelectedDevice(marker.options)); // Gerät in Redux speichern
});
marker.on("mouseout", function () {
if (process.env.NEXT_PUBLIC_DEBUG_LOG === "true") {
if (getDebugLog()) {
console.log("❌ Gerät abgewählt");
}
dispatch(clearSelectedDevice()); // Gerät aus Redux entfernen

View File

@@ -15,29 +15,71 @@ export const zoomIn = (e, map) => {
return;
}
const currentZoom = map.getZoom();
let maxZoom = 19;
try {
if (window && window.__tileSourceMaxZoom !== undefined) {
maxZoom = window.__tileSourceMaxZoom;
} else {
const xhr = new XMLHttpRequest();
xhr.open("GET", "/config.json", false);
xhr.send(null);
if (xhr.status === 200) {
const config = JSON.parse(xhr.responseText);
if (config.tileSources && config.active && config.tileSources[config.active]) {
maxZoom = config.tileSources[config.active].maxZoom;
}
}
}
} catch (e) {}
if (currentZoom < 14) {
map.flyTo(e.latlng, 14);
localStorage.setItem("mapZoom", 16);
const currentZoom = map.getZoom();
if (currentZoom < maxZoom) {
map.flyTo(e.latlng, currentZoom + 1);
localStorage.setItem("mapZoom", currentZoom + 1);
localStorage.setItem("mapCenter", JSON.stringify(map.getCenter()));
}
};
export const zoomOut = (map) => {
export const zoomOut = map => {
if (!map) {
console.error("map is not defined in zoomOut");
return;
}
let minZoom = 5;
try {
if (window && window.__tileSourceMinZoom !== undefined) {
minZoom = window.__tileSourceMinZoom;
} else {
const xhr = new XMLHttpRequest();
xhr.open("GET", "/config.json", false);
xhr.send(null);
if (xhr.status === 200) {
const config = JSON.parse(xhr.responseText);
if (config.tileSources && config.active && config.tileSources[config.active]) {
minZoom = config.tileSources[config.active].minZoom;
}
}
}
} catch (e) {}
const currentZoom = map.getZoom();
if (currentZoom > minZoom) {
let zoomOutCenter = [51.41321407879154, 7.739617925303934];
try {
const xhr = new XMLHttpRequest();
xhr.open("GET", "/config.json", false);
xhr.send(null);
if (xhr.status === 200) {
const config = JSON.parse(xhr.responseText);
if (Array.isArray(config.zoomOutCenter) && config.zoomOutCenter.length === 2) {
zoomOutCenter = config.zoomOutCenter;
}
}
} catch (e) {}
const zoom = minZoom;
if (currentZoom > 7) {
const x = 51.41321407879154;
const y = 7.739617925303934;
const zoom = 7;
map.flyTo([x, y], zoom);
map.flyTo(zoomOutCenter, zoom);
localStorage.setItem("mapZoom", zoom);
localStorage.setItem("mapCenter", JSON.stringify(map.getCenter()));
}

File diff suppressed because it is too large Load Diff

View File

@@ -1,153 +1,85 @@
[
{
"LD_Name": "CPL Ismail4",
"IdLD": 50922,
"Device": "CPL V3.5 mit 24 Kü",
"Link": "cpl.aspx?ver=35&kue=24&id=50922",
"LD_Name": "CPL Kai Schmidt",
"IdLD": 50977,
"Device": "CPL V3.5 mit 24 Kü",
"Link": "cpl.aspx?ver=35&kue=24&id=50977",
"Location_Name": "Littwin",
"Location_Short": "LTW",
"IdLocation": 24101,
"Area_Name": "Rastede",
"Area_Name": "Rastede 2",
"Area_Short": "",
"IdArea": 20998,
"X": 53.242157,
"Y": 8.160353,
"X": 53.245957,
"Y": 8.164172,
"Icon": 20,
"System": 1,
"Active": 1
},
{
"LD_Name": "LR 77 ISA",
"IdLD": 50935,
"Device": "LTE Modem LR77",
"Link": "lr77.aspx?ver=1&id=50935",
"LD_Name": "GMA-isa-test",
"IdLD": 50981,
"Device": "Glättemeldeanlage",
"Link": "gma.aspx?ver=1&id=50981",
"Location_Name": "Littwin",
"Location_Short": "LTW",
"IdLocation": 24101,
"Area_Name": "Rastede",
"Area_Name": "Rastede 2",
"Area_Short": "",
"IdArea": 20998,
"X": 53.242157,
"Y": 8.160353,
"Icon": 12,
"System": 5,
"Active": 1
},
{
"LD_Name": "Cisco Router 1841",
"IdLD": 50936,
"Device": "Cisco 1841",
"Link": "cisco1841.aspx?ver=1&id=50936",
"Location_Name": "Littwin",
"Location_Short": "LTW",
"IdLocation": 24101,
"Area_Name": "Rastede",
"Area_Short": "",
"IdArea": 20998,
"X": 53.242157,
"Y": 8.160353,
"Icon": 21,
"System": 6,
"Active": 1
},
{
"LD_Name": "GMA Testgerät ISA",
"IdLD": 50937,
"Device": "Glättemeldeanlage",
"Link": "gma.aspx?ver=1&id=50937",
"Location_Name": "Littwin",
"Location_Short": "LTW",
"IdLocation": 24101,
"Area_Name": "Rastede",
"Area_Short": "",
"IdArea": 20998,
"X": 53.242157,
"Y": 8.160353,
"X": 53.245957,
"Y": 8.164172,
"Icon": 1,
"System": 11,
"Active": 1
},
{
"LD_Name": "SMS-Funkmodem",
"IdLD": 50938,
"Device": "SMS Funkmodem",
"Link": "sms_modem.aspx?ver=1&id=50938",
"LD_Name": "Cisco-isa",
"IdLD": 50982,
"Device": "Cisco 1841",
"Link": "cisco1841.aspx?ver=1&id=50982",
"Location_Name": "Littwin",
"Location_Short": "LTW",
"IdLocation": 24101,
"Area_Name": "Rastede",
"Area_Name": "Rastede 2",
"Area_Short": "",
"IdArea": 20998,
"X": 53.242157,
"Y": 8.160353,
"Icon": 12,
"System": 111,
"X": 53.245957,
"Y": 8.164172,
"Icon": 21,
"System": 6,
"Active": 1
},
{
"LD_Name": "TALAS Meldestationen ISA",
"IdLD": 50939,
"Device": "CPL V3.5 mit 16 Kü",
"Link": "cpl.aspx?ver=35&kue=16&id=50939",
"LD_Name": "CPL Ganz neu",
"IdLD": 50984,
"Device": "CPL V3.5 mit 24 Kü",
"Link": "cpl.aspx?ver=35&kue=24&id=50984",
"Location_Name": "Littwin",
"Location_Short": "LTW",
"IdLocation": 24101,
"Area_Name": "Rastede",
"Area_Name": "Rastede 2",
"Area_Short": "",
"IdArea": 20998,
"X": 53.242157,
"Y": 8.160353,
"X": 53.245957,
"Y": 8.164172,
"Icon": 20,
"System": 1,
"Active": 1
},
{
"LD_Name": "WAGO Klemmen ISA",
"IdLD": 50941,
"Device": "WAGO 16 DE",
"Link": "wago.aspx?ver=1&DE=16&id=50941",
"Location_Name": "Littwin",
"Location_Short": "LTW",
"IdLocation": 24101,
"Area_Name": "Rastede",
"Area_Short": "",
"IdArea": 20998,
"X": 53.242157,
"Y": 8.160353,
"Icon": 9,
"System": 7,
"Active": 1
},
{
"LD_Name": "Cisco 1921",
"IdLD": 50942,
"Device": "Cisco 1921",
"Link": "cisco1921.aspx?ver=1&id=50942",
"Location_Name": "Littwin",
"Location_Short": "LTW",
"IdLocation": 24101,
"Area_Name": "Rastede",
"Area_Short": "",
"IdArea": 20998,
"X": 53.242157,
"Y": 8.160353,
"Icon": 21,
"System": 6,
"Active": 1
},
{
"LD_Name": "Cisco 8200",
"IdLD": 50943,
"LD_Name": "Entwicklung KAS Cisco 8200 für LVZ 2025",
"IdLD": 50986,
"Device": "Cisco 8200",
"Link": "cisco8200.aspx?ver=1&id=50943",
"Location_Name": "Littwin",
"Location_Short": "LTW",
"IdLocation": 24101,
"Link": "cisco8200.aspx?ver=1&id=50986",
"Location_Name": "SW-Entwicklung 1.01",
"Location_Short": "SW101",
"IdLocation": 24102,
"Area_Name": "Rastede",
"Area_Short": "",
"IdArea": 20998,
"X": 53.242157,
"Y": 8.160353,
"Area_Short": "RAST-007",
"IdArea": 18192,
"X": 53.24615,
"Y": 8.16237,
"Icon": 21,
"System": 6,
"Active": 1

View File

@@ -4,25 +4,7 @@
"Na": "system",
"Le": 4,
"Co": "#FF00FF",
"Me": "Eingang DE 01 kommend test2",
"Feld": 4,
"Icon": 0
},
{
"IdLD": 50922,
"Na": "system",
"Le": 4,
"Co": "#FF00FF",
"Me": "Eingang DE 05 kommend",
"Feld": 4,
"Icon": 0
},
{
"IdLD": 50922,
"Na": "system",
"Le": 4,
"Co": "#FF00FF",
"Me": "Eingang DE 17 kommend",
"Me": "Eingang DE 01 kommend",
"Feld": 4,
"Icon": 0
},
@@ -40,10 +22,127 @@
"Na": "system",
"Le": 4,
"Co": "#FF00FF",
"Me": "Eingang DE 17 kommend",
"Feld": 4,
"Icon": 0
},
{
"IdLD": 50922,
"Na": "system",
"Le": 4,
"Co": "#FF00FF",
"Me": "Eingang DE 05 kommend",
"Feld": 4,
"Icon": 0
},
{
"IdLD": 50984,
"Na": "system",
"Le": 4,
"Co": "#FF00FF",
"Me": "Eingang DE 20 kommend",
"Feld": 4,
"Icon": 0
},
{
"IdLD": 50975,
"Na": "system",
"Le": 4,
"Co": "#FF00FF",
"Me": "Eingang DE 32 kommend",
"Feld": 4,
"Icon": 0
},
{
"IdLD": 50984,
"Na": "system",
"Le": 4,
"Co": "#FF00FF",
"Me": "Eingang DE 01 kommend",
"Feld": 4,
"Icon": 0
},
{
"IdLD": 50977,
"Na": "system",
"Le": 4,
"Co": "#FF00FF",
"Me": "Station offline",
"Feld": 4,
"Icon": 0
},
{
"IdLD": 50977,
"Na": "system",
"Le": 4,
"Co": "#FF00FF",
"Me": "Eingang DE 01 kommend",
"Feld": 4,
"Icon": 0
},
{
"IdLD": 50975,
"Na": "system",
"Le": 4,
"Co": "#FF00FF",
"Me": "Station offline",
"Feld": 4,
"Icon": 0
},
{
"IdLD": 50066,
"Na": "system",
"Le": 4,
"Co": "#FF00FF",
"Me": "CPL offline",
"Feld": 5,
"Icon": 0
},
{
"IdLD": 50011,
"Na": "system",
"Le": 4,
"Co": "#FF00FF",
"Me": "CPL offline",
"Feld": 16,
"Icon": 0
},
{
"IdLD": 50011,
"Na": "system",
"Le": 4,
"Co": "#FF00FF",
"Me": "Wasserdruck aus",
"Feld": 16,
"Icon": 0
},
{
"IdLD": 50011,
"Na": "system",
"Le": 4,
"Co": "#FF00FF",
"Me": "Ein",
"Feld": 16,
"Icon": 0
},
{
"IdLD": 50011,
"Na": "system",
"Le": 4,
"Co": "#FF00FF",
"Me": "Digitaleingang 1 ON",
"Feld": 16,
"Icon": 0
},
{
"IdLD": 50000,
"Na": "system",
"Le": 4,
"Co": "#FF00FF",
"Me": "Ein",
"Feld": 4,
"Icon": 0
},
{
"IdLD": 50922,
"Na": "system",
@@ -53,6 +152,51 @@
"Feld": 4,
"Icon": 0
},
{
"IdLD": 50922,
"Na": "system",
"Le": 4,
"Co": "#FF00FF",
"Me": "Eingang DE 32 kommend",
"Feld": 4,
"Icon": 0
},
{
"IdLD": 50975,
"Na": "minor",
"Le": 3,
"Co": "#FFFF00",
"Me": "KÜG 07: Überspannung kommend",
"Feld": 4,
"Icon": 0
},
{
"IdLD": 50975,
"Na": "minor",
"Le": 3,
"Co": "#FFFF00",
"Me": "KÜG 08: Überspannung gehend",
"Feld": 4,
"Icon": 0
},
{
"IdLD": 50977,
"Na": "minor",
"Le": 3,
"Co": "#FFFF00",
"Me": "KÜG 08: Überspannung gehend",
"Feld": 4,
"Icon": 0
},
{
"IdLD": 50984,
"Na": "minor",
"Le": 3,
"Co": "#FFFF00",
"Me": "KÜG 08: Überspannung gehend",
"Feld": 4,
"Icon": 0
},
{
"IdLD": 50922,
"Na": "minor",
@@ -76,7 +220,16 @@
"Na": "major",
"Le": 2,
"Co": "#FF9900",
"Me": "Eingang DE 03 kommend",
"Me": "Eingang DE 03 Test Karte kommend",
"Feld": 4,
"Icon": 0
},
{
"IdLD": 50922,
"Na": "critical",
"Le": 1,
"Co": "#FF0000",
"Me": "KÜG 01: Aderbruch kommend",
"Feld": 4,
"Icon": 0
},
@@ -97,5 +250,167 @@
"Me": "KÜG 03: Aderbruch kommend",
"Feld": 4,
"Icon": 0
},
{
"IdLD": 50000,
"Na": "critical",
"Le": 1,
"Co": "#FF0000",
"Me": "über 8V kommend",
"Feld": 4,
"Icon": 0
},
{
"IdLD": 50984,
"Na": "critical",
"Le": 1,
"Co": "#FF0000",
"Me": "KÜG 05: Aderbruch kommend",
"Feld": 4,
"Icon": 0
},
{
"IdLD": 50984,
"Na": "critical",
"Le": 1,
"Co": "#FF0000",
"Me": "KÜG 02: Isolationsminderung kommend",
"Feld": 4,
"Icon": 0
},
{
"IdLD": 50984,
"Na": "critical",
"Le": 1,
"Co": "#FF0000",
"Me": "KÜG 06: Aderbruch kommend",
"Feld": 4,
"Icon": 0
},
{
"IdLD": 50977,
"Na": "critical",
"Le": 1,
"Co": "#FF0000",
"Me": "KÜG 01: Isolationsminderung kommend",
"Feld": 4,
"Icon": 0
},
{
"IdLD": 50977,
"Na": "critical",
"Le": 1,
"Co": "#FF0000",
"Me": "KÜG 06: Aderbruch kommend",
"Feld": 4,
"Icon": 0
},
{
"IdLD": 50977,
"Na": "critical",
"Le": 1,
"Co": "#FF0000",
"Me": "KÜG 05: Aderbruch kommend",
"Feld": 4,
"Icon": 0
},
{
"IdLD": 50976,
"Na": "critical",
"Le": 1,
"Co": "#FF0000",
"Me": "CPL offline",
"Feld": 3,
"Icon": 0
},
{
"IdLD": 50976,
"Na": "critical",
"Le": 1,
"Co": "#FF0000",
"Me": "KÜG 03: Isolationsminderung kommend",
"Feld": 3,
"Icon": 0
},
{
"IdLD": 50975,
"Na": "critical",
"Le": 1,
"Co": "#FF0000",
"Me": "KÜG 04: Isolationsminderung kommend",
"Feld": 4,
"Icon": 0
},
{
"IdLD": 50975,
"Na": "critical",
"Le": 1,
"Co": "#FF0000",
"Me": "KÜG 02: Isolationsminderung kommend",
"Feld": 4,
"Icon": 0
},
{
"IdLD": 50975,
"Na": "critical",
"Le": 1,
"Co": "#FF0000",
"Me": "KÜG 01: Isolationsminderung kommend",
"Feld": 4,
"Icon": 0
},
{
"IdLD": 50001,
"Na": "critical",
"Le": 1,
"Co": "#FF0000",
"Me": "Sammelstörung kommend",
"Feld": 5,
"Icon": 0
},
{
"IdLD": 50975,
"Na": "critical",
"Le": 1,
"Co": "#FF0000",
"Me": "KÜG 06: Aderbruch kommend",
"Feld": 4,
"Icon": 0
},
{
"IdLD": 50975,
"Na": "critical",
"Le": 1,
"Co": "#FF0000",
"Me": "KÜG 05: Aderbruch kommend",
"Feld": 4,
"Icon": 0
},
{
"IdLD": 50963,
"Na": "critical",
"Le": 1,
"Co": "#FF0000",
"Me": "CPL offline",
"Feld": 3,
"Icon": 0
},
{
"IdLD": 50063,
"Na": "critical",
"Le": 1,
"Co": "#FF0000",
"Me": "Digitaleingang 1 EIN",
"Feld": 4,
"Icon": 0
},
{
"IdLD": 50000,
"Na": "critical",
"Le": 1,
"Co": "#FF0000",
"Me": "über 10V kommend",
"Feld": 4,
"Icon": 0
}
]

View File

@@ -4,111 +4,303 @@
"Name": "TALAS",
"Longname": "Talas Meldestationen",
"Allow": 1,
"Icon": 1
"Icon": 20,
"Map": 1
},
{
"IdSystem": 1,
"Name": "TALAS",
"Longname": "Talas Meldestationen",
"Allow": 0,
"Icon": 20,
"Map": 1
},
{
"IdSystem": 2,
"Name": "ECI",
"Longname": "ECI Geräte",
"Allow": 0,
"Icon": 17,
"Map": 1
},
{
"IdSystem": 3,
"Name": "ULAF",
"Longname": "ULAF Geräte",
"Allow": 1,
"Icon": 2
"Icon": 14,
"Map": 1
},
{
"IdSystem": 3,
"Name": "ULAF",
"Longname": "ULAF Geräte",
"Allow": 0,
"Icon": 3
"Icon": 14,
"Map": 1
},
{
"IdSystem": 5,
"Name": "GSM Modem",
"Longname": "LR77 GSM Modems",
"Allow": 1,
"Icon": 5
"Icon": 12,
"Map": 1
},
{
"IdSystem": 5,
"Name": "GSM Modem",
"Longname": "LR77 GSM Modems",
"Allow": 0,
"Icon": 12,
"Map": 1
},
{
"IdSystem": 6,
"Name": "Cisco Router",
"Longname": "Cisco Router",
"Allow": 0,
"Icon": 21,
"Map": 1
},
{
"IdSystem": 6,
"Name": "Cisco Router",
"Longname": "Cisco Router",
"Allow": 1,
"Icon": 6
"Icon": 21,
"Map": 1
},
{
"IdSystem": 6,
"Name": "Cisco Router",
"Longname": "Cisco Router",
"Allow": 0,
"Icon": 21,
"Map": 1
},
{
"IdSystem": 7,
"Name": "WAGO",
"Longname": "WAGO I/O Systeme",
"Allow": 1,
"Icon": 7
"Icon": 9,
"Map": 1
},
{
"IdSystem": 7,
"Name": "WAGO",
"Longname": "WAGO I/O Systeme",
"Allow": 0,
"Icon": 9,
"Map": 1
},
{
"IdSystem": 7,
"Name": "WAGO",
"Longname": "WAGO I/O Systeme",
"Allow": 0,
"Icon": 9,
"Map": 1
},
{
"IdSystem": 7,
"Name": "WAGO",
"Longname": "WAGO I/O Systeme",
"Allow": 0,
"Icon": 9,
"Map": 1
},
{
"IdSystem": 8,
"Name": "Siemens",
"Longname": "Siemens Notrufsysteme",
"Allow": 1,
"Icon": 8
"Icon": 19,
"Map": 1
},
{
"IdSystem": 8,
"Name": "Siemens",
"Longname": "Siemens Notrufsysteme",
"Allow": 0,
"Icon": 19,
"Map": 1
},
{
"IdSystem": 9,
"Name": "OTDR",
"Longname": "Glasfaserüberwachung OTU",
"Allow": 1,
"Icon": 9
"Icon": 24,
"Map": 1
},
{
"IdSystem": 9,
"Name": "OTDR",
"Longname": "Glasfaserüberwachung OTU",
"Allow": 0,
"Icon": 24,
"Map": 1
},
{
"IdSystem": 9,
"Name": "OTDR",
"Longname": "Glasfaserüberwachung OTU",
"Allow": 0,
"Icon": 24,
"Map": 1
},
{
"IdSystem": 9,
"Name": "OTDR",
"Longname": "Glasfaserüberwachung OTU",
"Allow": 0,
"Icon": 24,
"Map": 1
},
{
"IdSystem": 10,
"Name": "WDM",
"Longname": " Wavelength Division Multiplexing",
"Allow": 1,
"Icon": 10
"Icon": 24,
"Map": 1
},
{
"IdSystem": 10,
"Name": "WDM",
"Longname": " Wavelength Division Multiplexing",
"Allow": 0,
"Icon": 24,
"Map": 1
},
{
"IdSystem": 10,
"Name": "WDM",
"Longname": " Wavelength Division Multiplexing",
"Allow": 0,
"Icon": 24,
"Map": 1
},
{
"IdSystem": 11,
"Name": "GMA",
"Longname": "Glättemeldeanlagen",
"Allow": 1,
"Icon": 11
"Icon": 1,
"Map": 1
},
{
"IdSystem": 11,
"Name": "GMA",
"Longname": "Glättemeldeanlagen",
"Allow": 0,
"Icon": 1,
"Map": 1
},
{
"IdSystem": 11,
"Name": "GMA",
"Longname": "Glättemeldeanlagen",
"Allow": 0,
"Icon": 1,
"Map": 1
},
{
"IdSystem": 13,
"Name": "Messstellen",
"Longname": "Messstellen",
"Allow": 1,
"Icon": 13
"Allow": 0,
"Icon": 23,
"Map": 1
},
{
"IdSystem": 30,
"Name": "TK-Komponenten",
"Longname": "TK-Komponenten",
"Allow": 1,
"Icon": 30
"Icon": 14,
"Map": 1
},
{
"IdSystem": 30,
"Name": "TK-Komponenten",
"Longname": "TK-Komponenten",
"Allow": 0,
"Icon": 14,
"Map": 1
},
{
"IdSystem": 100,
"Name": "TALAS ICL",
"Longname": "Talas ICL Unterstationen",
"Allow": 1,
"Icon": 100
"Icon": 23,
"Map": 1
},
{
"IdSystem": 100,
"Name": "TALAS ICL",
"Longname": "Talas ICL Unterstationen",
"Allow": 0,
"Icon": 23,
"Map": 1
},
{
"IdSystem": 110,
"Name": "DAUZ",
"Longname": "Dauerzählstellen",
"Allow": 0,
"Icon": 14,
"Map": 1
},
{
"IdSystem": 110,
"Name": "DAUZ",
"Longname": "Dauerzählstellen",
"Allow": 1,
"Icon": 110
"Icon": 14,
"Map": 1
},
{
"IdSystem": 110,
"Name": "DAUZ",
"Longname": "Dauerzählstellen",
"Allow": 0,
"Icon": 14,
"Map": 1
},
{
"IdSystem": 111,
"Name": "SMS Modem",
"Longname": "SMS Modem",
"Allow": 1,
"Icon": 111
"Icon": 12,
"Map": 1
},
{
"IdSystem": 111,
"Name": "SMS Modem",
"Longname": "SMS Modem",
"Allow": 0,
"Icon": 12,
"Map": 1
},
{
"IdSystem": 200,
"Name": "Sonstige",
"Longname": "Sonstige",
"Allow": 1,
"Icon": 200
"Icon": 31,
"Map": 1
},
{
"IdSystem": 200,
"Name": "Sonstige",
"Longname": "Sonstige",
"Allow": 0,
"Icon": 31,
"Map": 1
}
]