fix: POI Bearbeiten

This commit is contained in:
ISA
2025-06-10 08:52:17 +02:00
parent 2d897081c5
commit 004af608fc
10 changed files with 155 additions and 71 deletions

View File

@@ -44,6 +44,8 @@
Polling-Mechanismus → unbedingt prüfen, ob `clearInterval()` im useEffect-Cleanup enthalten Polling-Mechanismus → unbedingt prüfen, ob `clearInterval()` im useEffect-Cleanup enthalten
ist. 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 optimiert besser als setInterval, zuerst nur für TALAS.web WebServices erstellen, irgendwann soll
die Daten von DB auch mit WebSocket gelöst werden die Daten von DB auch mit WebSocket gelöst werden
- [ ] TODO: POI bearbeiten funktioniert es nicht

View File

@@ -1,2 +1,2 @@
// /config/appVersion // /config/appVersion
export const APP_VERSION = "1.1.251"; export const APP_VERSION = "1.1.252";

View File

@@ -2,7 +2,9 @@
# 🧠 Architekturübersicht NodeMap # 🧠 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 ## 🧩 Besonderheiten
- **Konfigurierbarer basePath:** - **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:** - **Rechteabhängige UI:**
Funktionen (z.B. POI bearbeiten) basieren auf Benutzerrechten (`IdRight`) vom Server. Funktionen (z.B. POI bearbeiten) basieren auf Benutzerrechten (`IdRight`) vom Server.
- **Zentrale Komponentensteuerung:** - **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 - Version ist in `appVersion.js` definiert → wird über `NEXT_PUBLIC_APP_VERSION` eingeblendet
- Build erfolgt via `npm run build`, Auslieferung über `.next/` - 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): Jetzt (dynamisch & Redux-basiert): MapComponent.js ruft folgenden Hook auf:
MapComponent.js ruft folgenden Hook auf:
js js Copy Edit const { markerStates, layerRefs } = useDynamicDeviceLayers(map, GisSystemStatic,
Copy mapLayersVisibility, priorityConfig, oms); useDynamicDeviceLayers.js verarbeitet die
Edit GisSystemStatic-Liste:
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. Jedes System (z.B. "TALAS", "ECI", "Cisco") bekommt einen eigenen Marker-Layer.
Die Marker werden erstellt durch: Die Marker werden erstellt durch:
js js Copy Edit createAndSetDevices(...) // Systemweise Marker erzeugen createAndSetDevices.js:
Copy
Edit
createAndSetDevices(...) // Systemweise Marker erzeugen
createAndSetDevices.js:
Filtert alle Stations aus staticDistrictData, deren System === IdSystem. 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: Der Hook speichert:
js js Copy Edit setMarkerStates((prev) => ({ ...prev, [Name]: newMarkers })); MapComponent.js hat dann:
Copy
Edit
setMarkerStates((prev) => ({ ...prev, [Name]: newMarkers }));
MapComponent.js hat dann:
Zugriff auf alle Marker dynamisch über markerStates (ein Objekt mit Schlüssel = Systemname) 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. 🕷️ Überlappende Marker werden mit checkOverlappingMarkers + PlusRoundIcon verarbeitet.
@@ -202,3 +197,90 @@ flowchart TD
A2 --> I1[Map aktualisiert Marker] A2 --> I1[Map aktualisiert Marker]
A2 --> I2[checkOverlappingMarkers mit OMS] 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

View File

@@ -1,10 +1,10 @@
// /utils/markerUtils.js // /utils/markerUtils.js
import L from "leaflet"; import L from "leaflet";
import { toast } from "react-toastify"; import { toast } from "react-toastify";
import circleIcon from "../components/CircleIcon"; import circleIcon from "@/components/gisPolylines/icons/CircleIcon";
import { store } from "../redux/store"; import { store } from "../redux/store";
import { updatePolylineCoordinatesThunk } from "../redux/thunks/database/polylines/updatePolylineCoordinatesThunk"; import { updatePolylineCoordinatesThunk } from "@/redux/thunks/database/polylines/updatePolylineCoordinatesThunk";
import { redrawPolyline } from "./mapUtils"; import { redrawPolyline } from "@/utils/mapUtils";
import { cleanupMarkers } from "@/utils/common/cleanupMarkers"; import { cleanupMarkers } from "@/utils/common/cleanupMarkers";
const savePolylineRedux = lineData => { const savePolylineRedux = lineData => {
@@ -57,37 +57,3 @@ export const removeMarker = (marker, lineData, currentZoom, currentCenter) => {
window.location.reload(); 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);
};

View File

@@ -1,8 +1,8 @@
// /utils/poiUtils.js // /utils/poiUtils.js
import L from "leaflet"; import L from "leaflet";
import { toast } from "react-toastify"; import { toast } from "react-toastify";
import circleIcon from "../components/gisPolylines/icons/CircleIcon.js"; import circleIcon from "@/components/gisPolylines/icons/CircleIcon.js";
import { redrawPolyline } from "./polylines/redrawPolyline.js"; import { redrawPolyline } from "@/utils/polylines/redrawPolyline.js";
import { store } from "../redux/store"; import { store } from "../redux/store";
import { updatePolylineCoordinatesThunk } from "../redux/thunks/database/polylines/updatePolylineCoordinatesThunk"; import { updatePolylineCoordinatesThunk } from "../redux/thunks/database/polylines/updatePolylineCoordinatesThunk";
@@ -67,3 +67,37 @@ export const updateMarkerPosition = (newCoords, lineData, marker) => {
if (!marker) return; if (!marker) return;
marker.setLatLng([newCoords.lat, newCoords.lng]); 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);
};

View File

@@ -1,8 +1,8 @@
// utils/setupPOIs.js // utils/setupPOIs.js
import { parsePoint } from "./geometryUtils"; import { parsePoint } from "@/utils/geometryUtils";
import { handleEditPoi } from "./poiUtils"; import { handleEditPoi } from "@/utils/poiUtils";
import { updateLocationInDatabaseService } from "../services/database/updateLocationInDatabaseService"; import { updateLocationInDatabaseService } from "@/services/database/updateLocationInDatabaseService";
import { setSelectedPoi, clearSelectedPoi } from "../redux/slices/database/pois/selectedPoiSlice"; import { setSelectedPoi, clearSelectedPoi } from "@/redux/slices/database/pois/selectedPoiSlice";
import { cleanupMarkers } from "@/utils/common/cleanupMarkers"; import { cleanupMarkers } from "@/utils/common/cleanupMarkers";
export const setupPOIs = async ( export const setupPOIs = async (

View File

@@ -3,7 +3,7 @@
"IdLD": 50922, "IdLD": 50922,
"Modul": 8, "Modul": 8,
"DpName": "KUE08_Messwertalarm", "DpName": "KUE08_Messwertalarm",
"ModulName": "Test9", "ModulName": "Test11",
"ModulTyp": "Kü705-FO", "ModulTyp": "Kü705-FO",
"Message": "KÜG 08: Überspannung gehend", "Message": "KÜG 08: Überspannung gehend",
"Level": 3, "Level": 3,

View File

@@ -4,7 +4,7 @@
"IdL": 24101, "IdL": 24101,
"IdDP": 3, "IdDP": 3,
"Na": "FBT", "Na": "FBT",
"Val": "13", "Val": "15",
"Unit": "°C", "Unit": "°C",
"Gr": "GMA", "Gr": "GMA",
"Area_Name": "Rastede" "Area_Name": "Rastede"

View File

@@ -1,6 +1,6 @@
[ [
{ {
"LD_Name": "CPL Ismail31", "LD_Name": "CPL Ismail",
"IdLD": 50922, "IdLD": 50922,
"Device": "CPL V3.5 mit 24 Kü", "Device": "CPL V3.5 mit 24 Kü",
"Link": "cpl.aspx?ver=35&kue=24&id=50922", "Link": "cpl.aspx?ver=35&kue=24&id=50922",

View File

@@ -4,7 +4,7 @@
"Na": "system", "Na": "system",
"Le": 4, "Le": 4,
"Co": "#FF00FF", "Co": "#FF00FF",
"Me": "Eingang DE 01 kommend", "Me": "Eingang DE 01 kommend test2",
"Feld": 4, "Feld": 4,
"Icon": 0 "Icon": 0
}, },