From 004af608fccfcc7fd8d85c69561094a7e0bebe45 Mon Sep 17 00:00:00 2001 From: ISA Date: Tue, 10 Jun 2025 08:52:17 +0200 Subject: [PATCH] fix: POI Bearbeiten --- TODO.md | 4 +- config/appVersion.js | 2 +- docs/architecture.md | 126 +++++++++++++++---- utils/markerUtils.js | 40 +----- utils/poiUtils.js | 38 +++++- utils/setupPOIs.js | 8 +- websocketDump/GisLinesStatus.json | 2 +- websocketDump/GisStationsMeasurements.json | 2 +- websocketDump/GisStationsStaticDistrict.json | 2 +- websocketDump/GisStationsStatusDistrict.json | 2 +- 10 files changed, 155 insertions(+), 71 deletions(-) diff --git a/TODO.md b/TODO.md index 5bb891047..dd2ad79be 100644 --- a/TODO.md +++ b/TODO.md @@ -44,6 +44,8 @@ Polling-Mechanismus → unbedingt prüfen, ob `clearInterval()` im useEffect-Cleanup enthalten ist. --[ ] TODO: WebSocket für webService erstellen , falls etwas geändert wird dann soll aktualisiert, +-[x] TODO: WebSocket für webService erstellen , falls etwas geändert wird dann soll aktualisiert, optimiert besser als setInterval, zuerst nur für TALAS.web WebServices erstellen, irgendwann soll die Daten von DB auch mit WebSocket gelöst werden + +- [ ] TODO: POI bearbeiten funktioniert es nicht diff --git a/config/appVersion.js b/config/appVersion.js index cd1253a47..e8dd07dec 100644 --- a/config/appVersion.js +++ b/config/appVersion.js @@ -1,2 +1,2 @@ // /config/appVersion -export const APP_VERSION = "1.1.251"; +export const APP_VERSION = "1.1.252"; diff --git a/docs/architecture.md b/docs/architecture.md index ba8cf36de..510d694bb 100644 --- a/docs/architecture.md +++ b/docs/architecture.md @@ -2,7 +2,9 @@ # 🧠 Architekturübersicht – NodeMap -Dieses Dokument beschreibt die technische Gesamtarchitektur des Projekts **NodeMap**, einer kartenbasierten Webanwendung zur Anzeige, Bearbeitung und Verwaltung von GIS-Daten, POIs und Gerätestatus. +Dieses Dokument beschreibt die technische Gesamtarchitektur des Projekts **NodeMap**, einer +kartenbasierten Webanwendung zur Anzeige, Bearbeitung und Verwaltung von GIS-Daten, POIs und +Gerätestatus. --- @@ -64,7 +66,8 @@ Dieses Dokument beschreibt die technische Gesamtarchitektur des Projekts **NodeM ## 🧩 Besonderheiten - **Konfigurierbarer basePath:** - Pfad wie `/talas5` ist optional und kann per `NEXT_PUBLIC_BASE_PATH` in `.env.local` gesetzt werden. + Pfad wie `/talas5` ist optional und kann per `NEXT_PUBLIC_BASE_PATH` in `.env.local` gesetzt + werden. - **Rechteabhängige UI:** Funktionen (z. B. POI bearbeiten) basieren auf Benutzerrechten (`IdRight`) vom Server. - **Zentrale Komponentensteuerung:** @@ -78,7 +81,8 @@ Dieses Dokument beschreibt die technische Gesamtarchitektur des Projekts **NodeM - Version ist in `appVersion.js` definiert → wird über `NEXT_PUBLIC_APP_VERSION` eingeblendet - Build erfolgt via `npm run build`, Auslieferung über `.next/` -- Nicht benötigte Dateien wie `__tests__`, `docs/`, `scripts/` etc. werden nicht in den Build aufgenommen +- Nicht benötigte Dateien wie `__tests__`, `docs/`, `scripts/` etc. werden nicht in den Build + aufgenommen --- @@ -141,24 +145,17 @@ flowchart TD --- -Jetzt (dynamisch & Redux-basiert): -MapComponent.js ruft folgenden Hook auf: +Jetzt (dynamisch & Redux-basiert): MapComponent.js ruft folgenden Hook auf: -js -Copy -Edit -const { markerStates, layerRefs } = useDynamicDeviceLayers(map, GisSystemStatic, mapLayersVisibility, priorityConfig, oms); -useDynamicDeviceLayers.js verarbeitet die GisSystemStatic-Liste: +js Copy Edit const { markerStates, layerRefs } = useDynamicDeviceLayers(map, GisSystemStatic, +mapLayersVisibility, priorityConfig, oms); useDynamicDeviceLayers.js verarbeitet die +GisSystemStatic-Liste: Jedes System (z. B. "TALAS", "ECI", "Cisco") bekommt einen eigenen Marker-Layer. Die Marker werden erstellt durch: -js -Copy -Edit -createAndSetDevices(...) // Systemweise Marker erzeugen -createAndSetDevices.js: +js Copy Edit createAndSetDevices(...) // Systemweise Marker erzeugen createAndSetDevices.js: Filtert alle Stations aus staticDistrictData, deren System === IdSystem. @@ -170,11 +167,7 @@ Ruft setMarkersFunction(markers) auf → Übergibt die Marker zurück an den Hoo Der Hook speichert: -js -Copy -Edit -setMarkerStates((prev) => ({ ...prev, [Name]: newMarkers })); -MapComponent.js hat dann: +js Copy Edit setMarkerStates((prev) => ({ ...prev, [Name]: newMarkers })); MapComponent.js hat dann: Zugriff auf alle Marker dynamisch über markerStates (ein Objekt mit Schlüssel = Systemname) @@ -182,9 +175,11 @@ Sichtbarkeit und OverlappingMarkerSpiderfier werden damit verarbeitet. --- -🔁 Die Geräte-Marker sind nicht mehr fest codiert, sondern werden dynamisch erzeugt anhand der Webservice-Daten GisSystemStatic. +🔁 Die Geräte-Marker sind nicht mehr fest codiert, sondern werden dynamisch erzeugt anhand der +Webservice-Daten GisSystemStatic. -🔄 Sichtbarkeit (Checkbox im Control Panel) löst ein Event visibilityChanged aus → MapComponent reagiert und rendert Marker neu. +🔄 Sichtbarkeit (Checkbox im Control Panel) löst ein Event visibilityChanged aus → MapComponent +reagiert und rendert Marker neu. 🕷️ Überlappende Marker werden mit checkOverlappingMarkers + PlusRoundIcon verarbeitet. @@ -202,3 +197,90 @@ flowchart TD A2 --> I1[Map aktualisiert Marker] A2 --> I2[checkOverlappingMarkers mit OMS] ``` + +--- + +10.06.2025 + +# Datenfluss-Konzept: WebSocket ↔ Redux ↔ UI + +Dieses Dokument beschreibt den technischen Ablauf des Live-Datenflusses im NodeMap-Projekt, um neue +Entwickler\:innen beim Onboarding zu unterstützen. + +## ᵀᵃᵗᵉᵖᵏᴼᵏᴼᵉ: Architekturübersicht + +```mermaid +sequenceDiagram + autonumber + participant WS_Server as WebSocket Server + participant WebService as TALAS WebService + participant Browser + participant ReduxStore as Redux Store + participant UI as React Leaflet UI + + loop Alle 5 Sekunden + WS_Server->>WebService: fetch endpointX + alt Daten haben sich geändert + WS_Server-->>Browser: emit 'endpointXUpdated' + Browser->>ReduxStore: dispatch(fetchEndpointXThunk()) + ReduxStore->>WebService: fetch endpointX + WebService-->>ReduxStore: JSON response + ReduxStore-->>UI: update Slice + UI-->>User: re-render Markers + else Keine Änderung + WS_Server-->>WS_Server: keine Aktion + end + end +``` + +## Beteiligte Komponenten + +### WebSocket Server (`server.js`) + +- Ruft regelmäßig (`setInterval`) die Webservice-Endpunkte auf. +- Erkennt Änderungen durch JSON-Vergleich (`JSON.stringify`). +- Sendet WebSocket-Events nur bei echten Änderungen. + +### Client: MapComponent + +- Hört auf `socket.on("endpointXUpdated")`. +- Ruft dann gezielt den passenden Redux-Thunk auf (z. B. `fetchGisLinesStatusThunk`). + +### Redux Store & Thunks + +- Jeder Endpunkt besitzt: + + - einen `Service` (API-Fetch) + - einen `Thunk` (Redux-Logik) + - einen `Slice` (State-Verwaltung) + +### React UI (Leaflet Map) + +- Beobachtet relevante Redux-Slices via `useSelector()`. +- Aktualisiert Marker, Tooltip und Popup über `createAndSetDevices()` und + `useDynamicDeviceLayers()`. + +## Beispiel-Endpunkte + +| Endpunktname | WebSocket Event | Redux Thunk | +| --------------------------- | ---------------------------------- | --------------------------------------- | +| `GisLinesStatus` | `GisLinesStatusUpdated` | `fetchGisLinesStatusThunk()` | +| `GisStationsMeasurements` | `GisStationsMeasurementsUpdated` | `fetchGisStationsMeasurementsThunk()` | +| `GisStationsStaticDistrict` | `GisStationsStaticDistrictUpdated` | `fetchGisStationsStaticDistrictThunk()` | +| `GisStationsStatusDistrict` | `GisStationsStatusDistrictUpdated` | `fetchGisStationsStatusDistrictThunk()` | + +## Vorteile + +- UI aktualisiert sich nur bei echten Datenänderungen → weniger Re-Renders. +- Live-Synchronisation zwischen Datenquelle und Anzeige. +- Skalierbar für beliebige Endpunkte. + +## ToDo/Erweiterungen + +- Automatische Reconnect-Logik für WebSocket. +- Anzeige des letzten Update-Zeitpunkts in UI. +- Logging-UI für WebSocket-Messages zur Diagnose. + +--- + +> Letzte Änderung: `{{heutiges Datum}}` von Ismail Ali diff --git a/utils/markerUtils.js b/utils/markerUtils.js index 1f1bd0f9c..7ffc551b4 100644 --- a/utils/markerUtils.js +++ b/utils/markerUtils.js @@ -1,10 +1,10 @@ // /utils/markerUtils.js import L from "leaflet"; import { toast } from "react-toastify"; -import circleIcon from "../components/CircleIcon"; +import circleIcon from "@/components/gisPolylines/icons/CircleIcon"; import { store } from "../redux/store"; -import { updatePolylineCoordinatesThunk } from "../redux/thunks/database/polylines/updatePolylineCoordinatesThunk"; -import { redrawPolyline } from "./mapUtils"; +import { updatePolylineCoordinatesThunk } from "@/redux/thunks/database/polylines/updatePolylineCoordinatesThunk"; +import { redrawPolyline } from "@/utils/mapUtils"; import { cleanupMarkers } from "@/utils/common/cleanupMarkers"; const savePolylineRedux = lineData => { @@ -57,37 +57,3 @@ export const removeMarker = (marker, lineData, currentZoom, currentCenter) => { window.location.reload(); } }; - -export const handleEditPoi = ( - marker, - userRights, - setCurrentPoiData, - setShowPoiUpdateModal, - fetchPoiData, - toast -) => { - if (!Array.isArray(userRights)) { - toast.error("Benutzerrechte sind ungültig.", { - position: "top-center", - autoClose: 5000, - }); - return; - } - - if (!userRights.some(r => r.IdRight === 56)) { - toast.error("Benutzer hat keine Berechtigung zum Bearbeiten.", { - position: "top-center", - autoClose: 5000, - }); - return; - } - - setCurrentPoiData({ - idPoi: marker.options.id, - name: marker.options.name, - description: marker.options.description, - }); - - fetchPoiData(marker.options.id); - setShowPoiUpdateModal(true); -}; diff --git a/utils/poiUtils.js b/utils/poiUtils.js index 7f1042b01..2ed42aeaf 100644 --- a/utils/poiUtils.js +++ b/utils/poiUtils.js @@ -1,8 +1,8 @@ // /utils/poiUtils.js import L from "leaflet"; import { toast } from "react-toastify"; -import circleIcon from "../components/gisPolylines/icons/CircleIcon.js"; -import { redrawPolyline } from "./polylines/redrawPolyline.js"; +import circleIcon from "@/components/gisPolylines/icons/CircleIcon.js"; +import { redrawPolyline } from "@/utils/polylines/redrawPolyline.js"; import { store } from "../redux/store"; import { updatePolylineCoordinatesThunk } from "../redux/thunks/database/polylines/updatePolylineCoordinatesThunk"; @@ -67,3 +67,37 @@ export const updateMarkerPosition = (newCoords, lineData, marker) => { if (!marker) return; marker.setLatLng([newCoords.lat, newCoords.lng]); }; + +export const handleEditPoi = ( + marker, + userRights, + setCurrentPoiData, + setShowPoiUpdateModal, + fetchPoiData, + toast +) => { + if (!Array.isArray(userRights)) { + toast.error("Benutzerrechte sind ungültig.", { + position: "top-center", + autoClose: 5000, + }); + return; + } + + if (!userRights.some(r => r.IdRight === 56)) { + toast.error("Benutzer hat keine Berechtigung zum Bearbeiten.", { + position: "top-center", + autoClose: 5000, + }); + return; + } + + setCurrentPoiData({ + idPoi: marker.options.id, + name: marker.options.name, + description: marker.options.description, + }); + + fetchPoiData(marker.options.id); + setShowPoiUpdateModal(true); +}; diff --git a/utils/setupPOIs.js b/utils/setupPOIs.js index bb200e4bd..2f7395c6b 100644 --- a/utils/setupPOIs.js +++ b/utils/setupPOIs.js @@ -1,8 +1,8 @@ // utils/setupPOIs.js -import { parsePoint } from "./geometryUtils"; -import { handleEditPoi } from "./poiUtils"; -import { updateLocationInDatabaseService } from "../services/database/updateLocationInDatabaseService"; -import { setSelectedPoi, clearSelectedPoi } from "../redux/slices/database/pois/selectedPoiSlice"; +import { parsePoint } from "@/utils/geometryUtils"; +import { handleEditPoi } from "@/utils/poiUtils"; +import { updateLocationInDatabaseService } from "@/services/database/updateLocationInDatabaseService"; +import { setSelectedPoi, clearSelectedPoi } from "@/redux/slices/database/pois/selectedPoiSlice"; import { cleanupMarkers } from "@/utils/common/cleanupMarkers"; export const setupPOIs = async ( diff --git a/websocketDump/GisLinesStatus.json b/websocketDump/GisLinesStatus.json index 3edba5682..6c45d8a94 100644 --- a/websocketDump/GisLinesStatus.json +++ b/websocketDump/GisLinesStatus.json @@ -3,7 +3,7 @@ "IdLD": 50922, "Modul": 8, "DpName": "KUE08_Messwertalarm", - "ModulName": "Test9", + "ModulName": "Test11", "ModulTyp": "Kü705-FO", "Message": "KÜG 08: Überspannung gehend", "Level": 3, diff --git a/websocketDump/GisStationsMeasurements.json b/websocketDump/GisStationsMeasurements.json index 3198bcf68..bd0743673 100644 --- a/websocketDump/GisStationsMeasurements.json +++ b/websocketDump/GisStationsMeasurements.json @@ -4,7 +4,7 @@ "IdL": 24101, "IdDP": 3, "Na": "FBT", - "Val": "13", + "Val": "15", "Unit": "°C", "Gr": "GMA", "Area_Name": "Rastede" diff --git a/websocketDump/GisStationsStaticDistrict.json b/websocketDump/GisStationsStaticDistrict.json index aea32cb23..2271267dd 100644 --- a/websocketDump/GisStationsStaticDistrict.json +++ b/websocketDump/GisStationsStaticDistrict.json @@ -1,6 +1,6 @@ [ { - "LD_Name": "CPL Ismail31", + "LD_Name": "CPL Ismail", "IdLD": 50922, "Device": "CPL V3.5 mit 24 Kü", "Link": "cpl.aspx?ver=35&kue=24&id=50922", diff --git a/websocketDump/GisStationsStatusDistrict.json b/websocketDump/GisStationsStatusDistrict.json index d6b4d4e4e..f68e4ccba 100644 --- a/websocketDump/GisStationsStatusDistrict.json +++ b/websocketDump/GisStationsStatusDistrict.json @@ -4,7 +4,7 @@ "Na": "system", "Le": 4, "Co": "#FF00FF", - "Me": "Eingang DE 01 kommend", + "Me": "Eingang DE 01 kommend test2", "Feld": 4, "Icon": 0 },