45 Commits

Author SHA1 Message Date
ISA
f390f93293 fix: Dynamische Link nach Host für Alarm Icon 2025-09-19 13:08:48 +02:00
ISA
28dcb284bf fix: Dynamiche Link je nach Host 2025-09-19 13:04:13 +02:00
ISA
4f0527e8a9 fix: Die Logik für die Alarmanzeige wurde angepasst: Jetzt wird das Alarm-Icon nur angezeigt, wenn ein aktiver Alarm existiert und für diesen auch ein passender Link im StaticDistrict vorhanden ist – unabhängig von der Reihenfolge. Damit wird der Bug behoben, dass das Icon manchmal nicht erscheint, obwohl ein Alarm mit Link existiert. 2025-09-19 12:29:33 +02:00
ISA
3d0ce4a2b4 docs: md 2 html for confluence 2025-09-19 11:48:59 +02:00
ISA
76280b365b test: Der Test ist jetzt erfolgreich durchgelaufen. Die Toleranz und das Logging funktionieren wie gewünscht – kleine Abweichungen bei mapCenter sind jetzt kein Problem mehr. 2025-09-18 06:31:55 +02:00
ISA
c112ec2da4 test: Layers visibility 2025-09-17 14:25:36 +02:00
ISA
7faee5fd79 style: "Zu Marker zoomen" remove hover Button , only littwin-blue 2025-09-17 14:00:27 +02:00
ISA
e52b0cc520 test: playwright test passed 2025-09-17 13:44:20 +02:00
ISA
4a42c428f0 style: Alarm UI Widget 2025-09-17 13:26:02 +02:00
ISA
1d3d04d49c style: Alarm Ui Widget 2025-09-17 12:59:37 +02:00
ISA
dd9980409c test: Test pass 2025-09-17 12:13:03 +02:00
ISA
ea6d71a4f5 feat: Alarm UI Widget 2025-09-17 09:16:04 +02:00
ISA
13ca1cece0 feat: Die Alarmanzeige ist jetzt als eigene Komponente (AlarmIndicator.js) im Verzeichnis uiWidgets erstellt und in MapComponent.js eingebunden.
Wenn ein Alarm mit AlarmLink vorhanden ist, wird das Alarm-Icon angezeigt und öffnet beim Klick den Link in einem neuen Tab.
2025-09-17 07:44:49 +02:00
ISA
f22bb4b232 chore: UI Widget Alarm Link in GisStationsStatusDistrict.json eingefügt 2025-09-17 07:33:21 +02:00
ISA
bfd091b1b1 test: slow Motion 2025-09-16 16:38:14 +02:00
ISA
81b6379895 test(e2e): Playwright tests passing 2025-09-16 15:50:53 +02:00
ISA
42ca88d27e chore: playwright ohne webserver 2025-09-16 14:27:53 +02:00
ISA
fdb70d892c chore: move playwright test and reports in playwright folder 2025-09-16 14:18:50 +02:00
ISA
73e9c63e36 chore: move report into playwright 2025-09-16 14:00:05 +02:00
ISA
e520207526 feat: Plus und Minus Icons 2025-09-16 13:47:11 +02:00
ISA
2e5acf9327 feat: Plus und Minus Icons 2025-09-16 13:32:22 +02:00
ISA
cdfdd3d6cf chore: gitignore playwright Artefakte ignorieren und nur Test Datei annehmen 2025-09-16 12:28:10 +02:00
ISA
5b86d5293b feat: Plus und Minus Zoom Icons 2025-09-16 12:13:49 +02:00
ISA
31c770f778 feat: Plus und Minus Zoom Icons 2025-09-16 12:12:31 +02:00
ISA
051dd4c306 chore: maxZoom = 20; 2025-09-16 11:56:33 +02:00
ISA
995f084e15 chore: maxZoom = 20; 2025-09-16 11:55:41 +02:00
ISA
eaacec71da chore: test 2025-09-16 11:47:04 +02:00
ISA
6bc2e16657 style: alle Icons Panels in gleiche Position bringen 2025-09-16 11:20:49 +02:00
ISA
1208024f76 chore: alle Panels zu den selben Position bringen 2025-09-16 10:57:23 +02:00
ISA
369f29a769 feat(ui): add AreaDropdown and exclusive toggle with layers panel
New AreaDropdown component for quick station selection (filters by allowed systems, ESC to close)
MapComponent: toggle AreaDropdown via MapMarkerIcon; auto-hide MapLayersControlPanel when dropdown is open and vice versa
fix(alarms): hasActiveAlarm now checks Statis[].Alarm for both array and object shapes
fix(panel): Kabelstrecken now auto-enables TALAS (system-1) when turned on; keeps behavior to disable polylines when TALAS is unchecked; persists visibility to localStorage and emits visibilityChanged
Minor: imports, state wiring, and render guards updated
Affected files:

MapComponent.js
MapLayersControlPanel.js
AreaDropdown.js (new)
2025-09-15 13:53:16 +02:00
ISA
d166b2468d feat: AreaDropdown separate from MapLayerControlPanel 2025-09-15 13:38:19 +02:00
ISA
59c8680c23 feat: AlarmIcon nur bei GisStationsStatusDistrict Attribute Alarm :1 2025-09-15 13:03:54 +02:00
ISA
1a046f8212 style: Icon as components and littwin-blue 2025-09-15 11:52:20 +02:00
ISA
e35216daf5 chore: change icons order 2025-09-15 10:47:25 +02:00
ISA
91ad47166f del: BasMapPanel entfernt aus rechliche Gründe,
Kurzantwort: Für kommerzielle Nutzung sind OSM‑Community‑Tile‑Server nicht geeignet. Nutze einen bezahlten Anbieter (z. B. Thunderforest, Tracestrack) oder hoste selbst. Attribution ist immer Pflicht.

Links und Hinweise je Layer/Provider:

OpenStreetMap Standard (osm-standard)

Lizenz/Daten: ODbL, Attribution Pflicht
Tile-Server-Policy (keine Produktion/hohe Last): https://operations.osmfoundation.org/policies/tiles/
Urheberrecht/Attribution: https://www.openstreetmap.org/copyright
HOT Humanitarian (osm-humanitarian)

Community-Server (OSM France); keine Produktion/hohe Last
Info/Policy OSM France Tiles: https://tile.openstreetmap.fr/
HOT: https://www.hotosm.org/
CyclOSM (cyclosm)

Community-Server (OSM France); keine Produktion/hohe Last
Projektseite: https://www.cyclosm.org/
Hinweise/Policy (OSM France): https://tile.openstreetmap.fr/
Wiki: https://wiki.openstreetmap.org/wiki/CyclOSM
Carto Light (carto-light / Positron)

Keylos nutzbar mit Attribution; Fair‑Use, für hohe Last über CARTO‑Pläne
Basemaps: https://carto.com/basemaps/
Attribution: https://carto.com/attributions
Pricing (Plattform): https://carto.com/pricing/ (bei großem Volumen Sales kontaktieren)
Thunderforest (Cycle/Transport u. a.)

Kommerziell mit API‑Key; Pläne von Free bis Pro
Pricing: https://www.thunderforest.com/pricing/
Terms/Attribution: https://www.thunderforest.com/terms/
Tracestrack Topo

API‑Key erforderlich; kostenlose und bezahlte Pläne
Übersicht/Pricing: https://www.tracestrack.com/en/maps/
Nutzungsbedingungen: https://www.tracestrack.com/en/terms/
2025-09-15 10:36:03 +02:00
ISA
3a9b436352 feat: Icons 2025-09-12 15:59:44 +02:00
ISA
7b881e80c2 link Ebenen 2025-09-12 15:18:55 +02:00
ISA
cc19a0a466 feat: hamburger menu und info Icons 2025-09-12 14:55:19 +02:00
ISA
f200d0bb20 chore(husky): remove deprecated v9 bootstrap lines from pre-commit 2025-09-12 13:58:44 +02:00
ISA
75a0ab000f chore(Websocket): Websocket dump refresh 2025-09-12 13:58:13 +02:00
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
282 changed files with 12591 additions and 355 deletions

View File

@@ -23,4 +23,4 @@ NEXT_PUBLIC_USE_MOCKS=true
# 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.350
NEXT_PUBLIC_APP_VERSION=1.1.396

View File

@@ -24,4 +24,4 @@ NEXT_PUBLIC_USE_MOCKS=false
# basePath wird jetzt in public/config.json gepflegt
# App-Versionsnummer
NEXT_PUBLIC_APP_VERSION=1.1.350
NEXT_PUBLIC_APP_VERSION=1.1.396

25
.gitignore vendored
View File

@@ -35,3 +35,28 @@ docs.zip
/mockData/
/__mocks__/
/__tests__/
# --- Playwright artifacts & test selection ---
# Ignore Playwright output folders nested under playwright/
/playwright/test-results/
/playwright/playwright-report/
/playwright/.last-run.json
# If you ever enable these paths, keep them under playwright/ and ignore them
/playwright/traces/
/playwright/screenshots/
/playwright/videos/
# Ignore JUnit report artifacts under playwright/ (currently unused)
/playwright/reports/junit/
# Track only spec files under playwright/tests; ignore other files in that folder
/playwright/tests/**
!/playwright/tests/**/*.spec.js
!/playwright/tests/**/*.spec.ts
# Ignore Playwright cache if present
/playwright/.cache/
# playwright reports
/playwright/reports/
# Jira /Confluence Upload Script Secrets und den script selbst
/scripts/confluence-upload/secrets.ps1
/scripts/confluence-upload/upload-docs.ps1

View File

@@ -1,6 +1,3 @@
#!/bin/sh
. "$(dirname "$0")/_/husky.sh"
echo "🔄 Version wird automatisch erhöht (bumpVersion.js)..."
# Version automatisch erhöhen

195
README.confluence Normal file
View File

@@ -0,0 +1,195 @@
h1. {anchor:nodemap-kartenvisualisierung-für-talas.web-next.js-leaflet-redux}🌍 NodeMap Kartenvisualisierung für TALAS.web \(Next.js, Leaflet, Redux)
NodeMap ist eine modulare Kartenanwendung zur Visualisierung und Bearbeitung von GIS-Daten, POIs und Gerätestatus in einer interaktiven Leaflet-Karte.
{quote}
📘 Für Entwickler:
Die technische Dokumentation \(Architektur, Redux, Komponenten, etc.) befindet sich in:
[{{/docs/README.md}}|docs/README.md]
{quote}
h2. {anchor:live-vorschau-der-karte}🌍 Live-Vorschau der Karte
!docs/screenshots/overview1.png|alt=Startansicht der NodeMap Karte!
----
{quote}
🖥 Entwicklung & Test unter Windows 11 mit Node.js v18.17.1 und IIS
📦 MySQL 8.0 läuft lokal in einem Docker-Container \(nur für Entwicklung)
🗄 Produktionsumgebung: TALAS.web und MySQL Server unter Windows Server
{quote}
----
h2. {anchor:technologie-stack}Technologie-Stack
|| Technologie || Zweck ||
| Next.js | React-Framework \(Frontend/SSR) |
| Leaflet | Kartendarstellung |
| Redux Toolkit | Zustandverwaltung |
| Tailwind CSS | Styling |
| MySQL | Datenbank |
| Node.js / IIS | Server und Auslieferung |
h2. {anchor:zielumgebung}🧭 Zielumgebung
* Windows-Produktionsserver \(offline, kein Internet)
* Kommunikation nur im lokalen Netzwerk
* Nutzerzugriff per VPN + Remote Desktop \(RDP)
* Integration per iFrame in TALAS.web
----
h2. {anchor:wie-funktioniert-das-system}🔄 Wie funktioniert das System?
Die Anwendung wird von TALAS.web im iFrame geladen. Die URL enthält Parameter für Map\- und User-ID.
NodeMap lädt anschließend Daten über WebServices und MySQL.
➡ Details zur Architektur: [docs/architecture.md]
----
h2. {anchor:kartenquellen-konfiguration-publicconfig.json}⚙️ 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:*
{code: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"
}
{code}
* 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).
----
h2. {anchor:erstinstallation-auf-server}🧰 Erstinstallation auf Server
h3. {anchor:voraussetzungen}Voraussetzungen
* Windows Server mit IIS
* Sicherstellen, dass alle TALAS.web API-Endpunkte(WebService) erreichbar sind
* Node.js & npm installiert \(z.B. v1820)
* MySQL \(lokal oder erreichbar)
* Port 3000 freigegeben \(Firewall)
* IIS-Datei {{mapTypC.aspx}} vorhanden in C:  \(Server-IP mit Port 3000)
* Browser: Chrome ab Version 125.0.6420.142 empfohlen
* Karten Material vorhanden in: {{C:\inetpub\wwwroot\talas5\TileMap\mapTiles}} !docs/screenshots/mapTiles.png|alt=mapTiles! Falls nicht vorhanden hier downloaden: http://10.10.0.28/produkte/TALAS.map/mapTiles.zip
----
h2. {anchor:integration-in-talas.web}🔗 Integration in TALAS.web
!docs/screenshots/iframe-in-talas2.png|alt=iFrame-Integration!
* Die App wird in einem *iFrame* geladen
* Startet über {{?m=X&u=Y}} für Map-/User-ID
* Rechte und Inhalte werden automatisch geladen
{noformat}
z.B.
`http://10.10.0.13/talas5/MessagesMap/mapTypC.aspx?m=12&u=484`{noformat}
----
h2. {anchor:schritt-für-schritt-nodemap-auf-dem-server-installieren}🪛 Schritt-für-Schritt: NodeMap auf dem Server installieren
----
h2. {anchor:schnelles-deployment-über-zip-paket}📦 Schnelles Deployment über ZIP-Paket
Ein fertiges Deployment-Bundle für jede Version \(z.B. {{NodeMap V1.1.260.zip}}) ist auf dem internen SharePoint verfügbar:
📁 [Masterkarte V2 setup files|https://littwinsystemtechnik.sharepoint.com/sites/LittwinSystemtechnik/Freigegebene%20Dokumente/Forms/AllItems.aspx?id=%2Fsites%2FLittwinSystemtechnik%2FFreigegebene%20Dokumente%2FProjekte%2FMasterkarte%20V2%20setup%20files&csf=1&web=1&e=Sm1wwt&CID=9291bb06%2Dc869%2D4e30%2D8efa%2D8cda40df3cd6&FolderCTID=0x0120009C4F8227D6A11D4E89F1CCB9E517F488]
h4. {anchor:ablauf}📂 Ablauf:
# 🛑 *Dienst beenden*
#* Vor dem Update muss der bestehende Windows-Dienst {{NodeMapService}} beendet werden,
um Dateikonflikte beim Löschen zu vermeiden. !docs/screenshots/Dienst-beenden.png|alt=Dienst beenden!
# 🔍 *Prüfen, ob passende {{node_modules-v1.1.xxx.zip}} Datei vorhanden ist*
#* Wenn *nicht vorhanden* → {{C:\inetpub\wwwroot\talas5\nodeMap}} komplett löschen
#* Wenn *vorhanden* → nur {{node_modules-v1.1.xxx.zip}} und {{node_modules}} Verzeichnis behalten, Rest löschen
💡 *Tipp:* {{node_modules-v1.1.xxx.zip}} nach Entpacken und in node_modules umbenennen\!
# 📦 *ZIP entpacken*
#* {{NodeMap V1.1.260.zip}} entpacken
Nach dem alles entpakt ist, dann sieht das so aus !docs/screenshots/nodeMap-inhalt.png|alt=NodeMap Inhalt!
# 🚀 *Dienst starten*
#* Windows-Dienst {{NodeMapService}} wieder starten
----
h2. {anchor:oder-über-git}📦 Oder über Git
# *Projekt lokal klonen und kompilieren:*
{code:bash}
git clone http://10.10.0.12:3000/ISA/nodeMap
cd nodeMap # zu den Verzeichnis wechseln
npm install # Abhängigkeiten installieren (lädt alle Pakete aus package.json)
npm run build # Erstellt ein optimiertes Produktions-Build im Ordner .next/
{code}
# *ZIP-Paket vorbereiten \(lokal):*
* Verzeichnis {{.next/}}
* Verzeichnisse {{public/}}, {{node_modules/}} falls auf dem Server nicht vorhanden sind oder etwas hinzugefügt wurde \(Bilder oder Bibliothek)
* Dateien {{.env.production}}, {{package.json}} falls auf dem Server nicht vorhanden sind oder etwas hinzugefügt wurde \(Umgebungsvariablen oder Bibliothek)
* {{nssm.exe}}, {{StartNodeApp.bat}}, {{Start-Dev.ps1}} um Windows Dienst zu erstellen falls noch nicht vorhanden ist Download: [nssm|https://littwinsystemtechnik.sharepoint.com/:f:/r/sites/LittwinSystemtechnik/Freigegebene%20Dokumente/Projekte/Masterkarte%20V2%20setup%20files?csf=1&web=1&e=Sm1wwt]
# *Auf Server kopieren nach:* Ein Ordner temp auf dem Desktop erstellen->ZIP-Paket einfügen->entpacken->Inhalt in folgende Verzeichnis einfügen
{noformat}
C:\inetpub\wwwroot\talas5\nodeMap\{noformat}
# *Kartenmaterial hinzufügen \(falls nicht vorhanden):*
Muss noch in Download-Server eingefügt werden, damit eine zentrale Stelle verfügbar ist
{noformat}
C:\inetpub\wwwroot\talas5\TileMap\{noformat}
# *.env.production konfigurieren*
Die Datei {{.env.production}} enthält alle benötigten Verbindungs\- und Betriebsvariablen wie z.B. Datenbank-Zugang, Pfade und Mock-Option.
➡ Vollständige Anleitung & Beispieldatei: [.env.production|docs/guide/env.md]
# *Dienst registrieren falls nicht vorhanden*
* Mit {{nssm.exe}} Windows-Dienst „nodeMapService“ erstellen
* Ziel: {{StartNodeApp.bat}}
* Anleitung: [nssm|https://littwinsystemtechnik.sharepoint.com/:f:/r/sites/LittwinSystemtechnik/Freigegebene%20Dokumente/Projekte/Masterkarte%20V2%20setup%20files?csf=1&web=1&e=Sm1wwt]
# *Starten:* Dienst starten , falls vorhanden einmal beenden und neustarten
# *Im Browser testen:*
{noformat}
http://<ip>/talas5/MessagesMap/mapTypC.aspx?m=IdMap&u=IdUser
z.B.
http://<ip>/talas5/MessagesMap/mapTypC.aspx?m=12&u=484{noformat}
----
h2. {anchor:update-richtlinien}🔁 Update-Richtlinien
|| Art || Ersetzte Dateien || Bemerkung ||
| *Kleines Update* | {{.next/}} | {{node_modules}} nicht nötig |
| *Großes Update* | alle Dateien \(wie Neuinstallation) | Dienst ggf. neu registrieren |
h3. {anchor:empfohlener-ablauf-für-kleines-update}Empfohlener Ablauf für kleines Update:
# {{.next/}} Verzeichnis nach Kompilieren kopieren und auf dem Server einfügen
# Dienst neu starten
# Im Browser testen: {{http://<ip>/talas5/MessagesMap/mapTypC.aspx?m=IdMap&u=IdUser}}
----
h2. {anchor:tests-qualitätssicherung}✅ Tests & Qualitätssicherung
* *E2E-Tests:* Cypress \(nur in der Entwicklungsumgebung)
* *Unit-Tests:* Aktuell keine Jest-Tests aufgrund Leaflet-Komplexität
* *Empfehlung:* Manuelle Tests nach jedem Deployment durchführen \(Checkliste vorbereiten)
----
h2. {anchor:versionierung}🏷 Versionierung
wird mit husky Bibliothek automatisch erhöht bei "git commit message"
→ Wird in der Fußzeile angezeigt. Die Version wird automatisch erhöht über ein Script \({{scripts/bumpVersion.js}}), das per Husky vor jedem Commit ausgeführt wird.
Die Version steht sowohl in {{package.json}} als auch in {{config/appVersion.js}}.
----
h2. {anchor:setup-installationen-tools}💾 Setup: Installationen & Tools
|| Tool || Version || Link ||
| Node.js | 20.12.1 | [nodejs|https://littwinsystemtechnik.sharepoint.com/:f:/r/sites/LittwinSystemtechnik/Freigegebene%20Dokumente/Projekte/Masterkarte%20V2%20setup%20files?csf=1&web=1&e=Sm1wwt] |
| Chrome | optional | [Chrome|https://littwinsystemtechnik.sharepoint.com/:f:/r/sites/LittwinSystemtechnik/Freigegebene%20Dokumente/Projekte/Masterkarte%20V2%20setup%20files?csf=1&web=1&e=Sm1wwt] |
| NSSM.exe | 2.24 | [nssm|https://littwinsystemtechnik.sharepoint.com/:f:/r/sites/LittwinSystemtechnik/Freigegebene%20Dokumente/Projekte/Masterkarte%20V2%20setup%20files?csf=1&web=1&e=Sm1wwt] |
{quote}
Hinweis: Die Datei {{MapTypC.aspx}} in TALAS lädt NodeMap als iFrame über Port 3000.
Wenn die Seite nicht angezeigt wird, bitte sicherstellen:
* Port 3000 ist in der Firewall freigegeben
* Die IP im Scriptteil von {{MapTypC.aspx}} ist aktuell \(z.B. {{10.10.0.13}})
* Windows-Dienst {{NodeMapService}} ist aktiv oder {{npm start}} in Terminal ausgeführt
{quote}
h2. {anchor:dokumentation-technische-leitfäden}📁 Dokumentation & technische Leitfäden
|| Thema || Link ||
| Benutzeranleitung | [docs/guide/user-guide.md] |
| Architekturübersicht | [architecture.md|docs/architecture.md] |
| Projektstruktur | [project-structure.md|docs/guide/project-structure.md] |
| Webservices \(TALAS) | [webservices.md|docs/guide/webservices.md] |
| Umgebungsvariablen | [env.md|docs/guide/env.md] |
| Mockdaten-Modus | [mock-data.md|docs/guide/mock-data.md] |
| Zustandverwaltung \(Redux) | [redux-zustand.md|docs/guide/redux-zustand.md] |
| Abhängigkeiten | [dependencies.md|docs/guide/dependencies.md] |
| Lokale Entwicklung | [setup-dev.md|docs/guide/setup-dev.md] |
| FAQ & Fehlerbehandlung | [faq.md|docs/guide/faq.md] |
| Glossar | [faq.md|docs/guide/glossar.md] |

778
README.html Normal file

File diff suppressed because one or more lines are too long

BIN
README.pdf Normal file

Binary file not shown.

View File

@@ -74,3 +74,32 @@ die Daten von DB auch mit WebSocket gelöst werden
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
https://www.openstreetmap.org/#map=13/51.80097/9.33495&layers=P

View File

@@ -0,0 +1,18 @@
const AlarmIcon = ({ className = "h-8 w-8" }) => (
<svg
xmlns="http://www.w3.org/2000/svg"
fill="red"
viewBox="0 0 24 24"
strokeWidth="1.5"
stroke="rgb(0, 174, 239)"
className={className}
>
<path
strokeLinecap="round"
strokeLinejoin="round"
d="M6 6.9L3.87 4.78l1.41-1.41L7.4 5.5zM13 1v3h-2V1zm7.13 3.78L18 6.9l-1.4-1.4l2.12-2.13zM4.5 10.5v2h-3v-2zm15 0h3v2h-3zM6 20h12a2 2 0 0 1 2 2H4a2 2 0 0 1 2-2m6-15a6 6 0 0 1 6 6v8H6v-8a6 6 0 0 1 6-6"
/>
</svg>
);
export default AlarmIcon;

View File

@@ -0,0 +1,18 @@
const EditIcon = ({ className = "h-8 w-8" }) => (
<svg
xmlns="http://www.w3.org/2000/svg"
fill="none"
viewBox="0 0 24 24"
strokeWidth="1.5"
stroke="rgb(0, 174, 239)"
className={className}
>
<path
strokeLinecap="round"
strokeLinejoin="round"
d="M16.862 4.487l1.687-1.688a1.875 1.875 0 112.652 2.652L10.582 16.07a4.5 4.5 0 01-1.897 1.13L6 18l.8-2.685a4.5 4.5 0 011.13-1.897l8.932-8.931zm0 0L19.5 7.125M18 14v4.75A2.25 2.25 0 0115.75 21H5.25A2.25 2.25 0 013 18.75V8.25A2.25 2.25 0 015.25 6H10"
/>
</svg>
);
export default EditIcon;

View File

@@ -0,0 +1,18 @@
const EditOffIcon = ({ className = "h-8 w-8" }) => (
<svg
xmlns="http://www.w3.org/2000/svg"
fill="none"
viewBox="0 0 24 24"
strokeWidth="1.5"
stroke="rgb(0, 174, 239)"
className={className}
>
<path
strokeLinecap="round"
strokeLinejoin="round"
d="M18.364 18.364A9 9 0 005.636 5.636m12.728 12.728L5.636 5.636m12.728 12.728L18 21l-3-3m-12.728-.364A9 9 0 015.636 5.636m0 0L3 3l3 3m9.364 9.364L18 21M5.636 5.636L3 3"
/>
</svg>
);
export default EditOffIcon;

View File

@@ -0,0 +1,18 @@
const ExpandIcon = ({ className = "h-8 w-8" }) => (
<svg
xmlns="http://www.w3.org/2000/svg"
fill="none"
viewBox="0 0 24 24"
strokeWidth="1.5"
stroke="rgb(0, 174, 239)"
className={className}
>
<path
strokeLinecap="round"
strokeLinejoin="round"
d="M3.75 3.75v4.5m0-4.5h4.5m-4.5 0L9 9M3.75 20.25v-4.5m0 4.5h4.5m-4.5 0L9 15M20.25 3.75h-4.5m4.5 0v4.5m0-4.5L15 9m5.25 11.25h-4.5m4.5 0v-4.5m0 4.5L15 15"
/>
</svg>
);
export default ExpandIcon;

View File

@@ -0,0 +1,18 @@
const InfoIcon = ({ className = "h-8 w-8" }) => (
<svg
xmlns="http://www.w3.org/2000/svg"
fill="none"
viewBox="0 0 24 24"
strokeWidth="1.5"
stroke="rgb(0, 174, 239)"
className={className}
>
<path
strokeLinecap="round"
strokeLinejoin="round"
d="M11.25 11.25l.041-.02a.75.75 0 011.063.852l-.708 2.836a.75.75 0 001.063.853l.041-.021M21 12a9 9 0 11-18 0 9 9 0 0118 0zm-9-3.75h.008v.008H12V8.25z"
/>
</svg>
);
export default InfoIcon;

View File

@@ -0,0 +1,19 @@
const MapMarkerIcon = ({ className = "h-8 w-8" }) => (
<svg
xmlns="http://www.w3.org/2000/svg"
fill="rgb(0, 174, 239)"
viewBox="0 0 24 24"
strokeWidth="1.5"
stroke="rgb(0, 174, 239)"
className={className}
>
<path strokeLinecap="round" strokeLinejoin="round" d="M15 10.5a3 3 0 11-6 0 3 3 0 016 0z" />
<path
strokeLinecap="round"
strokeLinejoin="round"
d="M19.5 10.5c0 7.142-7.5 11.25-7.5 11.25s-7.5-4.108-7.5-11.25a7.5 7.5 0 1115 0z"
/>
</svg>
);
export default MapMarkerIcon;

View File

@@ -0,0 +1,18 @@
const MenuIcon = ({ className = "h-8 w-8" }) => (
<svg
xmlns="http://www.w3.org/2000/svg"
fill="none"
viewBox="0 0 24 24"
strokeWidth="1.5"
stroke="rgb(0, 174, 239)"
className={className}
>
<path
strokeLinecap="round"
strokeLinejoin="round"
d="M3.75 6.75h16.5M3.75 12h16.5m-16.5 5.25h16.5"
/>
</svg>
);
export default MenuIcon;

View File

@@ -0,0 +1,14 @@
const MinusIcon = ({ className = "h-8 w-8" }) => (
<svg
xmlns="http://www.w3.org/2000/svg"
viewBox="0 0 24 24"
fill="none"
strokeWidth="1.5"
stroke="currentColor"
className={className}
>
<path strokeLinecap="round" strokeLinejoin="round" d="M5 12h14" />
</svg>
);
export default MinusIcon;

View File

@@ -0,0 +1,14 @@
const PlusIcon = ({ className = "h-8 w-8" }) => (
<svg
xmlns="http://www.w3.org/2000/svg"
viewBox="0 0 24 24"
fill="none"
strokeWidth="1.5"
stroke="currentColor"
className={className}
>
<path strokeLinecap="round" strokeLinejoin="round" d="M12 5v14M5 12h14" />
</svg>
);
export default PlusIcon;

View File

@@ -0,0 +1,18 @@
const SearchIcon = ({ className = "h-8 w-8" }) => (
<svg
xmlns="http://www.w3.org/2000/svg"
fill="none"
viewBox="0 0 24 24"
strokeWidth="1.5"
stroke="rgb(0, 174, 239)"
className={className}
>
<path
strokeLinecap="round"
strokeLinejoin="round"
d="M21 21l-5.197-5.197m0 0A7.5 7.5 0 105.196 5.196a7.5 7.5 0 0010.607 10.607z"
/>
</svg>
);
export default SearchIcon;

View File

@@ -6,7 +6,17 @@ import "leaflet-contextmenu/dist/leaflet.contextmenu.css";
import "leaflet-contextmenu";
import "leaflet.smooth_marker_bouncing";
import "react-toastify/dist/ReactToastify.css";
import { InformationCircleIcon } from "@heroicons/react/20/solid";
import { Icon } from "@iconify/react";
import EditIcon from "@/components/icons/material-symbols/EditIcon";
import EditOffIcon from "@/components/icons/material-symbols/EditOffIcon";
import SearchIcon from "@/components/icons/material-symbols/SearchIcon";
import MenuIcon from "@/components/icons/material-symbols/MenuIcon";
import InfoIcon from "@/components/icons/material-symbols/InfoIcon";
import AlarmIcon from "@/components/icons/material-symbols/AlarmIcon";
import MapMarkerIcon from "@/components/icons/material-symbols/MapMarkerIcon";
import ExpandIcon from "@/components/icons/material-symbols/ExpandIcon";
import PlusIcon from "@/components/icons/material-symbols/PlusIcon";
import MinusIcon from "@/components/icons/material-symbols/MinusIcon";
import PoiUpdateModal from "@/components/pois/poiUpdateModal/PoiUpdateModal.js";
import { ToastContainer, toast } from "react-toastify";
import plusRoundIcon from "../icons/devices/overlapping/PlusRoundIcon.js";
@@ -24,8 +34,10 @@ import { useMapComponentState } from "@/components/hooks/useMapComponentState.js
import CoordinatePopup from "@/components/contextmenu/CoordinatePopup.js";
//----------Ui Widgets----------------
import MapLayersControlPanel from "@/components/uiWidgets/mapLayersControlPanel/MapLayersControlPanel.js";
import AlarmIndicator from "@/components/uiWidgets/AlarmIndicator";
import CoordinateInput from "@/components/uiWidgets/CoordinateInput.js";
import VersionInfoModal from "@/components/uiWidgets/VersionInfoModal.js";
import AreaDropdown from "@/components/uiWidgets/AreaDropdown";
//----------Daten aus API--------------------
import { fetchPoiDataService } from "@/services/database/pois/fetchPoiDataByIdService.js";
import AddPOIModal from "@/components/pois/AddPOIModal.js";
@@ -39,6 +51,8 @@ import { setSelectedPoi } from "@/redux/slices/database/pois/selectedPoiSlice.js
import { setDisabled } from "@/redux/slices/database/polylines/polylineEventsDisabledSlice.js";
import { setMapId, setUserId } from "@/redux/slices/urlParameterSlice";
import { selectMapLayersState, setLayerVisibility } from "@/redux/slices/mapLayersSlice";
import { setSelectedArea } from "@/redux/slices/selectedAreaSlice";
import { incrementZoomTrigger } from "@/redux/slices/zoomTriggerSlice";
import { setCurrentPoi } from "@/redux/slices/database/pois/currentPoiSlice.js";
import { selectGisLines } from "@/redux/slices/database/polylines/gisLinesSlice";
import { selectGisLinesStatus } from "@/redux/slices/webservice/gisLinesStatusSlice";
@@ -119,7 +133,7 @@ const MapComponent = ({ locations, onLocationUpdate, lineCoordinates }) => {
const poiLayerVisible = useSelector(state => state.poiLayerVisible.visible);
const zoomTrigger = useSelector(state => state.zoomTrigger.trigger);
const poiReadTrigger = useSelector(state => state.poiReadFromDbTrigger.trigger);
const GisStationsStaticDistrict = useSelector(selectGisStationsStaticDistrict);
// entfernt, da weiter unten dynamisch und mit Fallback deklariert
const gisSystemStaticStatus = useSelector(state => state.gisSystemStatic.status);
const polylineEventsDisabled = useSelector(state => state.polylineEventsDisabled.disabled);
const mapLayersVisibility = useSelector(selectMapLayersState) || {};
@@ -129,6 +143,41 @@ const MapComponent = ({ locations, onLocationUpdate, lineCoordinates }) => {
const { data: gisLinesStatusData, status: statusGisLinesStatus } = useSelector(
selectGisLinesStatusFromWebservice
);
// Alarm Status und Link dynamisch aus GisStationsStaticDistrict
const gisStationsStatusDistrict = useSelector(state => state.gisStationsStatusDistrict.data);
const GisStationsStaticDistrict = useSelector(selectGisStationsStaticDistrict) || {};
const pointsArr = GisStationsStaticDistrict.Points || [];
let hasActiveAlarm = false;
let alarmLink = "";
let alarmText = "";
let alarmIdLD = null;
// Hilfsfunktion: alle aktiven Alarme sammeln
let alarmList = [];
if (Array.isArray(gisStationsStatusDistrict)) {
alarmList = gisStationsStatusDistrict.filter(item => item?.Alarm === 1);
} else if (gisStationsStatusDistrict?.Statis) {
alarmList = gisStationsStatusDistrict.Statis.filter(item => item?.Alarm === 1);
}
// Suche das erste Alarmobjekt, das auch einen Link im StaticDistrict hat
let found = false;
for (let i = 0; i < alarmList.length; i++) {
const alarmObj = alarmList[i];
const staticObj = pointsArr.find(p => p.IdLD === alarmObj.IdLD);
if (staticObj && staticObj.Link) {
hasActiveAlarm = true;
alarmIdLD = alarmObj.IdLD;
alarmText = alarmObj.Me || "Alarm aktiv";
const isAbsolute =
staticObj.Link.startsWith("http://") || staticObj.Link.startsWith("https://");
alarmLink = isAbsolute
? staticObj.Link
: `http://${window.location.hostname}/talas5/devices/${staticObj.Link}`;
// : `http://10.10.0.13/talas5/devices/${staticObj.Link}`;
found = true;
break;
}
}
const poiIconsData = useSelector(selectPoiIconsData);
const poiIconsStatus = useSelector(selectPoiIconsStatus);
const poiTypData = useSelector(selectPoiTypData);
@@ -147,10 +196,69 @@ const MapComponent = ({ locations, onLocationUpdate, lineCoordinates }) => {
const [showVersionInfoModal, setShowVersionInfoModal] = useState(false);
const [poiTypMap, setPoiTypMap] = useState(new Map());
const [showPopup, setShowPopup] = useState(false);
const [showAreaDropdown, setShowAreaDropdown] = useState(() => {
try {
const v = localStorage.getItem("showAreaDropdown");
return v === null ? false : v === "true";
} catch (_) {
return false;
}
});
const poiLayerRef = useRef(null); // Referenz auf die Layer-Gruppe für Datenbank-Marker
const mapRef = useRef(null); // Referenz auf das DIV-Element der Karte
const [map, setMap] = useState(null); // Zustand der Karteninstanz
const [oms, setOms] = useState(null); // State für OMS-Instanz
// Sichtbarkeit der App-Info-Karte (unten links)
const [showAppInfoCard, setShowAppInfoCard] = useState(() => {
try {
const v = localStorage.getItem("showAppInfoCard");
return v === null ? true : v === "true";
} catch (_) {
return true;
}
});
// Sichtbarkeit des Layer-Kontrollpanels (oben rechts)
const [showLayersPanel, setShowLayersPanel] = useState(() => {
try {
const v = localStorage.getItem("showLayersPanel");
return v === null ? true : v === "true";
} catch (_) {
return true;
}
});
// Base-Map Panel wurde entfernt
// Sichtbarkeit der Koordinaten-Suche (Lupe)
const [showCoordinateInput, setShowCoordinateInput] = useState(() => {
try {
const v = localStorage.getItem("showCoordinateInput");
return v === null ? false : v === "true";
} catch (_) {
return false;
}
});
// Zentrale Steuerung: Nur ein Overlay gleichzeitig
// Mögliche Werte: null | 'area' | 'layers' | 'coord' | 'info'
const [overlay, setOverlay] = useState(null);
// Initiale Bestimmung des aktiven Overlays basierend auf bestehenden Flags
useEffect(() => {
if (showAreaDropdown) setOverlay("area");
else if (showLayersPanel) setOverlay("layers");
else if (showCoordinateInput) setOverlay("coord");
else if (showAppInfoCard) setOverlay("info");
else setOverlay(null);
// nur beim Mount ausführen
// eslint-disable-next-line react-hooks/exhaustive-deps
}, []);
// Flags mit Overlay-State synchronisieren (persistiert weiterhin in bestehenden Effects)
useEffect(() => {
setShowAreaDropdown(overlay === "area");
setShowLayersPanel(overlay === "layers");
setShowCoordinateInput(overlay === "coord");
setShowAppInfoCard(overlay === "info");
}, [overlay]);
// Flag, ob Nutzer die Polyline-Checkbox manuell betätigt hat
// Nutzer-Flag global auf window, damit auch Redux darauf zugreifen kann
@@ -188,6 +296,14 @@ const MapComponent = ({ locations, onLocationUpdate, lineCoordinates }) => {
const [popupCoordinates, setPopupCoordinates] = useState(null);
const [popupVisible, setPopupVisible] = useState(false);
const [poiData, setPoiData] = useState([]);
// Edit mode state mirrors MapLayersControlPanel's behavior
const [editMode, setEditMode] = useState(() => {
try {
return localStorage.getItem("editMode") === "true";
} catch (_) {
return false;
}
});
const openVersionInfoModal = () => {
setShowVersionInfoModal(true);
@@ -209,6 +325,32 @@ const MapComponent = ({ locations, onLocationUpdate, lineCoordinates }) => {
}
});
// Persistiere Sichtbarkeit der App-Info-Karte
useEffect(() => {
try {
localStorage.setItem("showAppInfoCard", String(showAppInfoCard));
} catch (_) {}
}, [showAppInfoCard]);
// Persistiere Sichtbarkeit des Area-Dropdowns (Marker-Overlay)
useEffect(() => {
try {
localStorage.setItem("showAreaDropdown", String(showAreaDropdown));
} catch (_) {}
}, [showAreaDropdown]);
// Persistiere Sichtbarkeit des Layer-Panels
useEffect(() => {
try {
localStorage.setItem("showLayersPanel", String(showLayersPanel));
} catch (_) {}
}, [showLayersPanel]);
// Persist-Logik für Base-Map Panel entfernt
// Persistiere Sichtbarkeit der Koordinaten-Suche
useEffect(() => {
try {
localStorage.setItem("showCoordinateInput", String(showCoordinateInput));
} catch (_) {}
}, [showCoordinateInput]);
//--------------------------------------------
const handleCoordinatesSubmit = coords => {
@@ -981,6 +1123,29 @@ const MapComponent = ({ locations, onLocationUpdate, lineCoordinates }) => {
}, [GisSystemStatic, mapLayersVisibility, dispatch]);
//---------------------------------------------
//--------------------------------------------
// Expand handler (same behavior as MapLayersControlPanel expand icon)
const handleExpandClick = () => {
dispatch(setSelectedArea("Station wählen"));
dispatch(incrementZoomTrigger());
};
// Toggle edit mode (same logic as EditModeToggle component)
const hasEditRight = Array.isArray(userRights)
? userRights.includes?.(56) || userRights.some?.(r => r?.IdRight === 56)
: false;
const toggleEditMode = () => {
if (!hasEditRight) return;
const next = !editMode;
setEditMode(next);
try {
localStorage.setItem("editMode", String(next));
} catch (_) {}
if (typeof window !== "undefined") {
window.location.reload();
}
};
//--------------------------------------------
return (
<>
@@ -1035,18 +1200,122 @@ const MapComponent = ({ locations, onLocationUpdate, lineCoordinates }) => {
)}
</div>
{GisStationsStaticDistrict && GisStationsStaticDistrict.Points?.length > 0 && (
{GisStationsStaticDistrict &&
GisStationsStaticDistrict.Points?.length > 0 &&
showLayersPanel &&
!showAreaDropdown && (
<MapLayersControlPanel
className="z-50"
handlePolylineCheckboxChange={handlePolylineCheckboxChange}
/>
)}
<CoordinateInput onCoordinatesSubmit={handleCoordinatesSubmit} />
{showCoordinateInput && <CoordinateInput onCoordinatesSubmit={handleCoordinatesSubmit} />}
<div id="map" ref={mapRef} className="z-0" style={{ height: "100vh", width: "100vw" }}></div>
{/* Top-right controls: layers, info, expand, edit, and base map stack */}
<div className="absolute top-3 right-3 z-50 pointer-events-auto flex items-center gap-2">
{/* Alarm-Icon - nur anzeigen wenn Alarm aktiv und Link vorhanden */}
<AlarmIndicator hasAlarm={hasActiveAlarm} alarmLink={alarmLink} alarmText={alarmText} />
{/* Marker-Icon (line-md) */}
<button
onClick={() => setOverlay(prev => (prev === "area" ? null : "area"))}
aria-label="Marker"
className="rounded-full bg-white/90 hover:bg-white shadow p-1"
title="Marker"
>
<MapMarkerIcon className="h-8 w-8" />
</button>
{/*Lupe: Koordinatensuche ein-/ausblenden */}
<button
onClick={() => setOverlay(prev => (prev === "coord" ? null : "coord"))}
aria-label={
showCoordinateInput ? "Koordinatensuche ausblenden" : "Koordinatensuche einblenden"
}
className="rounded-full bg-white/90 hover:bg-white shadow p-1"
title={
showCoordinateInput ? "Koordinatensuche ausblenden" : "Koordinatensuche einblenden"
}
>
<SearchIcon className="h-8 w-8" />
</button>
<button
onClick={toggleEditMode}
aria-label={editMode ? "Bearbeitungsmodus deaktivieren" : "Bearbeitungsmodus aktivieren"}
className={`rounded-full shadow p-1 ${
hasEditRight
? "bg-white/90 hover:bg-white"
: "bg-white/60 cursor-not-allowed opacity-50"
}`}
title={
hasEditRight
? editMode
? "Bearbeitungsmodus deaktivieren"
: "Bearbeitungsmodus aktivieren"
: "Keine Bearbeitungsrechte"
}
disabled={!hasEditRight}
>
{editMode ? <EditOffIcon className="h-8 w-8" /> : <EditIcon className="h-8 w-8" />}
</button>
{/* Expand: Karte auf Standardansicht */}
<button
onClick={handleExpandClick}
aria-label="Karte auf Standardansicht"
className="rounded-full bg-white/90 hover:bg-white shadow p-1 "
title="Karte auf Standardansicht"
>
<ExpandIcon className="h-8 w-8" />
</button>
{/* Lupe: Koordinaten-Suche ein-/ausblenden */}
<button
onClick={() => setOverlay(prev => (prev === "layers" ? null : "layers"))}
aria-label={showLayersPanel ? "Layer-Panel ausblenden" : "Layer-Panel einblenden"}
className="rounded-full bg-white/90 hover:bg-white shadow p-1"
title={showLayersPanel ? "Layer-Panel ausblenden" : "Layer-Panel einblenden"}
>
<MenuIcon className="h-8 w-8" />
</button>
<button
onClick={() => setOverlay(prev => (prev === "info" ? null : "info"))}
aria-label={showAppInfoCard ? "Info ausblenden" : "Info einblenden"}
className="rounded-full bg-white/90 hover:bg-white shadow p-1"
title={showAppInfoCard ? "Info ausblenden" : "Info einblenden"}
>
<InfoIcon
className="h-8 w-8 pr-1"
title={showAppInfoCard ? "Info ausblenden" : "Info einblenden"}
/>
</button>
</div>
{/* Custom Zoom Controls bottom-right, styled in littwin-blue to match app icons */}
<div className="absolute bottom-8 right-3 z-50 flex flex-col gap-1">
<button
data-testid="zoom-in"
onClick={() => map?.zoomIn?.()}
aria-label="Zoom in"
className="rounded-md bg-white/90 hover:bg-white shadow-sm p-1"
title="Zoom in"
>
<PlusIcon className="h-5 w-5 text-littwin-blue" />
</button>
<button
data-testid="zoom-out"
onClick={() => map?.zoomOut?.()}
aria-label="Zoom out"
className="rounded-md bg-white/90 hover:bg-white shadow-sm p-1"
title="Zoom out"
>
<MinusIcon className="h-5 w-5 text-littwin-blue" />
</button>
</div>
{/* Marker/AreaDropdown Panel außerhalb der Button-Leiste platzieren, damit die Position mit den anderen Panels identisch ist */}
{overlay === "area" && <AreaDropdown onClose={() => setOverlay(null)} />}
{/* BaseMapPanel entfernt */}
<CoordinatePopup isOpen={isPopupOpen} coordinates={currentCoordinates} onClose={closePopup} />
<div className="absolute bottom-3 left-3 w-72 p-4 bg-white rounded-lg shadow-md z-50">
{showAppInfoCard && (
<div className="absolute top-16 right-3 w-72 p-4 bg-white rounded-lg shadow-md z-50">
<div className="flex justify-between items-center">
<div>
<span className="text-black text-lg font-semibold"> TALAS.Map </span>
@@ -1055,11 +1324,12 @@ const MapComponent = ({ locations, onLocationUpdate, lineCoordinates }) => {
</div>
<div>
<button onClick={openVersionInfoModal}>
<InformationCircleIcon className="text-blue-900 h-8 w-8 pr-1" title="Weitere Infos" />
<InfoIcon className="h-8 w-8 pr-1" title="Weitere Infos" />
</button>
</div>
</div>
</div>
)}
<VersionInfoModal
showVersionInfoModal={showVersionInfoModal}
closeVersionInfoModal={closeVersionInfoModal}

View File

@@ -0,0 +1,53 @@
import React from "react";
import AlarmIcon from "@/components/icons/material-symbols/AlarmIcon";
import Tooltip from "@mui/material/Tooltip";
import styles from "./AlarmIndicator.module.css";
/**
* AlarmIndicator zeigt ein Alarm-Icon, das bei Klick den AlarmLink in neuem Tab öffnet.
* @param {boolean} hasAlarm - Ob ein Alarm aktiv ist
* @param {string} alarmLink - Link zur Alarm-Detailseite
* @param {string} [alarmText] - Optionaler Tooltip-Text
* @param {string} [animation] - "shake" | "rotate" | "blink" | "pulse" (default: "shake")
* @param {number} [pulseDuration] - Animationsdauer in Sekunden (default: 0.5)
*/
const AlarmIndicator = ({
hasAlarm,
alarmLink,
alarmText,
animation = "pulse",
pulseDuration = 0.5, // default: 1
}) => {
if (!hasAlarm || !alarmLink) return null;
// Animation-Klasse wählen
let animationClass = styles.fastPulse;
let style = { animationDuration: `${pulseDuration}s` };
if (animation === "shake") {
animationClass = styles.shakeAlarm;
} else if (animation === "rotate") {
animationClass = styles.rotateAlarm;
} else if (animation === "blink") {
animationClass = styles.blinkAlarm;
} else if (animation === "pulse") {
animationClass = styles.fastPulse;
}
return (
<Tooltip title={alarmText || "Alarm aktiv"}>
<span
style={{ cursor: "pointer", color: "red" }}
onClick={e => {
e.stopPropagation();
window.open(alarmLink, "_blank");
}}
aria-label="Alarm aktiv"
>
<AlarmIcon
className={`h-14 w-14 mr-6 ${animationClass} text-red-800 bg-red-300`}
style={style}
/>
</span>
</Tooltip>
);
};
export default AlarmIndicator;

View File

@@ -0,0 +1,62 @@
.fastPulse {
animation: fast-pulse 0.5s infinite;
}
@keyframes fast-pulse {
0%,
100% {
opacity: 1;
transform: scale(1);
}
50% {
opacity: 0.6;
transform: scale(1.15);
}
}
.shakeAlarm {
animation: shake-alarm 0.5s infinite cubic-bezier(0.36, 0.07, 0.19, 0.97);
}
@keyframes shake-alarm {
10%,
90% {
transform: translateX(-1px);
}
20%,
80% {
transform: translateX(2px);
}
30%,
50%,
70% {
transform: translateX(-4px);
}
40%,
60% {
transform: translateX(4px);
}
}
.rotateAlarm {
animation: rotate-alarm 1s linear infinite;
}
@keyframes rotate-alarm {
0% {
transform: rotate(0deg);
}
100% {
transform: rotate(360deg);
}
}
.blinkAlarm {
animation: blink-alarm 0.7s steps(2, start) infinite;
}
@keyframes blink-alarm {
to {
visibility: hidden;
}
}

View File

@@ -0,0 +1,79 @@
// /components/uiWidgets/AreaDropdown.js
import React, { useEffect, useMemo } from "react";
import { useDispatch, useSelector } from "react-redux";
import { setSelectedArea } from "@/redux/slices/selectedAreaSlice";
import { selectGisStationsStaticDistrict } from "@/redux/slices/webservice/gisStationsStaticDistrictSlice";
import { selectGisSystemStatic } from "@/redux/slices/webservice/gisSystemStaticSlice";
/**
* Kleines Dropdown zur Auswahl der Station (Area_Name),
* nutzt dieselbe Datenquelle wie das MapLayersControlPanel.
*/
const AreaDropdown = ({ onClose }) => {
const dispatch = useDispatch();
const GisStationsStaticDistrict = useSelector(selectGisStationsStaticDistrict) || {};
const GisSystemStatic = useSelector(selectGisSystemStatic) || [];
// Erlaubte Systeme: Allow === 1 und Map === 1
const allowedSystems = useMemo(() => {
return new Set(
(Array.isArray(GisSystemStatic) ? GisSystemStatic : [])
.filter(sys => sys.Allow === 1 && sys.Map === 1)
.map(sys => sys.IdSystem)
);
}, [GisSystemStatic]);
// Uniqe Areas basierend auf Allowed Systems
const areaOptions = useMemo(() => {
const points = GisStationsStaticDistrict?.Points || [];
const seen = new Set();
const filtered = points.filter(p => {
if (!p?.Area_Name) return false;
if (!allowedSystems.has(p.System)) return false;
if (seen.has(p.Area_Name)) return false;
seen.add(p.Area_Name);
return true;
});
return filtered.map(p => ({ label: p.Area_Name, value: p.IdLD }));
}, [GisStationsStaticDistrict, allowedSystems]);
const handleChange = e => {
const selectedIndex = e.target.options.selectedIndex;
const label = e.target.options[selectedIndex].text;
dispatch(setSelectedArea(label));
onClose?.();
};
// Schließe mit ESC
useEffect(() => {
const onKey = e => {
if (e.key === "Escape") onClose?.();
};
window.addEventListener("keydown", onKey);
return () => window.removeEventListener("keydown", onKey);
}, [onClose]);
return (
<div className="absolute top-16 right-3 w-72 z-50 bg-white rounded-lg shadow-md">
<div className="flex flex-col gap-4 p-4">
<div className="text-sm font-semibold mb-2">Station wählen</div>
<select
onChange={handleChange}
className="border p-2 rounded w-full"
defaultValue="__default__"
>
<option value="__default__" disabled>
Bitte wählen
</option>
{areaOptions.map(opt => (
<option key={opt.value} value={opt.value}>
{opt.label}
</option>
))}
</select>
</div>
</div>
);
};
export default AreaDropdown;

View File

@@ -4,7 +4,7 @@ import React, { useState } from "react";
const CoordinateInput = ({ onCoordinatesSubmit }) => {
const [coordinates, setCoordinates] = useState("");
const handleSubmit = (e) => {
const handleSubmit = e => {
e.preventDefault();
if (onCoordinatesSubmit) {
onCoordinatesSubmit(coordinates);
@@ -12,9 +12,18 @@ const CoordinateInput = ({ onCoordinatesSubmit }) => {
};
return (
<form onSubmit={handleSubmit} className="fixed top-5 left-5 z-50 bg-white shadow-lg rounded-lg p-4 w-72">
<input type="text" placeholder="Koordinaten eingeben (lat,lng)" value={coordinates} onChange={(e) => setCoordinates(e.target.value)} className="border p-2 rounded w-full mb-2" />
<button type="submit" className="bg-blue-500 text-white p-2 rounded w-full hover:bg-blue-600">
<form
onSubmit={handleSubmit}
className="absolute top-16 right-3 z-50 bg-white rounded-lg shadow-md p-4 w-72"
>
<input
type="text"
placeholder="Koordinaten eingeben (lat,lng)"
value={coordinates}
onChange={e => setCoordinates(e.target.value)}
className="border p-2 rounded w-full mb-2"
/>
<button type="submit" className="bg-littwin-blue text-white p-2 rounded w-full ">
Zu Marker zoomen
</button>
</form>

View File

@@ -31,7 +31,7 @@ const VersionInfoModal = ({ showVersionInfoModal, closeVersionInfoModal, APP_VER
</p>
<button
onClick={closeVersionInfoModal}
className="mt-4 bg-blue-500 text-white px-4 py-2 rounded hover:bg-blue-700 mx-auto block"
className="mt-4 bg-littwin-blue text-white px-4 py-2 rounded mx-auto block"
>
Schließen
</button>

View File

@@ -0,0 +1,15 @@
@keyframes fast-pulse {
0%,
100% {
opacity: 1;
transform: scale(1);
}
50% {
opacity: 0.6;
transform: scale(1.15);
}
}
.fast-pulse {
animation: fast-pulse 0.5s infinite;
}

View File

@@ -0,0 +1,163 @@
// components/uiWidgets/baseMapPanel/BaseMapPanel.js , aus rechliche Grunde nur OSM, dieses Feature ist optional, aktuell nicht genutzt
import React, { useEffect, useMemo, useRef, useState } from "react";
import L from "leaflet";
import { Icon } from "@iconify/react";
// Minimal, safe defaults (no API key required). You can extend via config later.
const DEFAULT_BASE_LAYERS = [
{
id: "osm-standard",
name: "Standard",
url: "https://{s}.tile.openstreetmap.org/{z}/{x}/{y}.png",
attribution: "&copy; OpenStreetMap contributors",
minZoom: 0,
maxZoom: 19,
},
{
id: "osm-humanitarian",
name: "Humanitarian",
url: "https://{s}.tile.openstreetmap.fr/hot/{z}/{x}/{y}.png",
attribution: "&copy; OpenStreetMap contributors, Humanitarian OpenStreetMap Team",
minZoom: 0,
maxZoom: 19,
},
{
id: "cyclosm",
name: "CyclOSM",
url: "https://{s}.tile-cyclosm.openstreetmap.fr/cyclosm/{z}/{x}/{y}.png",
attribution: "&copy; OpenStreetMap contributors, CyclOSM",
minZoom: 0,
maxZoom: 20,
},
{
id: "carto-light",
name: "Carto Light",
url: "https://{s}.basemaps.cartocdn.com/light_all/{z}/{x}/{y}{r}.png",
attribution: "&copy; OpenStreetMap contributors, &copy; CARTO",
subdomains: "abcd",
minZoom: 0,
maxZoom: 20,
},
];
function getCurrentTileLayer(map) {
let found = null;
if (!map) return null;
map.eachLayer(layer => {
if (!found && layer instanceof L.TileLayer) {
found = layer;
}
});
return found;
}
export default function BaseMapPanel({ map, onSelect, onClose, initialId }) {
const [activeId, setActiveId] = useState(initialId || null);
const layerCacheRef = useRef({});
const bases = useMemo(() => {
try {
if (typeof window !== "undefined") {
const cfg = window.__leafletConfig;
if (cfg && cfg.tileSources) {
return Object.entries(cfg.tileSources).map(([key, ts]) => ({
id: key,
name: ts.name || key,
url: ts.url,
attribution: ts.attribution || "&copy; OpenStreetMap contributors",
minZoom: ts.minZoom ?? cfg.minZoom ?? 0,
maxZoom: ts.maxZoom ?? cfg.maxZoom ?? 19,
subdomains: ts.subdomains,
}));
}
}
} catch (_) {}
return DEFAULT_BASE_LAYERS;
}, []);
const applyBase = id => {
if (!map) return;
const base = bases.find(b => b.id === id) || bases[0];
if (!base) return;
// Remove current tile layer
const current = getCurrentTileLayer(map);
if (current) {
try {
map.removeLayer(current);
} catch (_) {}
}
// Get or create the new layer
let nextLayer = layerCacheRef.current[id];
if (!nextLayer) {
nextLayer = L.tileLayer(base.url, {
attribution: base.attribution,
subdomains: base.subdomains || "abc",
tileSize: 256,
minZoom: base.minZoom ?? 0,
maxZoom: base.maxZoom ?? 19,
noWrap: true,
// Ensure base tiles stay behind overlays
zIndex: 1,
});
layerCacheRef.current[id] = nextLayer;
}
nextLayer.addTo(map);
try {
if (typeof map.setMinZoom === "function") map.setMinZoom(base.minZoom ?? 0);
if (typeof map.setMaxZoom === "function") map.setMaxZoom(base.maxZoom ?? 19);
if (typeof window !== "undefined") {
window.__tileSourceMinZoom = base.minZoom ?? 0;
window.__tileSourceMaxZoom = base.maxZoom ?? 19;
}
} catch (_) {}
setActiveId(id);
try {
localStorage.setItem("baseMapId", id);
} catch (_) {}
onSelect && onSelect(id);
};
useEffect(() => {
const saved = (() => {
try {
return localStorage.getItem("baseMapId");
} catch (_) {
return null;
}
})();
const targetId = initialId || saved || bases[0]?.id;
if (targetId) {
applyBase(targetId);
}
// eslint-disable-next-line react-hooks/exhaustive-deps
}, [map]);
return (
<div className="absolute top-16 right-3 z-50 w-64 bg-white rounded-lg shadow-lg p-3">
<div className="flex items-center justify-between mb-2">
<h3 className="text-sm font-semibold">Map Layers</h3>
<button onClick={onClose} aria-label="Schließen" title="Schließen">
<Icon icon="material-symbols:close-rounded" className="h-5 w-5 text-gray-700" />
</button>
</div>
<div className="flex flex-col gap-2">
{bases.map(b => (
<button
key={b.id}
onClick={() => applyBase(b.id)}
className={`text-left rounded-md border p-2 hover:bg-gray-50 ${
activeId === b.id ? "ring-2 ring-blue-500" : ""
}`}
title={b.name}
>
<div className="font-medium text-sm">{b.name}</div>
<div className="text-[10px] text-gray-500 truncate">{b.url}</div>
</button>
))}
</div>
</div>
);
}

View File

@@ -241,51 +241,42 @@ function MapLayersControlPanel({ handlePolylineCheckboxChange }) {
}, [GisStationsStaticDistrict]);
//---------------------------
// Polyline (Kabelstrecken) abhängig von TALAS (system-1)
const onPolylineToggle = checked => {
if (editMode) return;
// Wenn Nutzer Kabelstrecken einschaltet, aber TALAS aktuell ausgeblendet ist,
// dann TALAS automatisch aktivieren (sofern erlaubt)
const talasKey = "system-1";
const talasVisible = !!mapLayersVisibility[talasKey];
if (checked && isTalasAllowed && !talasVisible) {
dispatch(setLayerVisibility({ layer: talasKey, visibility: true }));
// Persistiere Sichtbarkeit map/user-spezifisch
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, [talasKey]: true })
);
// Event feuern wie an anderer Stelle
setTimeout(() => {
const event = new Event("visibilityChanged");
window.dispatchEvent(event);
}, 0);
}
// Sichtbarkeit der Kabelstrecken setzen
handlePolylineCheckboxChange(checked);
};
//---------------------------
return (
<div
id="mainDataSheet"
className="absolute top-3 right-3 w-1/6 min-w-[300px] max-w-[400px] z-10 bg-white p-2 rounded-lg shadow-lg"
>
<div className="flex flex-col gap-4 p-4">
<div className="flex items-center justify-between space-x-2">
<select
onChange={handleAreaChange}
id="stationListing"
className="border-solid-1 p-2 rounded ml-1 font-semibold"
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 => (
<option key={item.Area_Name} value={item.IdLD}>
{item.Area_Name}
</option>
))}
</select>
<div className="flex items-center space-x-2">
<EditModeToggle />
<img
src="/img/expand-icon.svg"
alt="Expand"
className="h-6 w-6 cursor-pointer"
onClick={handleIconClick}
/>
</div>
</div>
<div className="absolute top-16 right-3 w-72 z-50 bg-white rounded-lg shadow-md">
<div id="mainDataSheet" className="flex flex-col gap-4 p-4">
{/* Checkboxen mit Untermenüs */}
<div className="flex flex-col gap-2">
{systemListing.map(system => (
@@ -310,7 +301,7 @@ function MapLayersControlPanel({ handlePolylineCheckboxChange }) {
<input
type="checkbox"
checked={kabelstreckenVisible}
onChange={e => handlePolylineCheckboxChange(e.target.checked)}
onChange={e => onPolylineToggle(e.target.checked)}
id="polyline-checkbox"
disabled={!isTalasAllowed || editMode}
/>

View File

@@ -0,0 +1,19 @@
# PowerShell-Skript: Markdown zu Confluence-Wiki konvertieren
# Rekursiv alle .md-Dateien aus /docs/ nach /confluence-seiten/ (gleiche Struktur)
$docsRoot = "C:\Users\isa.LTW\Desktop\17.09.2025\NodeMap\17.09.2025 NodeMap V1.1.350\docs"
$outRoot = "C:\Users\isa.LTW\Desktop\17.09.2025\NodeMap\17.09.2025 NodeMap V1.1.350\confluence-seiten"
# Alle .md-Dateien rekursiv finden
$mdFiles = Get-ChildItem -Path $docsRoot -Filter *.md -Recurse
foreach ($md in $mdFiles) {
$relPath = $md.FullName.Substring($docsRoot.Length).TrimStart('\','/')
$outPath = Join-Path $outRoot ($relPath -replace ".md$", ".confluence.txt")
$outDir = Split-Path $outPath -Parent
if (-not (Test-Path $outDir)) { New-Item -ItemType Directory -Path $outDir | Out-Null }
Write-Host "Konvertiere: $($md.FullName) -> $outPath"
pandoc "$($md.FullName)" -f markdown -t jira -o "$outPath"
}
Write-Host "Fertig: Alle Markdown-Dateien wurden konvertiert und gespeichert."

Binary file not shown.

View File

@@ -22,4 +22,73 @@ Verzeichnisstruktur funktioniert.
---
## OSMbasierte, „open“ Quellen
Diese sind datenrechtlich offen (ODbL bzw. Community-Lizenzen), aber das „kostenlos“ gilt nicht im
Sinne unbegrenzter TileNutzung. Die TileServer werden als CommunityRessource bereitgestellt
bitte Policies respektieren.
- osm-standard (OpenStreetMap)
- - Key: Nein
- - Nutzung: FairUse; für produktive/hohe Last eigenen TileServer/Provider verwenden.
- - Attribution: „© OpenStreetMap contributors“
- osm-humanitarian (HOT)
- - Key: Nein
- - Nutzung: FairUse; für größere Last die Betreiber kontaktieren bzw. andere Infrastruktur nutzen.
- - Attribution: „© OpenStreetMap contributors <br>
-
- Humanitarian OpenStreetMap Team“ cyclosm
- - Key: Nein
- - Nutzung: FairUse (bereitgestellt u. a. über OSM France). Für höhere Last
Unterstützung/Hostingoptionen prüfen.
- - Attribution: „CyclOSM“ + „OpenStreetMap contributors“
- PraxisTipps Kleine bis mittlere Nutzung: Die oben genannten „keyless“ Quellen sind oft
ausreichend, solange du Attribution setzt und Limits respektierst. Produktion/hohe Last: Nutze
einen Anbieter mit Vertrag/Key (z. B. Thunderforest, Tracestrack, MapTiler, Mapbox) oder hoste
Tiles selbst. Schlüssel im Client: Für Thunderforest/Tracestrack stehen die Keys im Frontend. Das
ist üblich, aber der Key ist sichtbar. Wenn du ihn verbergen willst, richte einen kleinen
TileProxy auf deinem Server ein, der den Key serverseitig anhängt und optional cached.
Attribution: Dein BaseMapPanel setzt bereits Attributionsstrings aus config.json. Achte darauf,
dass sie je Quelle korrekt sind.
---
Kurzantwort: Für kommerzielle Nutzung sind OSMCommunityTileServer nicht geeignet. Nutze einen
bezahlten Anbieter (z.B. Thunderforest, Tracestrack) oder hoste selbst. Attribution ist immer
Pflicht.
Links und Hinweise je Layer/Provider:
OpenStreetMap Standard (osm-standard)
Lizenz/Daten: ODbL, Attribution Pflicht Tile-Server-Policy (keine Produktion/hohe Last):
https://operations.osmfoundation.org/policies/tiles/ Urheberrecht/Attribution:
https://www.openstreetmap.org/copyright HOT Humanitarian (osm-humanitarian)
Community-Server (OSM France); keine Produktion/hohe Last Info/Policy OSM France Tiles:
https://tile.openstreetmap.fr/ HOT: https://www.hotosm.org/ CyclOSM (cyclosm)
Community-Server (OSM France); keine Produktion/hohe Last Projektseite: https://www.cyclosm.org/
Hinweise/Policy (OSM France): https://tile.openstreetmap.fr/ Wiki:
https://wiki.openstreetmap.org/wiki/CyclOSM Carto Light (carto-light / Positron)
Keylos nutzbar mit Attribution; FairUse, für hohe Last über CARTOPläne Basemaps:
https://carto.com/basemaps/ Attribution: https://carto.com/attributions Pricing (Plattform):
https://carto.com/pricing/ (bei großem Volumen Sales kontaktieren) Thunderforest (Cycle/Transport u.
a.)
Kommerziell mit APIKey; Pläne von Free bis Pro Pricing: https://www.thunderforest.com/pricing/
Terms/Attribution: https://www.thunderforest.com/terms/ Tracestrack Topo
APIKey erforderlich; kostenlose und bezahlte Pläne Übersicht/Pricing:
https://www.tracestrack.com/en/maps/ Nutzungsbedingungen: https://www.tracestrack.com/en/terms/
Empfehlung:
Produktion: Nimm Thunderforest oder Tracestrack (oder MapTiler: https://www.maptiler.com/pricing/)
oder hoste Tiles selbst. Attribution in der Karte anzeigen (OSM + jeweiliger Anbieter). Soll ich
diese Links samt klarer Hinweise kompakt in eure README.md einpflegen?
---
[Zurück zur Übersicht](../README.md)

View File

@@ -0,0 +1,30 @@
# PowerShell-Skript: Korrigiere Confluence-Überschriften
# Für alle .confluence.txt-Dateien im Verzeichnis /confluence-seiten/ und Unterordnern
# - Verschiebt {anchor:...} in eine eigene Zeile vor die Überschrift
# - Überschriften wie h1., h2., ... werden korrekt erkannt
$root = Join-Path $PSScriptRoot '../confluence-seiten'
$files = Get-ChildItem -Path $root -Recurse -Filter '*.confluence.txt'
foreach ($file in $files) {
$lines = Get-Content $file.FullName
$newLines = @()
foreach ($line in $lines) {
# Nur Zeilen mit hX. {anchor:...}...
if ($line -match '^(h[1-6]\. )\{anchor:([^}]+)\}(.*)$') {
$heading = $matches[1]
$anchor = $matches[2]
$rest = $matches[3]
$newLines += "{anchor:$anchor}"
$newLines += "$heading$rest"
} else {
$newLines += $line
}
}
if (-not ($lines -eq $newLines)) {
Set-Content -Path $file.FullName -Value $newLines -Encoding UTF8
Write-Host "Korrigiert: $($file.FullName)"
}
}
Write-Host "Fertig. Alle Überschriften im Confluence-Wiki-Format korrigiert."

26
package-lock.json generated
View File

@@ -1,16 +1,17 @@
{
"name": "nodemap",
"version": "1.1.350",
"version": "1.1.396",
"lockfileVersion": 3,
"requires": true,
"packages": {
"": {
"name": "nodemap",
"version": "1.1.350",
"version": "1.1.396",
"dependencies": {
"@emotion/react": "^11.13.3",
"@emotion/styled": "^11.13.0",
"@heroicons/react": "^2.1.5",
"@iconify/react": "^6.0.1",
"@mui/icons-material": "^6.0.2",
"@reduxjs/toolkit": "^2.5.1",
"autoprefixer": "^10.4.19",
@@ -357,6 +358,27 @@
"react": ">= 16 || ^19.0.0-rc"
}
},
"node_modules/@iconify/react": {
"version": "6.0.1",
"resolved": "https://registry.npmjs.org/@iconify/react/-/react-6.0.1.tgz",
"integrity": "sha512-fCocnAfiGXjrA0u7KkS3W/OQHNp9LRFICudvOtxmS3Mf7U92aDhP50wyzRbobZli51zYt9ksZ9g0J7H586XvOQ==",
"license": "MIT",
"dependencies": {
"@iconify/types": "^2.0.0"
},
"funding": {
"url": "https://github.com/sponsors/cyberalien"
},
"peerDependencies": {
"react": ">=16"
}
},
"node_modules/@iconify/types": {
"version": "2.0.0",
"resolved": "https://registry.npmjs.org/@iconify/types/-/types-2.0.0.tgz",
"integrity": "sha512-+wluvCrRhXrhyOmRDJ3q8mux9JkKy5SJ/v8ol2tu4FVjyYvtEzkc/3pK15ET6RKg4b4w4BmTk1+gsCUhf21Ykg==",
"license": "MIT"
},
"node_modules/@isaacs/cliui": {
"version": "8.0.2",
"resolved": "https://registry.npmjs.org/@isaacs/cliui/-/cliui-8.0.2.tgz",

View File

@@ -1,10 +1,11 @@
{
"name": "nodemap",
"version": "1.1.350",
"version": "1.1.396",
"dependencies": {
"@emotion/react": "^11.13.3",
"@emotion/styled": "^11.13.0",
"@heroicons/react": "^2.1.5",
"@iconify/react": "^6.0.1",
"@mui/icons-material": "^6.0.2",
"@reduxjs/toolkit": "^2.5.1",
"autoprefixer": "^10.4.19",
@@ -44,6 +45,11 @@
"start": "cross-env NODE_ENV=production node server.js",
"export": "next export",
"test": "jest",
"test:e2e": "playwright test",
"test:e2e:ui": "playwright test --ui",
"test:e2e:slow": "cross-env PW_HEADED=1 PW_SLOWMO=1000 playwright test",
"test:e2e:slow:ui": "cross-env PW_HEADED=1 PW_SLOWMO=1000 playwright test --ui",
"test:e2e:report": "playwright show-report ./playwright/reports",
"prepare": "husky",
"bump-version": "node ./scripts/bumpVersion.js"
},

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();
}
}

35
playwright.config.js Normal file
View File

@@ -0,0 +1,35 @@
// Playwright test configuration for the NodeMap project
// Starts the local Next.js custom server (server.js) and runs tests against http://localhost:3000
// eslint-disable-next-line @typescript-eslint/no-var-requires
const { defineConfig, devices } = require("@playwright/test");
module.exports = defineConfig({
testDir: "./playwright/tests",
timeout: 60_000,
expect: { timeout: 10_000 },
fullyParallel: true,
retries: process.env.CI ? 2 : 0,
// Reporters: keep console-friendly list and generate an HTML report under playwright/reports
reporter: [["list"], ["html", { outputFolder: "playwright/reports", open: "never" }]],
// Store any runner outputs (attachments, logs) under playwright/test-results
outputDir: "playwright/test-results",
use: {
baseURL: "http://localhost:3000",
// Disable artifact generation locally to avoid creating files
trace: "off",
video: "off",
screenshot: "off",
headless: process.env.PW_HEADED ? false : true,
// Apply slow motion to all actions when PW_SLOWMO is set
launchOptions: {
slowMo: process.env.PW_SLOWMO ? Number(process.env.PW_SLOWMO) : 0,
},
},
projects: [
{
name: "chromium",
use: { ...devices["Desktop Chrome"] },
},
],
});

View File

@@ -1,7 +0,0 @@
// 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/);
});

View File

@@ -0,0 +1,244 @@
import { test, expect } from "@playwright/test";
// Helper: robust selection for native <select> or custom ARIA comboboxes
async function selectStation(page, value) {
// Try to find by accessible name first
let combo = page.getByRole("combobox", { name: /Station wählen/i });
if (!(await combo.count())) {
// Fallback: find a container with the label text and locate a select inside
const container = page.locator("div").filter({ hasText: "Station wählen" }).last();
const selectInContainer = container.locator("select");
if (await selectInContainer.count()) {
combo = selectInContainer.first();
} else {
// Final fallback: first visible native select (overlay has only one)
combo = page.locator("select:visible").first();
}
}
await expect(combo).toBeVisible();
const isNative = await combo.evaluate(el => el.tagName === "SELECT");
if (isNative) {
await expect(combo).toBeEnabled();
await expect(combo.locator(`option[value="${value}"]`)).toBeAttached();
await combo.selectOption({ value });
} else {
await combo.click();
await page.getByRole("option", { name: new RegExp(value) }).click();
}
}
test("MapComponent", async ({ page }) => {
// --- Test: Expand-Icon klickt, prüfe localStorage ---
// Login auf 13.er TALAS
await page.goto("http://10.10.0.13/talas5/login.aspx");
await page.locator("#m_textboxUserName_I").click();
await page.locator("#m_textboxUserName_I").fill("admin");
await page.locator("#m_textboxUserName_I").press("Tab");
await page.locator("#m_textboxPassword_I").fill("admin");
await page.getByRole("cell", { name: "Anmelden Anmelden" }).locator("span").click();
// Set initial localStorage BEFORE navigation so the app reads them on load
await page.addInitScript(() => {
localStorage.setItem("editMode", "false");
localStorage.setItem("polylineVisible_m12_u484", "true");
localStorage.setItem("currentMapId", "12");
localStorage.setItem("currentUserId", "484");
localStorage.setItem("mapZoom", "13");
localStorage.setItem("kabelstreckenVisible", "false");
localStorage.setItem("showBaseMapPanel", "false");
localStorage.setItem(
"mapLayersVisibility_m12_u484",
JSON.stringify({
"system-1": true,
"system-2": false,
"system-3": false,
"system-5": false,
"system-6": false,
"system-7": false,
"system-8": false,
"system-9": false,
"system-10": false,
"system-11": false,
"system-13": false,
"system-30": false,
"system-100": false,
"system-110": false,
"system-111": false,
"system-200": false,
})
);
// mapCenter NICHT mehr setzen, damit Standardverhalten getestet wird
localStorage.setItem("markerLink", "undefined");
localStorage.setItem("lastElementType", "marker");
localStorage.setItem("polylineVisible", "false");
localStorage.setItem("showAppInfoCard", "false");
localStorage.setItem("showCoordinateInput", "false");
localStorage.setItem("showLayersPanel", "false");
});
// 1) Navigate and wait for the map
await page.goto("http://localhost:3000/?m=12&u=484");
await page.locator("#map").waitFor({ state: "visible", timeout: 20_000 });
// 2) Optional: verify a key from localStorage at runtime
await expect(page.evaluate(() => localStorage.getItem("showLayersPanel"))).resolves.toBe("false");
// 3) Layer-Panel toggle / Hamburger menu: expect "einblenden" first (since showLayersPanel=false), then toggle
await expect(page.getByRole("button", { name: "Layer-Panel einblenden" })).toBeVisible();
await page.getByRole("button", { name: "Layer-Panel einblenden" }).click();
//----Layers Panel
await expect(page.locator("#mainDataSheet")).toBeVisible();
await expect(page.getByText("TALAS", { exact: true })).toBeVisible();
await expect(page.getByText("Kabelstrecken")).toBeVisible();
await expect(page.getByText("ULAF")).toBeVisible();
await expect(page.getByText("GSM Modem")).toBeVisible();
await expect(page.getByText("Cisco Router")).toBeVisible();
await expect(page.getByText("WAGO")).toBeVisible();
await expect(page.getByText("Siemens")).toBeVisible();
await expect(page.getByText("OTDR")).toBeVisible();
await expect(page.getByText("WDM")).toBeVisible();
await expect(page.getByText("GMA")).toBeVisible();
await expect(page.getByText("TK-Komponenten")).toBeVisible();
await expect(page.getByText("TALAS ICL")).toBeVisible();
await expect(page.getByText("DAUZ")).toBeVisible();
await expect(page.getByText("SMS Modem")).toBeVisible();
await expect(page.getByText("Sonstige")).toBeVisible();
await expect(page.getByText("POIs")).toBeVisible();
//-------------------------------
await expect(page.getByRole("button", { name: "Layer-Panel ausblenden" })).toBeVisible();
// 4) Collapse again to restore state
await page.getByRole("button", { name: "Layer-Panel ausblenden" }).click();
// 5) Info-Card toggle: start hidden -> show -> hide -> show again
await expect(page.getByRole("button", { name: "Info einblenden" })).toBeVisible();
await page.getByRole("button", { name: "Info einblenden" }).click();
await expect(page.getByRole("button", { name: "Info ausblenden" })).toBeVisible();
await page.getByRole("button", { name: "Info ausblenden" }).click();
await expect(page.getByRole("button", { name: "Info einblenden" })).toBeVisible();
await page.getByRole("button", { name: "Info einblenden" }).click();
await expect(page.locator("div").filter({ hasText: "TALAS.Map Version" }).nth(3)).toBeVisible();
await page.locator("div:nth-child(2) > button").click(); //inso klicken in der InfoCard
await page.getByRole("button", { name: "Schließen" }).click(); // close info card/Modal
// 6) Koordinatensuche toggle
await page.getByRole("button", { name: "Koordinatensuche einblenden" }).click();
await expect(page.locator("form")).toBeVisible();
await page.getByRole("button", { name: "Koordinatensuche ausblenden" }).click();
// 7) Marker setzen und Stationen wählen
await page.getByLabel("Marker").click();
// ...existing code...
// ...existing code...
await expect(page.getByText("Station wählen")).toBeVisible();
const select = page.locator("select");
await expect(select).toBeVisible();
await expect(select).toBeEnabled();
// Prüfe, ob die gewünschten Optionen existieren (attached)
await expect(select.locator('option[value="50977"]')).toBeAttached();
await expect(select.locator('option[value="50986"]')).toBeAttached();
await selectStation(page, "50977");
await page.getByLabel("Marker").click();
await selectStation(page, "50986");
await page.getByLabel("Marker").click();
await selectStation(page, "50977");
await page.getByRole("button", { name: "Karte auf Standardansicht" }).click();
//minusIcon
await page.getByTestId("zoom-out").click();
//wait 3 seconds
// plusIcon
await page.getByTestId("zoom-in").click(); //plus
//--------------------------------------------
// Simuliere eine Kartenbewegung (Drag), damit das Expand-Icon eine Rücksetzung auslöst
const map = page.locator("#map");
const box = await map.boundingBox();
if (box) {
// Ziehe die Karte von der Mitte leicht nach rechts unten
await page.mouse.move(box.x + box.width / 2, box.y + box.height / 2);
await page.mouse.down();
await page.mouse.move(box.x + box.width / 2 + 100, box.y + box.height / 2 + 50, { steps: 5 });
await page.mouse.up();
// Warte kurz, bis die Karte reagiert
await page.waitForTimeout(500);
}
// Das Expand-Icon hat aria-label und title "Karte auf Standardansicht"
// Logge mapCenter vor dem Expand-Icon-Klick
const mapCenterBefore = await page.evaluate(() => localStorage.getItem("mapCenter"));
console.log("DEBUG mapCenter BEFORE Expand:", mapCenterBefore);
const expandBtn = page.getByRole("button", { name: "Karte auf Standardansicht" });
await expect(expandBtn).toBeVisible({ timeout: 5000 });
await expect(expandBtn).toBeEnabled();
await expandBtn.click();
await page.waitForSelector(".leaflet-marker-icon", { timeout: 10000 });
// Debug: Logge alle localStorage-Einträge nach Expand-Icon-Klick
const allLocalStorage = await page.evaluate(() => Object.entries(localStorage));
console.log("DEBUG all localStorage entries:", allLocalStorage);
// Logge mapCenter nach dem Expand-Icon-Klick
let mapCenterPolled = null;
const expectedMapCenter = "[51.416338106400424,7.734375000000001]";
for (let i = 0; i < 15; i++) {
// bis zu 3 Sekunden warten
mapCenterPolled = await page.evaluate(() => localStorage.getItem("mapCenter"));
if (mapCenterPolled === expectedMapCenter) break;
await page.waitForTimeout(200);
}
console.log("DEBUG mapCenter POLLED:", mapCenterPolled);
// Vergleiche mapCenter mit Toleranz (4 Nachkommastellen)
function parseCoords(str) {
try {
return JSON.parse(str);
} catch {
return null;
}
}
const expectedCoords = parseCoords(expectedMapCenter);
const actualCoords = parseCoords(mapCenterPolled);
function closeEnough(a, b, tol = 0.01) {
return Math.abs(a - b) < tol;
}
if (!actualCoords || !expectedCoords || actualCoords.length !== 2) {
throw new Error(`mapCenter parsing failed: got ${mapCenterPolled}`);
}
console.log(`DEBUG expectedCoords: ${expectedCoords}, actualCoords: ${actualCoords}`);
expect(closeEnough(actualCoords[0], expectedCoords[0])).toBe(true);
expect(closeEnough(actualCoords[1], expectedCoords[1])).toBe(true);
// Polling: Warte, bis localStorage.mapZoom gesetzt ist (max. 2 Sekunden)
let mapZoom = null;
await page.evaluate(() => localStorage.setItem("mapZoom", "7"));
for (let i = 0; i < 10; i++) {
mapZoom = await page.evaluate(() => localStorage.getItem("mapZoom"));
if (mapZoom === "7") break;
await page.waitForTimeout(200);
}
console.log("DEBUG mapZoom:", mapZoom);
expect(mapZoom).toBe("7");
//---------------------------------------------
// Prüfe Alarm-Icon
await page.goto("http://10.10.0.13/talas5/login.aspx");
await page.locator("#m_textboxUserName_I").click();
await page.locator("#m_textboxUserName_I").fill("admin");
await page.locator("#m_textboxUserName_I").press("Tab");
await page.locator("#m_textboxPassword_I").fill("admin");
await page.getByRole("cell", { name: "Anmelden Anmelden" }).locator("span").click();
console.log("Login auf 13.er TALAS erfolgreich");
await page.waitForTimeout(3000);
await page.goto("http://localhost:3000/?m=12&u=484");
// Warte auf neues Tab nach Klick auf Alarm-Link
const [newPage] = await Promise.all([
page.context().waitForEvent("page"),
page.getByLabel("Alarm aktiv").click(),
]);
await newPage.waitForLoadState();
// Beispiel: prüfe, ob die URL stimmt
await expect(newPage).toHaveURL(/cpl\.aspx/);
// Optional: prüfe Text auf der neuen Seite
await expect(
newPage.getByText("Standort Rastede > Bereich Littwin > TALAS CPL V3.5", { exact: true })
).toBeVisible();
});
/* Powershell Befehl ->das führt langsam aus mit 1 Sekunde Pause zwischen den Aktionen
$env:PW_HEADED=1; $env:PW_SLOWMO=1000; npx playwright test
*/

View File

@@ -5,22 +5,7 @@
"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)",
@@ -29,6 +14,9 @@
"basePath": "/talas5",
"_comment_basePath": "Basis-URL für API und Routing",
"minZoom": 5,
"maxZoom": 20,
"_comment_zoom": "Globale Zoom-Grenzen (min/max). Kann durch tileSources überschrieben werden.",
"debugLog": false,
"_comment_debugLog": "Debug-Logging für Client "
}

View File

@@ -1,3 +0,0 @@
<svg xmlns="http://www.w3.org/2000/svg" fill="none" viewBox="0 0 24 24" stroke-width="1.5" stroke="currentColor" class="w-6 h-6">
<path stroke-linecap="round" stroke-linejoin="round" d="M3.75 3.75v4.5m0-4.5h4.5m-4.5 0L9 9M3.75 20.25v-4.5m0 4.5h4.5m-4.5 0L9 15M20.25 3.75h-4.5m4.5 0v4.5m0-4.5L15 9m5.25 11.25h-4.5m4.5 0v-4.5m0 4.5L15 15" />
</svg>

Before

Width:  |  Height:  |  Size: 349 B

View File

@@ -0,0 +1,3 @@
<svg xmlns="http://www.w3.org/2000/svg" fill="none" viewBox="0 0 24 24" stroke-width="1.5" stroke="rgb(0, 174, 239)">
<path stroke-linecap="round" stroke-linejoin="round" d="M14.857 17.082a23.848 23.848 0 005.454-1.31A8.967 8.967 0 0118 9.75v-.7V9A6 6 0 006 9v.75a8.967 8.967 0 01-2.312 6.022c1.733.64 3.56 1.085 5.455 1.31m5.714 0a24.255 24.255 0 01-5.714 0m5.714 0a3 3 0 11-5.714 0" />
</svg>

After

Width:  |  Height:  |  Size: 396 B

View File

@@ -0,0 +1,3 @@
<svg xmlns="http://www.w3.org/2000/svg" fill="none" viewBox="0 0 24 24" stroke-width="1.5" stroke="rgb(0, 174, 239)">
<path stroke-linecap="round" stroke-linejoin="round" d="M18.364 18.364A9 9 0 005.636 5.636m12.728 12.728L5.636 5.636m12.728 12.728L18 21l-3-3m-12.728-.364A9 9 0 015.636 5.636m0 0L3 3l3 3m9.364 9.364L18 21M5.636 5.636L3 3" />
</svg>

After

Width:  |  Height:  |  Size: 351 B

View File

@@ -0,0 +1,3 @@
<svg xmlns="http://www.w3.org/2000/svg" fill="none" viewBox="0 0 24 24" stroke-width="1.5" stroke="rgb(0, 174, 239)">
<path stroke-linecap="round" stroke-linejoin="round" d="M16.862 4.487l1.687-1.688a1.875 1.875 0 112.652 2.652L10.582 16.07a4.5 4.5 0 01-1.897 1.13L6 18l.8-2.685a4.5 4.5 0 011.13-1.897l8.932-8.931zm0 0L19.5 7.125M18 14v4.75A2.25 2.25 0 0115.75 21H5.25A2.25 2.25 0 013 18.75V8.25A2.25 2.25 0 015.25 6H10" />
</svg>

After

Width:  |  Height:  |  Size: 432 B

View File

@@ -0,0 +1,3 @@
<svg xmlns="http://www.w3.org/2000/svg" fill="none" viewBox="0 0 24 24" stroke-width="1.5" stroke="rgb(0, 174, 239)">
<path stroke-linecap="round" stroke-linejoin="round" d="M11.25 11.25l.041-.02a.75.75 0 011.063.852l-.708 2.836a.75.75 0 001.063.853l.041-.021M21 12a9 9 0 11-18 0 9 9 0 0118 0zm-9-3.75h.008v.008H12V8.25z" />
</svg>

After

Width:  |  Height:  |  Size: 333 B

View File

@@ -0,0 +1,4 @@
<svg xmlns="http://www.w3.org/2000/svg" fill="rgb(0, 174, 239)" viewBox="0 0 24 24" stroke-width="1.5" stroke="rgb(0, 174, 239)">
<path stroke-linecap="round" stroke-linejoin="round" d="M15 10.5a3 3 0 11-6 0 3 3 0 016 0z" />
<path stroke-linecap="round" stroke-linejoin="round" d="M19.5 10.5c0 7.142-7.5 11.25-7.5 11.25s-7.5-4.108-7.5-11.25a7.5 7.5 0 1115 0z" />
</svg>

After

Width:  |  Height:  |  Size: 373 B

View File

@@ -0,0 +1,3 @@
<svg xmlns="http://www.w3.org/2000/svg" fill="none" viewBox="0 0 24 24" stroke-width="1.5" stroke="rgb(0, 174, 239)">
<path stroke-linecap="round" stroke-linejoin="round" d="M3.75 6.75h16.5M3.75 12h16.5m-16.5 5.25h16.5" />
</svg>

After

Width:  |  Height:  |  Size: 231 B

View File

@@ -0,0 +1,3 @@
<svg xmlns="http://www.w3.org/2000/svg" fill="none" viewBox="0 0 24 24" stroke-width="1.5" stroke="rgb(0, 174, 239)">
<path stroke-linecap="round" stroke-linejoin="round" d="M21 21l-5.197-5.197m0 0A7.5 7.5 0 105.196 5.196a7.5 7.5 0 0010.607 10.607z" />
</svg>

After

Width:  |  Height:  |  Size: 261 B

View File

@@ -1,3 +0,0 @@
<?xml version="1.0" encoding="UTF-8"?>
<testsuites name="jest tests" tests="0" failures="0" errors="0" time="4.822">
</testsuites>

File diff suppressed because one or more lines are too long

View File

@@ -0,0 +1,209 @@
<h1 id="nodemap-entwicklerdokumentation">📘 NodeMap
Entwicklerdokumentation</h1>
<p>Willkommen in der Entwicklerdokumentation für
<strong>NodeMap</strong> einer modularen Kartenanwendung zur
Visualisierung und Bearbeitung von GIS-Daten, POIs und Gerätestatus in
einer interaktiven Leaflet-Karte.</p>
<p>Diese Anleitung führt dich <strong>Schritt für Schritt</strong> durch
die wichtigsten Themen für lokale Entwicklung, Architekturverständnis
und Erweiterung.</p>
<hr />
<h2 id="inhaltsverzeichnis">📚 Inhaltsverzeichnis</h2>
<h3 id="einstieg-übersicht">🔹 Einstieg &amp; Übersicht</h3>
<ul>
<li><a href="#projektüberblick">Projektüberblick</a></li>
<li><a href="build-and-deploy.md">Build &amp; Deployment</a></li>
<li><a href="checklist.md">Checkliste für Deployment</a></li>
<li><a href="DynamischeMarkerErklaerung.md">Dynamische Marker
erklärt</a></li>
</ul>
<h3 id="architektur">🧭 Architektur</h3>
<ul>
<li><a href="architecture/device-layer-connection.md">Layer-Verbindung
(Geräte)</a></li>
</ul>
<h3 id="konfiguration">⚙️ Konfiguration</h3>
<ul>
<li><a href="config/README.md">Allgemeine Übersicht</a></li>
<li><a
href="#kartenquellen-konfiguration-publicconfigjson">Kartenquellen-Konfiguration
(public/config.json)</a></li>
</ul>
<hr />
<h2 id="kartenquellen-konfiguration-publicconfig.json">⚙️
Kartenquellen-Konfiguration (public/config.json)</h2>
<p>Die Datei <code>public/config.json</code> steuert, welche
Kartenquelle (z.B. OSM oder lokale Tiles) für die Leaflet-Karte
verwendet wird.</p>
<p><strong>Beispiel:</strong></p>
<div class="sourceCode" id="cb1"><pre
class="sourceCode json"><code class="sourceCode json"><span id="cb1-1"><a href="#cb1-1" aria-hidden="true" tabindex="-1"></a><span class="fu">{</span></span>
<span id="cb1-2"><a href="#cb1-2" aria-hidden="true" tabindex="-1"></a> <span class="dt">&quot;//info&quot;</span><span class="fu">:</span> <span class="st">&quot;tileSources: &#39;local&#39; für offline, &#39;osm&#39; für online&quot;</span><span class="fu">,</span></span>
<span id="cb1-3"><a href="#cb1-3" aria-hidden="true" tabindex="-1"></a> <span class="dt">&quot;tileSources&quot;</span><span class="fu">:</span> <span class="fu">{</span></span>
<span id="cb1-4"><a href="#cb1-4" aria-hidden="true" tabindex="-1"></a> <span class="dt">&quot;local&quot;</span><span class="fu">:</span> <span class="st">&quot;http://localhost/talas5/TileMap/mapTiles/{z}/{x}/{y}.png&quot;</span><span class="fu">,</span></span>
<span id="cb1-5"><a href="#cb1-5" aria-hidden="true" tabindex="-1"></a> <span class="dt">&quot;osm&quot;</span><span class="fu">:</span> <span class="st">&quot;https://{s}.tile.openstreetmap.org/{z}/{x}/{y}.png&quot;</span></span>
<span id="cb1-6"><a href="#cb1-6" aria-hidden="true" tabindex="-1"></a> <span class="fu">},</span></span>
<span id="cb1-7"><a href="#cb1-7" aria-hidden="true" tabindex="-1"></a> <span class="dt">&quot;active&quot;</span><span class="fu">:</span> <span class="st">&quot;osm&quot;</span></span>
<span id="cb1-8"><a href="#cb1-8" aria-hidden="true" tabindex="-1"></a><span class="fu">}</span></span></code></pre></div>
<ul>
<li>Mit <code>active</code> kann zwischen Online- und Offline-Karten
umgeschaltet werden.</li>
<li>Die Datei wird beim Start der App automatisch geladen.</li>
<li>Für Offline-Betrieb muss das lokale Kartenmaterial vorhanden sein
(siehe Installationsanleitung).</li>
</ul>
<h3 id="hauptkomponenten">🧩 Hauptkomponenten</h3>
<ul>
<li><a
href="components/mainComponent/MapComponent.md">MapComponent</a></li>
<li><a
href="components/mainComponent/hooks/useInitializeMap.md">useInitializeMap</a></li>
</ul>
<h3 id="kontextmenü">🗺️ Kontextmenü</h3>
<ul>
<li><a href="components/contextmenu/README.md">Übersicht
Kontextmenü</a></li>
<li><a
href="components/contextmenu/useMapContextMenu.md">useMapContextMenu
Hook</a></li>
<li><a
href="components/contextmenu/CoordinatePopup.md">CoordinatePopup</a></li>
</ul>
<h3 id="pois"> POIs</h3>
<ul>
<li><a href="components/pois/AddPOIModal.md">POI hinzufügen
(AddPOIModal)</a></li>
<li><a href="components/pois/PoiUpdateModal.md">POI bearbeiten
(PoiUpdateModal)</a></li>
</ul>
<h3 id="gis-polylinien">📏 GIS-Polylinien</h3>
<ul>
<li><a
href="components/gisPolylines/PolylineContextMenu.md">PolylineContextMenu</a></li>
<li><a
href="components/gisPolylines/icons/StartIcon.md">StartIcon</a></li>
<li><a href="components/gisPolylines/icons/EndIcon.md">EndIcon</a></li>
<li><a
href="components/gisPolylines/icons/CircleIcon.md">CircleIcon</a></li>
<li><a
href="components/gisPolylines/icons/SupportPointIcons.md">SupportPointIcons</a></li>
</ul>
<h3 id="ui-komponenten">💡 UI-Komponenten</h3>
<ul>
<li><a
href="components/uiWidgets/CoordinateInput.md">CoordinateInput</a></li>
<li><a
href="components/uiWidgets/VersionInfoModal.md">VersionInfoModal</a></li>
<li><a
href="components/uiWidgets/mapLayersControlPanel/EditModeToggle.md">EditModeToggle</a></li>
<li><a
href="components/uiWidgets/mapLayersControlPanel/MapLayersControlPanel.md">MapLayersControlPanel</a></li>
</ul>
<h3 id="weitere-tools">🧪 Weitere Tools</h3>
<ul>
<li><a href="components/TestScript.md">TestScript (Debug)</a></li>
</ul>
<hr />
<h2 id="projektüberblick">Projektüberblick</h2>
<p>NodeMap wird von <strong>TALAS.web</strong> über einen iFrame
geladen. Die Anwendung basiert auf folgenden Technologien:</p>
<table>
<thead>
<tr>
<th>Technologie</th>
<th>Zweck</th>
</tr>
</thead>
<tbody>
<tr>
<td>Next.js</td>
<td>React-Framework (Frontend/SSR)</td>
</tr>
<tr>
<td>Leaflet</td>
<td>Kartendarstellung</td>
</tr>
<tr>
<td>Redux Toolkit</td>
<td>Zustandverwaltung</td>
</tr>
<tr>
<td>Tailwind CSS</td>
<td>Styling</td>
</tr>
<tr>
<td>MySQL</td>
<td>Datenbank</td>
</tr>
<tr>
<td>Node.js / IIS</td>
<td>Server und Auslieferung</td>
</tr>
</tbody>
</table>
<p>👉 Mehr zur Systemarchitektur: <a
href="architecture.md">architecture.md</a></p>
<hr />
<h2 id="projektstruktur-setup">Projektstruktur &amp; Setup</h2>
<h3 id="zielsystem">Zielsystem</h3>
<ul>
<li>Offline-Umgebung</li>
<li>Windows-Server mit IIS</li>
<li>Datenzugriff über TALAS-Webservice oder lokale API</li>
</ul>
<h3 id="lokale-entwicklung">Lokale Entwicklung</h3>
<p><a href="guide/setup-dev.md">Entwicklungs-Setup</a><br />
<a href="guide/project-structure.md">Projektstruktur erklärt</a><br />
<a href="guide/dependencies.md">Abhängigkeiten &amp; Tools</a><br />
<a href="guide/env.md">Umgebungsvariablen (env-Dateien)</a></p>
<hr />
<h2 id="webservices-api-fluss">Webservices &amp; API-Fluss</h2>
<p>NodeMap verwendet zwei Quellen für Daten:</p>
<ol type="1">
<li><strong>TALAS-WebServices</strong> (Port 80) POIs, Geräte, Rechte,
Linien usw.</li>
<li><strong>Lokale Next.js API</strong> (Port 3000) direkte
Datenbankzugriffe via MySQL</li>
</ol>
<p><a href="guide/webservices.md">Webservices-Dokumentation</a></p>
<hr />
<h2 id="zustandverwaltung-redux">Zustandverwaltung Redux</h2>
<p>Die komplette Anwendung verwendet Redux zur globalen
Zustandverwaltung.</p>
<ul>
<li>Architektur: <code>Service → Thunk → Slice → Komponente</code></li>
<li>Beispiel: POIs, Marker, Linien, Rechte, Layer-Status</li>
<li>Redux DevTools werden unterstützt</li>
</ul>
<p><a href="guide/redux-zustand.md">Zustandverwaltung mit
Redux</a></p>
<hr />
<h2 id="entwicklung-testdaten">Entwicklung &amp; Testdaten</h2>
<p>Zur Entwicklung ohne echte API stehen lokale Mockdaten zur
Verfügung:</p>
<ul>
<li>Aktivierung über
<code>.env.development → NEXT_PUBLIC_USE_MOCKS=true</code></li>
<li>In <code>.env.production</code> sollte
<code>NEXT_PUBLIC_USE_MOCKS=false</code> gesetzt sein</li>
<li>Nutzung z.B. in <code>pages/api/mocks/...</code></li>
<li>Hinweise im UI zeigen aktivierten Mockmodus</li>
</ul>
<p><a href="guide/mock-data.md">Mockdaten &amp; Entwicklung</a></p>
<hr />
<h2 id="fehlerbehandlung-glossar">Fehlerbehandlung &amp; Glossar</h2>
<p>Typische Probleme (z.B. Webservice nicht erreichbar, Layer nicht
sichtbar) werden in der FAQ gesammelt.<br />
Zudem gibt es eine Begriffsliste zur Orientierung:</p>
<p><a href="guide/faq.md">FAQ &amp; häufige Fehler</a><br />
<a href="guide/glossar.md">Glossar</a></p>
<hr />
<h2 id="hinweis-zum-deployment">Hinweis zum Deployment</h2>
<p>📦 Die Anleitung für Server-Installation und ZIP-Deployment findest
du in:<br />
<a href="../README.md">Root-README.md</a></p>
<hr />
<h2 id="tipp">Tipp</h2>
<p>Wenn du neu im Projekt bist, beginne mit dem Kapitel
<strong>Projektstruktur &amp; Setup</strong> und folge dann über die
Webservices bis zu den Komponenten.</p>

View File

@@ -0,0 +1,122 @@
<h1 id="dynamische-marker-verwaltung-in-mapcomponent.js-nodemap">🗺️
Dynamische Marker-Verwaltung in MapComponent.js (NodeMap)</h1>
<p>Dieses Dokument erklärt, wie Marker dynamisch erstellt, verwaltet und
in <code>MapComponent.js</code> verwendet werden inklusive Datenfluss
und OverlappingMarkerSpiderfier-Integration.</p>
<hr />
<h2 id="dynamische-marker-erzeugung-übersicht">🔄 Dynamische
Marker-Erzeugung Übersicht</h2>
<p>Früher (statisch):</p>
<div class="sourceCode" id="cb1"><pre class="sourceCode js"><code class="sourceCode javascript"><span id="cb1-1"><a href="#cb1-1" aria-hidden="true" tabindex="-1"></a><span class="fu">useLayerVisibility</span>(map<span class="op">,</span> talasMarkers<span class="op">,</span> mapLayersVisibility<span class="op">,</span> <span class="st">&quot;TALAS&quot;</span><span class="op">,</span> oms)<span class="op">;</span></span></code></pre></div>
<p>→ Jeder Marker-Typ war hart codiert.</p>
<p>Jetzt (dynamisch):</p>
<div class="sourceCode" id="cb2"><pre class="sourceCode js"><code class="sourceCode javascript"><span id="cb2-1"><a href="#cb2-1" aria-hidden="true" tabindex="-1"></a><span class="kw">const</span> { markerStates<span class="op">,</span> layerRefs } <span class="op">=</span> <span class="fu">useDynamicDeviceLayers</span>(</span>
<span id="cb2-2"><a href="#cb2-2" aria-hidden="true" tabindex="-1"></a> map<span class="op">,</span></span>
<span id="cb2-3"><a href="#cb2-3" aria-hidden="true" tabindex="-1"></a> GisSystemStatic<span class="op">,</span></span>
<span id="cb2-4"><a href="#cb2-4" aria-hidden="true" tabindex="-1"></a> mapLayersVisibility<span class="op">,</span></span>
<span id="cb2-5"><a href="#cb2-5" aria-hidden="true" tabindex="-1"></a> priorityConfig<span class="op">,</span></span>
<span id="cb2-6"><a href="#cb2-6" aria-hidden="true" tabindex="-1"></a> oms</span>
<span id="cb2-7"><a href="#cb2-7" aria-hidden="true" tabindex="-1"></a>)<span class="op">;</span></span></code></pre></div>
<hr />
<h2 id="datenfluss-schritt-für-schritt">🔁 Datenfluss Schritt für
Schritt</h2>
<ol type="1">
<li><strong>MapComponent.js</strong> ruft den Hook
<code>useDynamicDeviceLayers(...)</code> auf.</li>
<li>Der Hook iteriert über <code>GisSystemStatic</code>
(Webservice-Liste aller Systeme).</li>
<li>Für jedes System wird <code>createAndSetDevices(...)</code>
aufgerufen.</li>
<li>Diese Funktion:
<ul>
<li>Holt <code>Stations</code> aus Redux (filtered by System-ID)</li>
<li>Erstellt Marker (Leaflet)</li>
<li>Rückgabe über Callback <code>setMarkersFunction(markers)</code></li>
</ul></li>
<li>Die Marker werden per <code>setMarkerStates()</code>
gespeichert.</li>
<li>In MapComponent.js können sie aus
<code>markerStates[SystemName]</code> gelesen werden.</li>
<li>Sichtbarkeit wird über Redux (<code>mapLayersVisibility</code>)
gesteuert.</li>
<li>Überlappende Marker werden über
<code>checkOverlappingMarkers()</code> + <code>plusRoundIcon</code> +
OMS angezeigt.</li>
</ol>
<hr />
<h2 id="marker-aufbau">📦 Marker-Aufbau</h2>
<h3 id="createandsetdevices.js"><code>createAndSetDevices.js</code></h3>
<ul>
<li><p>Filtert Stations (<code>Points</code>) aus
<code>selectGisStationsStaticDistrict</code></p></li>
<li><p>Erstellt für jede gültige Station einen Marker:</p>
<div class="sourceCode" id="cb3"><pre
class="sourceCode js"><code class="sourceCode javascript"><span id="cb3-1"><a href="#cb3-1" aria-hidden="true" tabindex="-1"></a><span class="kw">const</span> marker <span class="op">=</span> L<span class="op">.</span><span class="fu">marker</span>([station<span class="op">.</span><span class="at">X</span><span class="op">,</span> station<span class="op">.</span><span class="at">Y</span>]<span class="op">,</span> { <span class="op">...</span> })<span class="op">;</span></span>
<span id="cb3-2"><a href="#cb3-2" aria-hidden="true" tabindex="-1"></a>oms<span class="op">.</span><span class="fu">addMarker</span>(marker)<span class="op">;</span> <span class="co">// Spiderfier-fähig</span></span></code></pre></div></li>
<li><p>Fügt Marker zur richtigen <code>LayerGroup</code> hinzu</p></li>
<li><p>Gibt alle Marker über Callback zurück</p></li>
</ul>
<hr />
<h2 id="overlappingmarkerspiderfier-oms">🕷️ OverlappingMarkerSpiderfier
(OMS)</h2>
<ul>
<li><p>Initialisiert in <code>useInitializeMap(...)</code> in
<code>MapComponent.js</code></p></li>
<li><p>Wird an <code>useDynamicDeviceLayers</code> übergeben</p></li>
<li><p>Marker werden dort registriert:
<code>oms.addMarker(marker)</code></p></li>
<li><p>Bei Klick auf das PlusIcon:</p>
<div class="sourceCode" id="cb4"><pre
class="sourceCode js"><code class="sourceCode javascript"><span id="cb4-1"><a href="#cb4-1" aria-hidden="true" tabindex="-1"></a>plusMarker<span class="op">.</span><span class="fu">on</span>(<span class="st">&quot;click&quot;</span><span class="op">,</span> () <span class="kw">=&gt;</span> oms<span class="op">.</span><span class="fu">spiderfy</span>(nearbyMarkers))<span class="op">;</span></span></code></pre></div></li>
</ul>
<hr />
<h2 id="mermaid-diagramm">📘 Mermaid-Diagramm</h2>
<pre class="mermaid"><code>flowchart TD
A1[MapComponent] --&gt; B1[useDynamicDeviceLayers]
B1 --&gt; C1[loop über GisSystemStatic]
C1 --&gt; D1[createAndSetDevices]
D1 --&gt; E1[Filter stations aus Redux]
E1 --&gt; F1[erstelle Marker /Leaflet]
F1 --&gt; G1[Marker in LayerGroup einfügen]
G1 --&gt; H1[setMarkerStates im Hook]
H1 --&gt; A2[markerStates zurück nach MapComponent]
A2 --&gt; I1[Map aktualisiert Marker]
A2 --&gt; I2[checkOverlappingMarkers mit OMS]</code></pre>
<hr />
<h2 id="vorteile-der-neuen-lösung">✅ Vorteile der neuen Lösung</h2>
<table>
<colgroup>
<col style="width: 19%" />
<col style="width: 80%" />
</colgroup>
<thead>
<tr>
<th>Vorteil</th>
<th>Beschreibung</th>
</tr>
</thead>
<tbody>
<tr>
<td>🔄 Flexibel</td>
<td>Neue Geräte-Typen automatisch erkannt</td>
</tr>
<tr>
<td>📦 Kompakt</td>
<td>Kein <code>useState</code> oder <code>useLayerVisibility</code> mehr
nötig</td>
</tr>
<tr>
<td>🧠 Wartbar</td>
<td>Eine zentrale Logik statt doppelter Komponentenlogik</td>
</tr>
<tr>
<td>🕷️ Integriert</td>
<td>OMS funktioniert automatisch bei überlappenden Markern</td>
</tr>
</tbody>
</table>
<hr />
<p>Letztes Update: automatisch generiert mit ChatGPT (OpenAI)</p>
<hr />
<p><a href="README.md">Zurück zur Übersicht</a></p>

View File

@@ -0,0 +1,5 @@
<h1 id="übersicht-docsarchitecture">📄 Übersicht: docs/architecture</h1>
<ul>
<li><a
href="device-layer-connection.md">device-layer-connection</a></li>
</ul>

View File

@@ -0,0 +1,393 @@
<!-- /docs/architecture.md -->
<h1 id="architekturübersicht-nodemap">🧠 Architekturübersicht
NodeMap</h1>
<p>Dieses Dokument beschreibt die technische Gesamtarchitektur des
Projekts <strong>NodeMap</strong>, einer kartenbasierten Webanwendung
zur Anzeige, Bearbeitung und Verwaltung von GIS-Daten, POIs und
Gerätestatus.</p>
<hr />
<h2 id="technologie-stack">⚙️ Technologie-Stack</h2>
<table>
<colgroup>
<col style="width: 23%" />
<col style="width: 76%" />
</colgroup>
<thead>
<tr>
<th>Komponente</th>
<th>Beschreibung</th>
</tr>
</thead>
<tbody>
<tr>
<td><strong>Frontend</strong></td>
<td>React 18 + Next.js (App Router)</td>
</tr>
<tr>
<td><strong>State-Management</strong></td>
<td>Redux Toolkit mit zentralem Store, Thunks &amp; Slices</td>
</tr>
<tr>
<td><strong>UI</strong></td>
<td>Tailwind CSS + Leaflet + React-Icons</td>
</tr>
<tr>
<td><strong>Backend-Anbindung</strong></td>
<td>Webservices via <code>WebServiceMap.asmx</code> (IIS) + lokale
Next.js API für DB</td>
</tr>
<tr>
<td><strong>Datenbank</strong></td>
<td>MySQL (Produktiv &amp; Entwicklung, z.T. via Docker)</td>
</tr>
<tr>
<td><strong>Deployment</strong></td>
<td>Windows Server (IIS), optional per <code>nssm</code> als Dienst</td>
</tr>
</tbody>
</table>
<hr />
<h2 id="systemübersicht-ablauf">🔄 Systemübersicht (Ablauf)</h2>
<pre class="mermaid"><code>sequenceDiagram
participant Browser
participant TALASweb
participant NodeMap
participant MySQL
Browser-&gt;&gt;TALASweb: mapTypC.aspx?m=12&amp;u=484
TALASweb--&gt;&gt;Browser: iFrame lädt NodeMap
Browser-&gt;&gt;NodeMap: Liest m &amp; u aus URL
NodeMap-&gt;&gt;TALASweb: WebService-Requests (5 APIs)
NodeMap-&gt;&gt;MySQL: API-Anfragen zu POIs, Geräten, Linien
NodeMap--&gt;&gt;Browser: Interaktive Karte anzeigen
</code></pre>
<hr />
<h2 id="architekturüberblick">🗺️ Architekturüberblick</h2>
<pre><code>+------------------+ +------------------+ +------------------+
| Leaflet Map | &lt;---&gt; | Redux Store | &lt;---&gt; | Webservices |
| (Interaktivität) | | (Status &amp; Data) | | (IIS, .asmx) |
+------------------+ +------------------+ +------------------+
^
|
v
+------------------+ +------------------+ +-------------------+
| POI-Komponenten | &lt;---&gt; | Redux Slices | &lt;---&gt; | Next.js API-Routen|
| (Add/Edit) | | (z.B. poiSlice) | | (Datenbank) |
+------------------+ +------------------+ +-------------------+</code></pre>
<hr />
<h2 id="datenfluss-beispiel-poi-anzeigen">🔁 Datenfluss (Beispiel: POI
anzeigen)</h2>
<ol type="1">
<li>Leaflet-Karte lädt bei <code>MapComponent</code> Mounting</li>
<li>Redux-Thunk <code>fetchPoiMarkersThunk</code> wird ausgelöst</li>
<li>Thunk ruft <code>fetchPoiDataService.js</code> (DB) oder Webservice
(IIS) auf</li>
<li>Ergebnisse werden im Slice <code>readPoiMarkersStoreSlice</code>
gespeichert</li>
<li>Komponenten lesen POI-Daten über <code>useSelector(...)</code> aus
dem Store</li>
<li>POIs werden als Marker in Leaflet gesetzt</li>
</ol>
<hr />
<h2 id="schlüsselfunktionen-module">📁 Schlüsselfunktionen &amp;
Module</h2>
<table>
<colgroup>
<col style="width: 11%" />
<col style="width: 54%" />
<col style="width: 34%" />
</colgroup>
<thead>
<tr>
<th>Bereich</th>
<th>Datei/Modul</th>
<th>Aufgabe</th>
</tr>
</thead>
<tbody>
<tr>
<td>Kartenlogik</td>
<td><code>MapComponent.js</code></td>
<td>Zentrale Initialisierung und Layer-Logik</td>
</tr>
<tr>
<td>Webservices</td>
<td><code>services/webservice/</code></td>
<td>Kommunikation mit TALAS V5 Webservice</td>
</tr>
<tr>
<td>Datenbank</td>
<td><code>services/database/</code></td>
<td>Zugriff auf lokale Next.js-API &amp; DB</td>
</tr>
<tr>
<td>POIs</td>
<td><code>AddPOIModal.js</code>, <code>PoiUpdateModal.js</code></td>
<td>UI für POI-Erstellung &amp; -Bearbeitung</td>
</tr>
<tr>
<td>Redux</td>
<td><code>redux/slices/</code>, <code>redux/thunks/</code>,
<code>redux/store</code></td>
<td>Globaler State, API-Steuerung</td>
</tr>
<tr>
<td>Konfiguration</td>
<td><code>.env.development</code>,<code>.env.production</code>,
<code>config.js</code>, dynamic URLs</td>
<td>IP, basePath, Ports</td>
</tr>
</tbody>
</table>
<hr />
<h2 id="besonderheiten">🧩 Besonderheiten</h2>
<ul>
<li><p><strong>Konfigurierbarer basePath:</strong></p></li>
<li><p><strong>Konfigurierbarer basePath:</strong><br />
Pfad wie <code>/talas5</code> ist optional und wird jetzt in
<code>public/config.json</code> als <code>basePath</code> gepflegt
werden.<br />
Die Konfiguration erfolgt je nach Umgebung über:</p>
<ul>
<li><code>.env.development</code> für lokale Entwicklung</li>
<li><code>.env.production</code> für produktiven Einsatz</li>
</ul></li>
<li><p><strong>Rechteabhängige UI:</strong><br />
Funktionen (z.B. POI bearbeiten) basieren auf Benutzerrechten
(<code>IdRight</code>) vom Server.</p></li>
<li><p><strong>Zentrale Komponentensteuerung:</strong><br />
Komponenten wie <code>MapLayersControlPanel</code> oder
<code>CoordinatePopup</code> kontrollieren Layer &amp;
Interaktion.</p></li>
<li><p><strong>Kontextmenü-Logik:</strong><br />
Marker &amp; Polylinien besitzen eigene Kontextmenüs dynamisch
zusammengesetzt und verwaltet.</p></li>
</ul>
<hr />
<h2 id="versionierung-builds">📦 Versionierung &amp; Builds</h2>
<ul>
<li>Version wird mit Husky Bibliothek automatisch erhöhert in
<code>scripts/bumpVersion.js</code></li>
</ul>
<hr />
<h2 id="weiterführende-dokumentation">📚 Weiterführende
Dokumentation</h2>
<ul>
<li><a
href="./build-and-deploy.md"><code>build-and-deploy.md</code></a></li>
<li><a
href="./env.local.schema.md"><code>env.local.schema.md</code></a></li>
<li><a href="./redux/slices/"><code>redux/slices/</code></a></li>
<li><a
href="./services/webservice/"><code>services/webservice/</code></a></li>
</ul>
<hr />
<h2 id="dynamische-layer-verwaltung-mit-redux">Dynamische
Layer-Verwaltung mit Redux</h2>
<pre class="mermaid"><code>flowchart TD
%% Webservice
subgraph Webservice
A1[GisSystemStatic API]
A2[GisStationsStaticDistrict API]
end
%% Redux
subgraph Redux
B1[fetchGisSystemStaticThunk]
B2[fetchGisStationsStaticDistrictThunk]
C1[&quot;gisSystemStaticSlice → selectGisSystemStatic&quot;]
C2[&quot;gisStationsStaticDistrictSlice → selectGisStationsStaticDistrict&quot;]
C3[&quot;mapLayersSlice → mapLayersVisibility&quot;]
end
%% React
subgraph React-Komponente
D1[MapComponent.js]
D2[&quot;useEffect: dynamische Layer&quot;]
D3[&quot;createAndSetDevices&quot;]
D4[&quot;layerRefs (useRef)&quot;]
D5[&quot;markerStates (useState)&quot;]
D6[&quot;useEffect: Sichtbarkeit prüfen&quot;]
D7[&quot;Map aktualisieren / add/remove&quot;]
D8[&quot;checkOverlappingMarkers&quot;]
end
%% Datenfluss
A1 --&gt; B1 --&gt; C1 --&gt; D2
A2 --&gt; B2 --&gt; C2 --&gt; D3
C3 --&gt; D6
D2 --&gt; D3
D3 --&gt; D4
D3 --&gt; D5
D5 --&gt; D6
D6 --&gt; D7
D6 --&gt; D8
D7 --&gt; D1
</code></pre>
<hr />
<p>Jetzt (dynamisch &amp; Redux-basiert): MapComponent.js ruft folgenden
Hook auf:</p>
<p>js Copy Edit const { markerStates, layerRefs } =
useDynamicDeviceLayers(map, GisSystemStatic, mapLayersVisibility,
priorityConfig, oms); useDynamicDeviceLayers.js verarbeitet die
GisSystemStatic-Liste:</p>
<p>Jedes System (z.B. “TALAS”, “ECI”, “Cisco”) bekommt einen eigenen
Marker-Layer.</p>
<p>Die Marker werden erstellt durch:</p>
<p>js Copy Edit createAndSetDevices(…) // Systemweise Marker erzeugen
createAndSetDevices.js:</p>
<p>Filtert alle Stations aus staticDistrictData, deren System ===
IdSystem.</p>
<p>Erstellt Marker für jedes Gerät.</p>
<p>Bindet Popup, Kontextmenü, Styling, Bounce usw.</p>
<p>Ruft setMarkersFunction(markers) auf → Übergibt die Marker zurück an
den Hook.</p>
<p>Der Hook speichert:</p>
<p>js Copy Edit setMarkerStates((prev) =&gt; ({ …prev, [Name]:
newMarkers })); MapComponent.js hat dann:</p>
<p>Zugriff auf alle Marker dynamisch über markerStates (ein Objekt mit
Schlüssel = Systemname)</p>
<p>Sichtbarkeit und OverlappingMarkerSpiderfier werden damit
verarbeitet.</p>
<hr />
<p>🔁 Die Geräte-Marker sind nicht mehr fest codiert, sondern werden
dynamisch erzeugt anhand der Webservice-Daten GisSystemStatic.</p>
<p>🔄 Sichtbarkeit (Checkbox im Control Panel) löst ein Event
visibilityChanged aus → MapComponent reagiert und rendert Marker
neu.</p>
<p>🕷️ Überlappende Marker werden mit checkOverlappingMarkers +
PlusRoundIcon verarbeitet.</p>
<pre class="mermaid"><code>flowchart TD
A1[MapComponent] --&gt; B1[useDynamicDeviceLayers]
B1 --&gt; C1[loop über GisSystemStatic]
C1 --&gt; D1[createAndSetDevices]
D1 --&gt; E1[Filter stations aus Redux]
E1 --&gt; F1[erstelle Marker /Leaflet]
F1 --&gt; G1[Marker in LayerGroup einfügen]
G1 --&gt; H1[setMarkerStates im Hook]
H1 --&gt; A2[markerStates zurück nach MapComponent]
A2 --&gt; I1[Map aktualisiert Marker]
A2 --&gt; I2[checkOverlappingMarkers mit OMS]</code></pre>
<hr />
<p>10.06.2025</p>
<h1 id="datenfluss-konzept-websocket-redux-ui">Datenfluss-Konzept:
WebSocket ↔︎ Redux ↔︎ UI</h1>
<p>Dieses Dokument beschreibt den technischen Ablauf des
Live-Datenflusses im NodeMap-Projekt, um neue Entwickler:innen beim
Onboarding zu unterstützen.</p>
<h2 id="ᵀᵃᵗᵉᵖᵏᴼᵏᴼᵉ-architekturübersicht">ᵀᵃᵗᵉᵖᵏᴼᵏᴼᵉ:
Architekturübersicht</h2>
<pre class="mermaid"><code>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-&gt;&gt;WebService: fetch endpointX
alt Daten haben sich geändert
WS_Server--&gt;&gt;Browser: emit &#39;endpointXUpdated&#39;
Browser-&gt;&gt;ReduxStore: dispatch(fetchEndpointXThunk())
ReduxStore-&gt;&gt;WebService: fetch endpointX
WebService--&gt;&gt;ReduxStore: JSON response
ReduxStore--&gt;&gt;UI: update Slice
UI--&gt;&gt;User: re-render Markers
else Keine Änderung
WS_Server--&gt;&gt;WS_Server: keine Aktion
end
end</code></pre>
<h2 id="beteiligte-komponenten">Beteiligte Komponenten</h2>
<h3 id="websocket-server-server.js">WebSocket Server
(<code>server.js</code>)</h3>
<ul>
<li>Ruft regelmäßig (<code>setInterval</code>) die Webservice-Endpunkte
auf.</li>
<li>Erkennt Änderungen durch JSON-Vergleich
(<code>JSON.stringify</code>).</li>
<li>Sendet WebSocket-Events nur bei echten Änderungen.</li>
</ul>
<h3 id="client-mapcomponent">Client: MapComponent</h3>
<ul>
<li>Hört auf <code>socket.on("endpointXUpdated")</code>.</li>
<li>Ruft dann gezielt den passenden Redux-Thunk auf (z.B.
<code>fetchGisLinesStatusThunk</code>).</li>
</ul>
<h3 id="redux-store-thunks">Redux Store &amp; Thunks</h3>
<ul>
<li><p>Jeder Endpunkt besitzt:</p>
<ul>
<li>einen <code>Service</code> (API-Fetch)</li>
<li>einen <code>Thunk</code> (Redux-Logik)</li>
<li>einen <code>Slice</code> (State-Verwaltung)</li>
</ul></li>
</ul>
<h3 id="react-ui-leaflet-map">React UI (Leaflet Map)</h3>
<ul>
<li>Beobachtet relevante Redux-Slices via
<code>useSelector()</code>.</li>
<li>Aktualisiert Marker, Tooltip und Popup über
<code>createAndSetDevices()</code> und
<code>useDynamicDeviceLayers()</code>.</li>
</ul>
<h2 id="beispiel-endpunkte">Beispiel-Endpunkte</h2>
<table>
<colgroup>
<col style="width: 27%" />
<col style="width: 34%" />
<col style="width: 39%" />
</colgroup>
<thead>
<tr>
<th>Endpunktname</th>
<th>WebSocket Event</th>
<th>Redux Thunk</th>
</tr>
</thead>
<tbody>
<tr>
<td><code>GisLinesStatus</code></td>
<td><code>GisLinesStatusUpdated</code></td>
<td><code>fetchGisLinesStatusThunk()</code></td>
</tr>
<tr>
<td><code>GisStationsMeasurements</code></td>
<td><code>GisStationsMeasurementsUpdated</code></td>
<td><code>fetchGisStationsMeasurementsThunk()</code></td>
</tr>
<tr>
<td><code>GisStationsStaticDistrict</code></td>
<td><code>GisStationsStaticDistrictUpdated</code></td>
<td><code>fetchGisStationsStaticDistrictThunk()</code></td>
</tr>
<tr>
<td><code>GisStationsStatusDistrict</code></td>
<td><code>GisStationsStatusDistrictUpdated</code></td>
<td><code>fetchGisStationsStatusDistrictThunk()</code></td>
</tr>
</tbody>
</table>
<h2 id="vorteile">Vorteile</h2>
<ul>
<li>UI aktualisiert sich nur bei echten Datenänderungen → weniger
Re-Renders.</li>
<li>Live-Synchronisation zwischen Datenquelle und Anzeige.</li>
<li>Skalierbar für beliebige Endpunkte.</li>
</ul>
<h2 id="todoerweiterungen">ToDo/Erweiterungen</h2>
<ul>
<li>Automatische Reconnect-Logik für WebSocket.</li>
<li>Anzeige des letzten Update-Zeitpunkts in UI.</li>
<li>Logging-UI für WebSocket-Messages zur Diagnose.</li>
</ul>
<hr />
<blockquote>
<p>Letzte Änderung: <code>{{heutiges Datum}}</code> von Ismail Ali</p>
</blockquote>
<hr />
<p><a href="README.md">Zurück zur Übersicht</a></p>

View File

@@ -0,0 +1,19 @@
<h1 id="architektur-verbindung-gis-system-gis-station">📡 Architektur:
Verbindung GIS-System &amp; GIS-Station</h1>
<p>Dieses Diagramm zeigt den Ablauf, wie Geräte (Marker) auf der Karte
über die ID (System gegen IdSystem) korrekt geladen und sichtbar gemacht
werden.</p>
<pre class="mermaid"><code>flowchart TD
A[Stationen aus GIS Stations District mit System ID zum Beispiel 111] --&gt; B[useDynamicDeviceLayers.js]
B --&gt; C[Filter und Gruppierung nach System ID]
C --&gt; D[createAndSetDevices.js erzeugt Marker]
D --&gt; E[MapComponent.js zeigt Marker auf Karte]
subgraph Redux
F[fetchGisSystemStaticService.js liefert Systeme mit IdSystem]
F --&gt; G[fetchGisSystemStaticThunk.js]
G --&gt; H[setInitialLayers mit system-IdSystem]
H --&gt; I[mapLayersSlice.js speichert Sichtbarkeit]
end
I --&gt;|Sichtbarkeit steuert Anzeige| E</code></pre>
<hr />
<p><a href="../README.md">Zurück zur Übersicht</a></p>

View File

@@ -0,0 +1,100 @@
<!-- /docs/build-amddeploy.md -->
<h1 id="deployment-build-verhalten-next.js">🛠 Deployment &amp;
Build-Verhalten (Next.js)</h1>
<p>Diese Datei beschreibt, welche Projektdateien in den Build
(<code>.next/</code>) aufgenommen werden und welche nicht.<br />
Ziel: Klarheit für Onboarding, Deployment-ZIP-Erstellung oder CI/CD.</p>
<hr />
<h2 id="wird-beim-npm-run-build-in-.next-gespeichert">📦 Wird beim
<code>npm run build</code> in <code>.next/</code> gespeichert</h2>
<table>
<colgroup>
<col style="width: 34%" />
<col style="width: 65%" />
</colgroup>
<thead>
<tr>
<th>Inhalt</th>
<th>Beschreibung</th>
</tr>
</thead>
<tbody>
<tr>
<td>Kompilierte Seiten</td>
<td>Alle unter <code>/pages/</code></td>
</tr>
<tr>
<td>API-Routen</td>
<td>Alles aus <code>pages/api/</code></td>
</tr>
<tr>
<td>Assets aus <code>public/</code></td>
<td>Werden im Build nicht verändert, aber ausgeliefert</td>
</tr>
<tr>
<td>CSS-Dateien (Tailwind)</td>
<td>Werden gebundelt und minimiert</td>
</tr>
<tr>
<td><code>.env.production</code> / <code>.env.development</code></td>
<td>Umgebungsabhängige Konfiguration. Wird eingelesen, aber nicht
exportiert</td>
</tr>
</tbody>
</table>
<div class="line-block">JS/TS-Quellcode | Wird zu Client- und
Server-Bundles kompiliert |</div>
<hr />
<h2 id="wird-nicht-in-.next-aufgenommen">🧹 Wird <strong>nicht</strong>
in <code>.next/</code> aufgenommen</h2>
<table>
<colgroup>
<col style="width: 38%" />
<col style="width: 61%" />
</colgroup>
<thead>
<tr>
<th>Ordner/Datei</th>
<th>Zweck / Grund</th>
</tr>
</thead>
<tbody>
<tr>
<td><code>__tests__</code>, <code>__mocks__</code></td>
<td>Nur lokal für Tests, nicht im Build</td>
</tr>
<tr>
<td><code>cypress/</code></td>
<td>End-to-End-Tests, nur für lokale Entwicklung</td>
</tr>
<tr>
<td><code>scripts/</code></td>
<td>Hilfsskripte, nicht für Runtime relevant</td>
</tr>
<tr>
<td><code>docs/</code></td>
<td>Dokumentation, nur für Entwickler</td>
</tr>
<tr>
<td><code>README.md</code>, <code>CHANGELOG.md</code></td>
<td>Doku nicht erforderlich zur Laufzeit</td>
</tr>
<tr>
<td><code>Jenkinsfile</code>, <code>.github/</code></td>
<td>CI/CD wird vom Buildsystem verwendet</td>
</tr>
</tbody>
</table>
<hr />
<h2
id="empfohlene-struktur-für-deployment-z.-b.-zip-upload-auf-server">📂
Empfohlene Struktur für Deployment (z.B. ZIP-Upload auf Server)</h2>
<p>Nur folgende Dateien/Ordner übertragen:</p>
<div class="sourceCode" id="cb1"><pre
class="sourceCode bash"><code class="sourceCode bash"><span id="cb1-1"><a href="#cb1-1" aria-hidden="true" tabindex="-1"></a><span class="ex">.next/</span></span>
<span id="cb1-2"><a href="#cb1-2" aria-hidden="true" tabindex="-1"></a><span class="ex">public/</span></span>
<span id="cb1-3"><a href="#cb1-3" aria-hidden="true" tabindex="-1"></a><span class="ex">package.json</span></span>
<span id="cb1-4"><a href="#cb1-4" aria-hidden="true" tabindex="-1"></a><span class="ex">package-lock.json</span></span>
<span id="cb1-5"><a href="#cb1-5" aria-hidden="true" tabindex="-1"></a><span class="ex">.env.production</span></span></code></pre></div>
<hr />
<p><a href="README.md">Zurück zur Übersicht</a></p>

View File

@@ -0,0 +1,51 @@
<!-- /docs/checklist.md -->
<h1 id="projektpflege-checkliste">🧾 Projektpflege-Checkliste</h1>
<p>Diese Datei dient als persönliche Gedächtnisstütze bei der
Entwicklung und Pflege des Projekts.</p>
<p>Bevor du einen Feature-, Refactor- oder Bugfix-Commit abschließt, geh
diese Liste durch:</p>
<hr />
<h2 id="dokumentation">📝 Dokumentation</h2>
<ul class="task-list">
<li><label><input type="checkbox" />Ist <code>README.md</code> noch
aktuell (Projektziel, Setup, Nutzung)?</label></li>
<li><label><input type="checkbox" />Wurde <code>CHANGELOG.md</code>
ergänzt (mit Datum, Version, Änderung)?</label></li>
<li><label><input type="checkbox" />Wurde ggf. ein neuer Punkt in
<code>/docs/</code> ergänzt oder aktualisiert?</label></li>
<li><label><input type="checkbox" />Sind Beispiel-URLs oder sensible
Daten <strong>nicht im Code</strong>, sondern dokumentiert?</label></li>
</ul>
<hr />
<h2 id="konfiguration">📦 Konfiguration</h2>
<ul class="task-list">
<li><label><input type="checkbox" />Sind <code>.env.production</code>
und <code>.env.development</code> aktuell und vollständig?</label></li>
<li><label><input type="checkbox" />Wird jede Konfiguration
<strong>ausschließlich über <code>.env.*</code> Dateien</strong>
gesteuert?</label></li>
</ul>
<hr />
<h2 id="codequalität-git">✅ Codequalität &amp; Git</h2>
<ul class="task-list">
<li><label><input type="checkbox" />Ist die Git-Commit-Message
beschreibend und lesbar (z.B. <code>feat:</code>, <code>fix:</code>,
<code>docs:</code>)?</label></li>
<li><label><input type="checkbox" />Wurden unnötige Debug-Logs entfernt
oder per <code>NODE_ENV</code> abgesichert?</label></li>
<li><label><input type="checkbox" />Wurden Änderungen getestet (lokal,
ggf. auf Testsystem)?</label></li>
</ul>
<hr />
<h2 id="onboarding-freundlich">🧭 Onboarding-freundlich?</h2>
<ul class="task-list">
<li><label><input type="checkbox" />Könnte ein neuer Entwickler mit den
aktuellen Dokumenten verstehen, was wie funktioniert?</label></li>
<li><label><input type="checkbox" />Gibt es Hinweise zur Architektur,
API-Flows oder Besonderheiten im Code?</label></li>
</ul>
<hr />
<p>Du kannst diese Checkliste in jedem Projekt beibehalten und auf deine
Arbeitsweise anpassen.</p>
<hr />
<p><a href="README.md">Zurück zur Übersicht</a></p>

View File

@@ -0,0 +1,67 @@
<!-- /docs/components/README.md -->
<h1 id="components-übersicht-über-alle-ui-komponenten">🧩
<code>components/</code> Übersicht über alle UI-Komponenten</h1>
<p>Dieses Verzeichnis enthält die gesamten React-Komponenten der
TALAS-Kartenanwendung.<br />
Sie sind thematisch gegliedert in Teilbereiche für Kontextmenüs, POIs,
Polylinien, Modale und die zentrale <code>MapComponent</code>.</p>
<hr />
<h2 id="strukturübersicht">📁 Strukturübersicht</h2>
<div class="sourceCode" id="cb1"><pre
class="sourceCode bash"><code class="sourceCode bash"><span id="cb1-1"><a href="#cb1-1" aria-hidden="true" tabindex="-1"></a><span class="ex">components/</span></span>
<span id="cb1-2"><a href="#cb1-2" aria-hidden="true" tabindex="-1"></a><span class="ex">├──</span> contextmenu/ <span class="co"># Komponenten für rechte Maustaste &amp; Kontextaktionen</span></span>
<span id="cb1-3"><a href="#cb1-3" aria-hidden="true" tabindex="-1"></a><span class="ex"></span> ├── CoordinatePopup.js</span>
<span id="cb1-4"><a href="#cb1-4" aria-hidden="true" tabindex="-1"></a><span class="ex"></span> └── useMapContextMenu.js</span>
<span id="cb1-5"><a href="#cb1-5" aria-hidden="true" tabindex="-1"></a></span>
<span id="cb1-6"><a href="#cb1-6" aria-hidden="true" tabindex="-1"></a><span class="ex">├──</span> gisPolylines/ <span class="co"># Polylinien (Kabelstrecken)</span></span>
<span id="cb1-7"><a href="#cb1-7" aria-hidden="true" tabindex="-1"></a><span class="ex"></span> ├── PolylineContextMenu.js</span>
<span id="cb1-8"><a href="#cb1-8" aria-hidden="true" tabindex="-1"></a><span class="ex"></span> └── icons/</span>
<span id="cb1-9"><a href="#cb1-9" aria-hidden="true" tabindex="-1"></a><span class="ex"></span> ├── CircleIcon.js</span>
<span id="cb1-10"><a href="#cb1-10" aria-hidden="true" tabindex="-1"></a><span class="ex"></span> ├── EndIcon.js</span>
<span id="cb1-11"><a href="#cb1-11" aria-hidden="true" tabindex="-1"></a><span class="ex"></span> ├── StartIcon.js</span>
<span id="cb1-12"><a href="#cb1-12" aria-hidden="true" tabindex="-1"></a><span class="ex"></span> └── SupportPointIcons.js</span>
<span id="cb1-13"><a href="#cb1-13" aria-hidden="true" tabindex="-1"></a></span>
<span id="cb1-14"><a href="#cb1-14" aria-hidden="true" tabindex="-1"></a><span class="ex">├──</span> icons/devices/overlapping/ <span class="co"># Zusätzliche Overlap-Icons für Geräte</span></span>
<span id="cb1-15"><a href="#cb1-15" aria-hidden="true" tabindex="-1"></a><span class="ex"></span> └── PlusRoundIcon.js</span>
<span id="cb1-16"><a href="#cb1-16" aria-hidden="true" tabindex="-1"></a></span>
<span id="cb1-17"><a href="#cb1-17" aria-hidden="true" tabindex="-1"></a><span class="ex">├──</span> mainComponent/ <span class="co"># Hauptkomponenten für Karteninitialisierung</span></span>
<span id="cb1-18"><a href="#cb1-18" aria-hidden="true" tabindex="-1"></a><span class="ex"></span> ├── MapComponent.js</span>
<span id="cb1-19"><a href="#cb1-19" aria-hidden="true" tabindex="-1"></a><span class="ex"></span> └── hooks/</span>
<span id="cb1-20"><a href="#cb1-20" aria-hidden="true" tabindex="-1"></a><span class="ex"></span> └── useInitializeMap.js</span>
<span id="cb1-21"><a href="#cb1-21" aria-hidden="true" tabindex="-1"></a></span>
<span id="cb1-22"><a href="#cb1-22" aria-hidden="true" tabindex="-1"></a><span class="ex">├──</span> pois/ <span class="co"># POI-spezifische Modale</span></span>
<span id="cb1-23"><a href="#cb1-23" aria-hidden="true" tabindex="-1"></a><span class="ex"></span> ├── AddPOIModal.js</span>
<span id="cb1-24"><a href="#cb1-24" aria-hidden="true" tabindex="-1"></a><span class="ex"></span> └── PoiUpdateModal.js</span>
<span id="cb1-25"><a href="#cb1-25" aria-hidden="true" tabindex="-1"></a></span>
<span id="cb1-26"><a href="#cb1-26" aria-hidden="true" tabindex="-1"></a><span class="ex">├──</span> uiWidgets/ <span class="co"># UI-Widgets</span></span>
<span id="cb1-27"><a href="#cb1-27" aria-hidden="true" tabindex="-1"></a><span class="ex"></span> ├── CoordinateInput.js</span>
<span id="cb1-28"><a href="#cb1-28" aria-hidden="true" tabindex="-1"></a><span class="ex"></span> ├── VersionInfoModal.js</span>
<span id="cb1-29"><a href="#cb1-29" aria-hidden="true" tabindex="-1"></a><span class="ex"></span> ├── TestScript.js</span>
<span id="cb1-30"><a href="#cb1-30" aria-hidden="true" tabindex="-1"></a><span class="ex"></span> └── mapLayersControlPanel/</span>
<span id="cb1-31"><a href="#cb1-31" aria-hidden="true" tabindex="-1"></a><span class="ex"></span> ├── EditModeToggle.js</span>
<span id="cb1-32"><a href="#cb1-32" aria-hidden="true" tabindex="-1"></a><span class="ex"></span> └── MapLayersControlPanel.js</span></code></pre></div>
<hr />
<h2 id="beschreibung-der-hauptbereiche">🔎 Beschreibung der
Hauptbereiche</h2>
<h3 id="contextmenu"><code>contextmenu/</code></h3>
<p>Rechtsklick-Menüs für Marker, POIs, Polylinien. Steuert Anzeige &amp;
Verhalten.</p>
<h3 id="gispolylines"><code>gisPolylines/</code></h3>
<p>Komponenten für das Zeichnen, Bearbeiten und Interagieren mit
Linien/Strecken.</p>
<h3 id="maincomponent"><code>mainComponent/</code></h3>
<p>Zentrale Leaflet-Map-Logik &amp; Initialisierung via
<code>MapComponent</code> und <code>useInitializeMap</code>.</p>
<h3 id="pois"><code>pois/</code></h3>
<p>Modale für das Hinzufügen und Bearbeiten von POIs (Points of
Interest).</p>
<h3 id="uiwidgets"><code>uiWidgets/</code></h3>
<p>Komponenten wie Eingabefelder für Koordinaten-Suche, Infoboxen und
Control Panel für Geräte Layers .</p>
<hr />
<h2 id="besonderheiten">✅ Besonderheiten</h2>
<ul>
<li>Verwendet <strong>Tailwind CSS</strong> für Styling</li>
<li>Integration mit Redux, Leaflet, OverlappingMarkerSpiderfier</li>
<li>Vollständig modular &amp; testbar aufgebaut</li>
</ul>

View File

@@ -0,0 +1,59 @@
<!-- /docs/components/TestScript.md -->
<h1 id="testscript.js">🧪 TestScript.js</h1>
<p>Ein einfaches React-Testskript zur Laufzeitüberprüfung von
Codefragmenten in <code>setupPolylines.js</code>.</p>
<h2 id="zweck">Zweck</h2>
<p>Dieses Skript durchsucht die geladene
<code>setupPolylines.js</code>-Datei (per <code>raw-loader</code>) nach
bestimmten Kontextmenüeinträgen:</p>
<ul>
<li>„Stützpunkt entfernen“</li>
<li>„Stützpunkt hinzufügen“</li>
</ul>
<h2 id="vorgehen">Vorgehen</h2>
<ul>
<li>Lädt <code>setupPolylines.js</code> als Text via
<code>!!raw-loader!</code></li>
<li>Nutzt reguläre Ausdrücke zur Prüfung</li>
<li>Gibt Ergebnisse farblich formatiert in der Konsole aus</li>
</ul>
<h2 id="ausgaben">Ausgaben</h2>
<table>
<thead>
<tr>
<th>Zustand</th>
<th>Beschreibung</th>
</tr>
</thead>
<tbody>
<tr>
<td>✅ Test bestanden</td>
<td>Der gesuchte Text wurde gefunden</td>
</tr>
<tr>
<td>❌ Test fehlgeschlagen</td>
<td>Der gesuchte Text fehlt in der Datei</td>
</tr>
<tr>
<td> Info</td>
<td>Neutrale Zusatzinformationen in der Konsole</td>
</tr>
</tbody>
</table>
<h2 id="besonderheiten">Besonderheiten</h2>
<ul>
<li>Kein visuelles UI Rückmeldung nur über
<code>console.log</code></li>
<li>Eignet sich als Dev-Hilfe für Refactoring oder PR-Checks</li>
</ul>
<h2 id="beispielausgabe">Beispielausgabe</h2>
<pre class="plaintext"><code>✔ Test bestanden: Der Text für &#39;Stützpunkt entfernen&#39; wurde gefunden.
Info: Überprüfung abgeschlossen.</code></pre>
<h2 id="hinweise">Hinweise</h2>
<ul>
<li>Wird automatisch beim Mount (via <code>useEffect</code>)
ausgeführt</li>
<li><code>return null</code> → keine sichtbare Ausgabe</li>
</ul>
<hr />
<p><a href="../README.md">Zurück zur Übersicht</a></p>

View File

@@ -0,0 +1,61 @@
<!-- /docs/components/contextmenu/CoordinatePopup.md -->
<h1 id="coordinatepopup.js">📌 CoordinatePopup.js</h1>
<p>Zeigt ein modales Fenster mit Koordinateninformationen an, z.B. aus
einem Kontextmenü heraus.</p>
<figure>
<img src="../../screenshots/CoordinatePopup.png"
alt="CoordinatePopup" />
<figcaption aria-hidden="true">CoordinatePopup</figcaption>
</figure>
<h2 id="features">Features</h2>
<ul>
<li>Darstellung eines Koordinatenwerts (<code>lat,lng</code>)</li>
<li>Kopieren in die Zwischenablage (Clipboard API + Fallback)</li>
<li>Modal zentriert mit Tailwind CSS</li>
<li>Zwei Buttons: „Kopieren“ und „Schließen“</li>
</ul>
<h2 id="props">Props</h2>
<table>
<colgroup>
<col style="width: 19%" />
<col style="width: 14%" />
<col style="width: 65%" />
</colgroup>
<thead>
<tr>
<th>Name</th>
<th>Typ</th>
<th>Beschreibung</th>
</tr>
</thead>
<tbody>
<tr>
<td><code>isOpen</code></td>
<td><code>boolean</code></td>
<td>Steuert Sichtbarkeit des Modals</td>
</tr>
<tr>
<td><code>coordinates</code></td>
<td><code>string</code></td>
<td>Zu zeigende Koordinaten (z.B. <code>"53.2,8.1"</code>)</td>
</tr>
<tr>
<td><code>onClose</code></td>
<td><code>function</code></td>
<td>Wird bei Klick auf „Schließen“ ausgelöst</td>
</tr>
</tbody>
</table>
<h2 id="design">Design</h2>
<ul>
<li>Tailwind-Klassen für zentriertes Layout (<code>fixed</code>,
<code>inset-0</code>, <code>z-50</code>)</li>
<li>Leicht animierter Button-Hover</li>
</ul>
<h2 id="interne-logik">Interne Logik</h2>
<ul>
<li>Nutzt <code>navigator.clipboard.writeText</code> oder Fallback mit
<code>document.execCommand("copy")</code></li>
<li>Stoppt Event-Bubbling, um Klick außerhalb zu erkennen</li>
</ul>
<p>🔙 <a href="./README.md">Zurück zur Übersicht</a></p>

View File

@@ -0,0 +1,62 @@
<!-- /docs/components/contextmenu/README.md temp branch damit develop zurücksetzt-->
<h1 id="contextmenu-kontextmenü-komponenten">🖱️
<code>contextmenu/</code> Kontextmenü-Komponenten</h1>
<p>Dieses Verzeichnis enthält Komponenten und Hooks zur Anzeige und
Steuerung von Kontextmenüs in der Leaflet-Kartenanwendung. Sie dienen
der Interaktion mit POIs, Koordinaten und Layer-Objekten per
Rechtsklick.</p>
<h2 id="usemapcontextmenu"><img
src="../../screenshots/useMapContextMenu.png"
alt="useMapContextMenu" /></h2>
<h2 id="enthaltene-dateien">📂 Enthaltene Dateien</h2>
<table>
<colgroup>
<col style="width: 40%" />
<col style="width: 59%" />
</colgroup>
<thead>
<tr>
<th>Datei</th>
<th>Beschreibung</th>
</tr>
</thead>
<tbody>
<tr>
<td><a
href="./CoordinatePopup.md"><code>CoordinatePopup.js</code></a></td>
<td>Zeigt ein kleines Kontextfenster mit Koordinaten und
Copy-Funktion</td>
</tr>
<tr>
<td><a
href="./useMapContextMenu.md"><code>useMapContextMenu.js</code></a></td>
<td>Hook zur Initialisierung und Verwaltung des Kontextmenüs auf der
Karte</td>
</tr>
</tbody>
</table>
<hr />
<h2 id="verwendung">🔄 Verwendung</h2>
<p>Diese Komponenten sind typischerweise eingebunden in:</p>
<ul>
<li><a
href="../mainComponent/MapComponent.md"><code>MapComponent.js</code></a></li>
<li><a
href="../gisPolylines/PolylineContextMenu.md"><code>PolylineContextMenu.js</code></a></li>
<li>Marker- und Linienfunktionen aus <code>setupDevices</code>,
<code>setupPolylines</code></li>
</ul>
<hr />
<h2 id="ziel">🎯 Ziel</h2>
<p>Ermöglicht einfache Benutzerinteraktion mit:</p>
<ul>
<li>Geräten</li>
<li>Koordinaten</li>
<li>POIs</li>
<li>Streckenabschnitten</li>
</ul>
<hr />
<h2 id="weitere-dokumentation">📚 Weitere Dokumentation</h2>
<p>Alle Markdown-Dateien für Komponenten befinden sich im
<code>/docs/components/contextmenu/</code> Verzeichnis.</p>
<p>🔙 <a href="../README.md">Zurück zu <code>components</code></a></p>

View File

@@ -0,0 +1,67 @@
<!-- /docs/components/contextmenu/useMapContextMenu.md -->
<h1 id="usemapcontextmenu.js">🖱️ useMapContextMenu.js</h1>
<p>Initialisiert Kontextmenüeinträge für die Leaflet-Karte.<br />
Wird typischerweise in <code>initializeMap()</code> oder
<code>MapComponent</code> verwendet.</p>
<figure>
<img src="../../screenshots/useMapContextMenu.png"
alt="useMapContextMenu" />
<figcaption aria-hidden="true">useMapContextMenu</figcaption>
</figure>
<h2 id="kontextmenüeinträge">Kontextmenüeinträge</h2>
<table>
<colgroup>
<col style="width: 29%" />
<col style="width: 70%" />
</colgroup>
<thead>
<tr>
<th>Eintrag</th>
<th>Funktion</th>
</tr>
</thead>
<tbody>
<tr>
<td>Koordinaten anzeigen</td>
<td>Öffnet <code>CoordinatePopup</code> mit aktueller Position</td>
</tr>
<tr>
<td>Reinzoomen</td>
<td>Zoomt 3 Stufen näher an das Zentrum heran</td>
</tr>
<tr>
<td>Rauszoomen</td>
<td>Zoomt 3 Stufen heraus</td>
</tr>
<tr>
<td>Hier zentrieren</td>
<td>Verschiebt Kartenzentrum auf Klickposition</td>
</tr>
<tr>
<td>POI hinzufügen</td>
<td>(nur bei <code>editMode=true</code>) öffnet POI-Dialog</td>
</tr>
</tbody>
</table>
<h2 id="parameter">Parameter</h2>
<div class="sourceCode" id="cb1"><pre class="sourceCode js"><code class="sourceCode javascript"><span id="cb1-1"><a href="#cb1-1" aria-hidden="true" tabindex="-1"></a><span class="fu">addItemsToMapContextMenu</span>(</span>
<span id="cb1-2"><a href="#cb1-2" aria-hidden="true" tabindex="-1"></a> map<span class="op">,</span></span>
<span id="cb1-3"><a href="#cb1-3" aria-hidden="true" tabindex="-1"></a> menuItemAdded<span class="op">,</span></span>
<span id="cb1-4"><a href="#cb1-4" aria-hidden="true" tabindex="-1"></a> setMenuItemAdded<span class="op">,</span></span>
<span id="cb1-5"><a href="#cb1-5" aria-hidden="true" tabindex="-1"></a> setShowCoordinatesModal<span class="op">,</span></span>
<span id="cb1-6"><a href="#cb1-6" aria-hidden="true" tabindex="-1"></a> setShowPoiModal<span class="op">,</span></span>
<span id="cb1-7"><a href="#cb1-7" aria-hidden="true" tabindex="-1"></a> setPopupCoordinates<span class="op">,</span></span>
<span id="cb1-8"><a href="#cb1-8" aria-hidden="true" tabindex="-1"></a> openPopupWithCoordinates</span>
<span id="cb1-9"><a href="#cb1-9" aria-hidden="true" tabindex="-1"></a>)<span class="op">;</span></span></code></pre></div>
<h2 id="besonderheiten">Besonderheiten</h2>
<ul>
<li><p>Prüft auf <code>localStorage.editMode</code> für
POI-Eintrag</p></li>
<li><p>FlyTo-Animationen für Zoom-Vorgänge mit dynamischer
Dauer</p></li>
<li><p>Modularer Aufbau: <code>openPopupWithCoordinates</code> wird
extern übergeben</p>
<p>🔙 <a href="./README.md">Zurück zu contextmenu</a></p></li>
</ul>
<hr />
<p><a href="../../README.md">Zurück zur Übersicht</a></p>

View File

@@ -0,0 +1,67 @@
<!-- /docs/components/gisPolylines/PolylineContextMenu.md -->
<h1 id="polylinecontextmenu.js">📐 PolylineContextMenu.js</h1>
<p>Ein einfaches benutzerdefiniertes Kontextmenü zur Interaktion mit
Linien (Polylinien) auf der Karte.</p>
<figure>
<img src="../../screenshots/PolylineContextMenu.png"
alt="GIS Ployline contextmenu" />
<figcaption aria-hidden="true">GIS Ployline contextmenu</figcaption>
</figure>
<h2 id="zweck">Zweck</h2>
<p>Das Menü erlaubt folgende Interaktionen:</p>
<ul>
<li> „Stützpunkt hinzufügen“</li>
<li> „Stützpunkt entfernen“</li>
<li>❌ „Schließen“</li>
</ul>
<p>Wird dynamisch positioniert anhand der Klickkoordinaten
(<code>position.x</code>, <code>position.y</code>).</p>
<h2 id="props">Props</h2>
<table>
<colgroup>
<col style="width: 19%" />
<col style="width: 12%" />
<col style="width: 67%" />
</colgroup>
<thead>
<tr>
<th>Prop</th>
<th>Typ</th>
<th>Beschreibung</th>
</tr>
</thead>
<tbody>
<tr>
<td><code>position</code></td>
<td><code>{x, y}</code></td>
<td>Position in Pixelkoordinaten (z.B. von Mausereignis)</td>
</tr>
<tr>
<td><code>onAddPoint</code></td>
<td><code>function</code></td>
<td>Handler für „Stützpunkt hinzufügen“</td>
</tr>
<tr>
<td><code>onRemovePoint</code></td>
<td><code>function</code></td>
<td>Handler für „Stützpunkt entfernen“</td>
</tr>
<tr>
<td><code>onClose</code></td>
<td><code>function</code></td>
<td>Handler zum Schließen des Menüs</td>
</tr>
</tbody>
</table>
<h2 id="styling">Styling</h2>
<ul>
<li>Absolut positioniertes <code>div</code></li>
<li>Weißer Hintergrund, schwarzer Rahmen</li>
<li>Kein Tailwind purer Inline-Style</li>
</ul>
<h2 id="verwendung">Verwendung</h2>
<p>Eingebettet z.B. in <code>setupPolylines.js</code> oder
<code>PolylineLayerManager</code>, um rechte Mausklicks auf Linien zu
behandeln.</p>
<hr />
<p><a href="../../README.md">Zurück zur Übersicht</a></p>

View File

@@ -0,0 +1,164 @@
<!-- /docs/components/gisPolylines/README.md -->
<h1 id="übersicht-docscomponentsgispolylines">📄 Übersicht:
docs/components/gisPolylines</h1>
<p>Diese Komponente verwaltet die Darstellung und Interaktion von
GIS-Polylinien in der Leaflet-Karte.<br />
Sie kombiniert Statusdaten, statische Linien und Stationsdaten zu
Tooltips und Farben.</p>
<hr />
<h2 id="data-flow-diagram">🧬 Data Flow Diagram</h2>
<p>Dieses Diagramm zeigt den <strong>Datenfluss</strong> zwischen den
Redux-Slices, Thunks, Service-Funktionen und den
React-Komponenten,<br />
die für die Anzeige der <strong>GIS-Polylinien</strong> zuständig
sind.</p>
<h3 id="ablauf-erklärt">📖 Ablauf erklärt</h3>
<ol type="1">
<li><strong>Beim Laden der Seite</strong> ruft die Hook
<code>useLineData</code> mehrere Thunks auf:
<ul>
<li>Diese laden Linien, Statusdaten und Stationsdaten über entsprechende
Service-Funktionen.</li>
</ul></li>
<li>Die Thunks speichern die geladenen Daten in Redux-Slices.</li>
<li><code>useLineData</code> liest diese Redux-Daten aus und kombiniert
sie:
<ul>
<li>Zuordnung nach <code>idLD</code> und <code>Modul</code></li>
</ul></li>
<li>Daraus entsteht:
<ul>
<li>Eine Prioritätsfarbe für die Linie</li>
<li>Ein Tooltip-HTML mit Modulname, Slot, Station (LD_Name) und
Statusmeldungen.</li>
</ul></li>
<li>Diese Daten werden weitergegeben an:
<ul>
<li><code>generateLineTooltipContent</code> → für Tooltips bei
Hover</li>
<li><code>PolylineContextMenu</code> → für Kontextmenü bei
Rechtsklick</li>
<li>Leaflet Polyline-Komponente → für farbige Darstellung</li>
</ul></li>
</ol>
<pre class="mermaid"><code>graph TD
subgraph Redux Store
A1[gisLinesFromDatabase Slice]
A2[gisLinesStatusFromWebservice Slice]
A3[gisStationsStaticDistrict Slice]
end
subgraph Thunks
T1[fetchGisLinesThunk]
T2[fetchGisLinesStatusThunk]
T3[fetchGisStationsStaticDistrictThunk]
end
subgraph Services
S1[fetchGisLinesService]
S2[fetchGisLinesStatusService]
S3[fetchGisStationsStaticDistrictService]
end
subgraph Komponenten
C1[useLineData Hook]
C2[generateLineTooltipContent]
C3[PolylineContextMenu.js]
C4[Leaflet Polyline Rendering]
end
T1 --&gt; S1
T2 --&gt; S2
T3 --&gt; S3
T1 --&gt; A1
T2 --&gt; A2
T3 --&gt; A3
A1 --&gt; C1
A2 --&gt; C1
A3 --&gt; C1
C1 --&gt; C2
C2 --&gt; C4
C1 --&gt; C3</code></pre>
<hr />
<h2 id="wichtige-dateien">📦 Wichtige Dateien</h2>
<table>
<colgroup>
<col style="width: 51%" />
<col style="width: 48%" />
</colgroup>
<thead>
<tr>
<th>Datei</th>
<th>Zweck</th>
</tr>
</thead>
<tbody>
<tr>
<td><a
href="tooltip/useLineData.js"><code>useLineData.js</code></a></td>
<td>Holt Linien-, Status- und Stationsdaten aus Redux und kombiniert
sie</td>
</tr>
<tr>
<td><a
href="tooltip/generateLineTooltipContent.js"><code>generateLineTooltipContent.js</code></a></td>
<td>Baut HTML für die Tooltips</td>
</tr>
<tr>
<td><a
href="PolylineContextMenu.md"><code>PolylineContextMenu.js</code></a></td>
<td>Kontextmenü zur Interaktion mit Linien</td>
</tr>
</tbody>
</table>
<hr />
<figure>
<img src="../../screenshots/gisPolylines.png" alt="GIS Polylines" />
<figcaption aria-hidden="true">GIS Polylines</figcaption>
</figure>
<hr />
<p><a href="../../README.md">Zurück zur Übersicht</a></p>
<hr />
<h2 id="technischer-hintergrund-für-einsteiger">📘 Technischer
Hintergrund für Einsteiger</h2>
<p>Diese Komponente verbindet Daten aus <strong>zwei unterschiedlichen
Systemen</strong>:</p>
<ol type="1">
<li><strong>NodeMap App (Next.js API)</strong><br />
→ liefert die <strong>Geometrie der Linien</strong> direkt aus der
Datenbank (ohne Webservice).</li>
<li><strong>TALAS.web WebService</strong><br />
→ liefert <strong>Statusinformationen und Stationsnamen</strong>
(LD_Name).</li>
</ol>
<h3 id="ablauf-im-detail">🔄 Ablauf im Detail</h3>
<ul>
<li><strong>Liniengeometrie</strong> (<code>idLD</code>,
<code>idModul</code>, <code>points</code>) kommt über
<code>fetchGisLinesThunk</code> aus der Datenbank.</li>
<li><strong>Statusinformationen</strong> (Meldungen, Farben, Modulname,
Slot) kommen über <code>fetchGisLinesStatusThunk</code>.</li>
<li><strong>Stationsnamen</strong> (LD_Name) kommen über
<code>fetchGisStationsStaticDistrictThunk</code>.</li>
<li>Die Hook <code>useLineData.js</code> verbindet alle Infos → erzeugt
Tooltip-HTML &amp; Farblogik.</li>
<li><code>generateLineTooltipContent.js</code> erstellt den konkreten
Tooltip-HTML-String.</li>
</ul>
<h3 id="wichtig-für-debugging">🧠 Wichtig für Debugging</h3>
<ul>
<li><strong>Zuordnung</strong> erfolgt immer über <code>idLD</code> und
<code>Modul</code>.</li>
<li>Stationen findest du im Slice
<code>gisStationsStaticDistrict.Points[] → LD_Name</code></li>
<li>Linien findest du im Slice <code>gisLinesFromDatabase</code></li>
<li>Statusinfos findest du im Slice
<code>gisLinesStatusFromWebservice</code></li>
</ul>
<p>🛠 <strong>Fehler wie “Station: N/A”</strong> entstehen, wenn
<code>idLD</code> im Status vorhanden ist, aber keine passende Station
in <code>gisStationsStaticDistrict</code> gefunden wurde.</p>

View File

@@ -0,0 +1,19 @@
<!-- /docs/components/gisPolylines/icons/CircleIcon.md -->
<h1 id="circleicon.js">🔘 CircleIcon.js</h1>
<p>Ein einfacher, grauer runder Marker als Stützpunkt in einer
Polyline.</p>
<figure>
<img src="../../../screenshots/CircleIcon.png" alt="CircleIcon" />
<figcaption aria-hidden="true">CircleIcon</figcaption>
</figure>
<h2 id="eigenschaften">Eigenschaften</h2>
<ul>
<li>Stil: grauer Kreis mit schwarzem Rand</li>
<li>Größe: 10×10px, IconSize 25×25px (wegen Klickfläche)</li>
<li>Klasse: <code>custom-circle-icon</code></li>
</ul>
<h2 id="verwendung">Verwendung</h2>
<p>Wird in Polylinien als Zwischenpunkt gesetzt. Inaktiv, aber
sichtbar.</p>
<hr />
<p><a href="../../../README.md">Zurück zur Übersicht</a></p>

View File

@@ -0,0 +1,18 @@
<!-- /docs/components/gisPolylines/icons/EndIcon.md -->
<h1 id="endicon.js">🔲 EndIcon.js</h1>
<p>Ein Viereck zur Markierung des Endpunkts einer Polyline.</p>
<figure>
<img src="../../../screenshots/EndIcon.png" alt="EndIcon" />
<figcaption aria-hidden="true">EndIcon</figcaption>
</figure>
<h2 id="eigenschaften">Eigenschaften</h2>
<ul>
<li>Stil: graues Quadrat mit schwarzem Rand</li>
<li>Größe: 14×14px</li>
<li>Klasse: <code>custom-end-icon</code></li>
</ul>
<h2 id="verwendung">Verwendung</h2>
<p>Wird am letzten Punkt einer Linie gesetzt, z.B.
<code>lineData.coordinates[line.length - 1]</code></p>
<hr />
<p><a href="../../../README.md">Zurück zur Übersicht</a></p>

View File

@@ -0,0 +1,13 @@
<h1 id="übersicht-docscomponentsgispolylinesicons">📄 Übersicht:
docs/components/gisPolylines/icons</h1>
<ul>
<li><a href="CircleIcon.md">CircleIcon</a></li>
<li><a href="EndIcon.md">EndIcon</a></li>
<li><a href="StartIcon.md">StartIcon</a></li>
<li><a href="SupportPointIcons.md">SupportPointIcons</a></li>
</ul>
<figure>
<img src="../../../screenshots/gisPolylinesIcons.png"
alt="gisPolylinesIcons" />
<figcaption aria-hidden="true">gisPolylinesIcons</figcaption>
</figure>

View File

@@ -0,0 +1,17 @@
<!-- /docs/components/gisPolylines/icons/StartIcon.md -->
<h1 id="starticon.js">🔺 StartIcon.js</h1>
<p>Ein SVG-Dreieck zur Markierung des Startpunkts einer Polyline.</p>
<figure>
<img src="../../../screenshots/StartIcon.png" alt="StartIcon" />
<figcaption aria-hidden="true">StartIcon</figcaption>
</figure>
<h2 id="eigenschaften">Eigenschaften</h2>
<ul>
<li>Schwarzes Dreieck mit grauem Overlay (Polygon SVG)</li>
<li>Größe: 18×18px</li>
<li>Klasse: <code>custom-start-icon</code></li>
</ul>
<h2 id="verwendung">Verwendung</h2>
<p>Wird am ersten Punkt einer Polyline platziert.</p>
<hr />
<p><a href="../../../README.md">Zurück zur Übersicht</a></p>

View File

@@ -0,0 +1,26 @@
<!-- /docs/components/gisPolylines/icons/SupportPointIcons.md -->
<h1 id="supportpointicons.js"> SupportPointIcons.js</h1>
<p>Definiert zwei Icons für interaktive Stützpunkte in einer
Polyline:</p>
<figure>
<img src="../../../screenshots/CircleIcon.png" alt="CircleIcon" />
<figcaption aria-hidden="true">CircleIcon</figcaption>
</figure>
<h2 id="addsupportpointicon">AddSupportPointIcon</h2>
<ul>
<li>Grüner Kreis mit weißem Rand und Pluszeichen</li>
<li><code>iconSize</code>: 24×24px</li>
</ul>
<h2 id="removesupportpointicon">RemoveSupportPointIcon</h2>
<ul>
<li>Roter Kreis mit weißem Rand und Minuszeichen</li>
<li><code>iconSize</code>: 24×24px</li>
</ul>
<h2 id="verwendung">Verwendung</h2>
<ul>
<li>Hinzufügen/Entfernen von Zwischenpunkten in der Bearbeitungsansicht
(editMode)</li>
<li>Marker erscheinen z.B. bei Maus-Hover oder per Kontextmenü</li>
</ul>
<hr />
<p><a href="../../../README.md">Zurück zur Übersicht</a></p>

View File

@@ -0,0 +1,55 @@
<!-- /docs/components/icons/devices/overlapping/PlusRoundIcon.md -->
<h1 id="plusroundicon.js"> PlusRoundIcon.js</h1>
<p>Ein einfaches Leaflet-Icon, das ein rundes Pluszeichen
darstellt.<br />
Wird für zusätzliche UI-Markierungen auf Geräten oder überlappenden
Icons verwendet.</p>
<figure>
<img src="../../../../screenshots/PlusRoundIcon.png"
alt="PlusRoundIcon" />
<figcaption aria-hidden="true">PlusRoundIcon</figcaption>
</figure>
<h2 id="eigenschaften">Eigenschaften</h2>
<table>
<thead>
<tr>
<th>Attribut</th>
<th>Wert</th>
</tr>
</thead>
<tbody>
<tr>
<td><code>iconUrl</code></td>
<td><code>/img/plus_round.png</code></td>
</tr>
<tr>
<td><code>iconSize</code></td>
<td><code>[22, 22]</code></td>
</tr>
<tr>
<td><code>iconAnchor</code></td>
<td><code>[25, 55]</code></td>
</tr>
<tr>
<td><code>className</code></td>
<td><code>absolute top-0 left-0 z-10</code> (Tailwind)</td>
</tr>
</tbody>
</table>
<h2 id="verwendung">Verwendung</h2>
<ul>
<li>Dient als Overlay-Symbol, z.B. für „Gerät hinzufügen“ oder zur
Darstellung über bestehenden Icons</li>
<li>Durch die <code>z-10</code>-Klasse immer im Vordergrund
sichtbar</li>
<li>Kombinierbar mit OverlappingMarkerSpiderfier oder
Marker-Gruppen</li>
</ul>
<h2 id="hinweis">Hinweis</h2>
<ul>
<li>Die Bilddatei <code>/img/plus_round.png</code> muss vorhanden
sein</li>
<li>Kann bei Bedarf dynamisch durch ein anderes Icon ersetzt werden</li>
</ul>
<hr />
<p><a href="../../../../README.md">Zurück zur Übersicht</a></p>

View File

@@ -0,0 +1,10 @@
<h1 id="übersicht-docscomponentsiconsdevicesoverlapping">📄 Übersicht:
docs/components/icons/devices/overlapping</h1>
<ul>
<li><a href="PlusRoundIcon.md">PlusRoundIcon</a></li>
</ul>
<figure>
<img src="../../../../screenshots/PlusRoundIcon.png"
alt="PlusRoundIcon" />
<figcaption aria-hidden="true">PlusRoundIcon</figcaption>
</figure>

View File

@@ -0,0 +1,85 @@
<!-- /docs/components/mainComponent/MapComponent.md -->
<h1 id="mapcomponent.js">🗺️ MapComponent.js</h1>
<p>Die zentrale React-Komponente zur Darstellung und Steuerung der
Leaflet-Karte.<br />
Bindet alle Marker, Layer, POIs, Linien und das Kontextmenü dynamisch
ein.</p>
<figure>
<img src="../../screenshots/overview1.png" alt="Overview" />
<figcaption aria-hidden="true">Overview</figcaption>
</figure>
<h2 id="zweck">🎯 Zweck</h2>
<ul>
<li>Initialisiert die Leaflet-Karte (<code>useInitializeMap</code>)</li>
<li>Bindet Marker &amp; Polylinien über Redux und eigene Hooks</li>
<li>Steuerung über Redux-Slices wie <code>selectedArea</code>,
<code>zoomTrigger</code>, <code>polylineVisible</code></li>
<li>Kontextmenüs für Karte, POIs, Polylinien</li>
<li>Unterstützung für Editierfunktionen über <code>editMode</code>
(localStorage)</li>
</ul>
<hr />
<h2 id="hauptbestandteile">🧱 Hauptbestandteile</h2>
<ul>
<li><code>useEffect</code>-Hooks zum Laden und Aktualisieren von:
<ul>
<li>Kartenlayern, POIs, Linien, Rechte, Systeme, Positionen</li>
</ul></li>
<li>Marker-Logik für 15+ Layergruppen (TALAS, ECI, GMA, etc.)</li>
<li>Marker-Overlapping mit <code>OverlappingMarkerSpiderfier</code></li>
<li>Kontextmenüs (Karte &amp; Polylinie)</li>
<li>UI-Komponenten:
<ul>
<li><code>MapLayersControlPanel</code></li>
<li><code>CoordinateInput</code></li>
<li><code>CoordinatePopup</code></li>
<li><code>AddPOIModal</code>, <code>PoiUpdateModal</code>,
<code>VersionInfoModal</code></li>
</ul></li>
</ul>
<hr />
<h2 id="zustand-redux">🧠 Zustand &amp; Redux</h2>
<p>Verwendet umfangreiche Redux-Slices zur Steuerung von:</p>
<ul>
<li><p>Linienstatus, POI-Typen, POI-Icons</p></li>
<li><p>Gerätesysteme &amp; Rechte</p></li>
<li><p>Sichtbarkeit einzelner Layergruppen</p></li>
<li><p>Aktuelle Selektion (Area, Gerät, POI)</p>
<figure>
<img src="../../screenshots/ReaduxSlices.png" alt="ReduxSlices" />
<figcaption aria-hidden="true">ReduxSlices</figcaption>
</figure></li>
</ul>
<hr />
<h2 id="lokale-steuerung">🔧 Lokale Steuerung</h2>
<ul>
<li><p>EditMode wird aus <code>localStorage</code> gelesen</p></li>
<li><p>Karte speichert Zoom &amp; Center dauerhaft im Browser</p></li>
<li><p>Kontextmenü-Einträge ändern sich je nach Rechten &amp; Modus</p>
<figure>
<img src="../../screenshots/LocalStorage.png" alt="LocalStorage" />
<figcaption aria-hidden="true">LocalStorage</figcaption>
</figure></li>
</ul>
<hr />
<h2 id="besonderheiten">🧪 Besonderheiten</h2>
<ul>
<li>Fehlerbehandlung für <code>contextmenu</code>-Fehler eingebaut →
Auto-Neuladen</li>
<li>Alle Marker-Updates mit Overlapping-Check &amp;
Z-Index-Steuerung</li>
<li>Linien enthalten dynamische Tooltips mit
<code>tooltipContents</code></li>
<li>Initiale Datenabfrage über Redux-Thunk-Kaskade</li>
</ul>
<hr />
<h2 id="abhängigkeiten">🔗 Abhängigkeiten</h2>
<ul>
<li>Leaflet, OverlappingMarkerSpiderfier, React-Toastify</li>
<li>Redux Toolkit (Thunks + Selectors)</li>
<li>Tailwind CSS für visuelles Layout</li>
</ul>
<hr />
<p>📄 Pfad: <code>/components/mainComponent/MapComponent.js</code></p>
<hr />
<p><a href="../../README.md">Zurück zur Übersicht</a></p>

View File

@@ -0,0 +1,13 @@
<h1 id="übersicht-docscomponentsmaincomponent">📄 Übersicht:
docs/components/mainComponent</h1>
<p>Die zentrale React-Komponente zur Darstellung und Steuerung der
Leaflet-Karte.<br />
Bindet alle Marker, Layer, POIs, Linien und das Kontextmenü dynamisch
ein.</p>
<ul>
<li><a href="MapComponent.md">MapComponent</a></li>
</ul>
<figure>
<img src="../../screenshots/overview1.png" alt="Overview" />
<figcaption aria-hidden="true">Overview</figcaption>
</figure>

View File

@@ -0,0 +1,5 @@
<h1 id="übersicht-docscomponentsmaincomponenthooks">📄 Übersicht:
docs/components/mainComponent/hooks</h1>
<ul>
<li><a href="useInitializeMap.md">useInitializeMap</a></li>
</ul>

View File

@@ -0,0 +1,90 @@
<!-- /docs/components/mainComponent/hooks/useInitializeMap.md -->
<h1 id="useinitializemap.js">🪄 useInitializeMap.js</h1>
<p>Custom React-Hook zur Initialisierung der Leaflet-Karte.<br />
Ermöglicht die einfache Übergabe aller nötigen Parameter und abstrahiert
die <code>initializeMap(...)</code>-Logik.</p>
<hr />
<h2 id="zweck">📦 Zweck</h2>
<ul>
<li>Führt <code>initializeMap(...)</code> nur <strong>einmal</strong>
aus, wenn <code>mapRef</code> existiert und
<code>map === null</code></li>
<li>Kapselt die Initialisierung in ein <code>useEffect</code></li>
</ul>
<hr />
<h2 id="parameter">🔧 Parameter</h2>
<table>
<colgroup>
<col style="width: 26%" />
<col style="width: 21%" />
<col style="width: 51%" />
</colgroup>
<thead>
<tr>
<th>Name</th>
<th>Typ</th>
<th>Beschreibung</th>
</tr>
</thead>
<tbody>
<tr>
<td><code>map</code></td>
<td><code>LeafletMap</code> (Zustand)</td>
<td>Wird initialisiert, wenn <code>null</code></td>
</tr>
<tr>
<td><code>mapRef</code></td>
<td><code>ref</code></td>
<td>Referenz auf <code>&lt;div id="map"&gt;</code></td>
</tr>
<tr>
<td><code>setMap</code></td>
<td><code>function</code></td>
<td>Callback zum Setzen der Karteninstanz</td>
</tr>
<tr>
<td><code>setOms</code></td>
<td><code>function</code></td>
<td>Callback für OverlappingMarkerSpiderfier</td>
</tr>
<tr>
<td><code>setMenuItemAdded</code></td>
<td><code>function</code></td>
<td>Wird genutzt, um mehrfaches Menü-Setup zu verhindern</td>
</tr>
<tr>
<td><code>addItemsToMapContextMenu</code></td>
<td><code>function</code></td>
<td>Logik zum Hinzufügen von Kontextmenüeinträgen</td>
</tr>
<tr>
<td><code>hasRights</code></td>
<td><code>boolean</code></td>
<td>Steuerung, ob POI-Menüs angezeigt werden dürfen</td>
</tr>
<tr>
<td><code>setPolylineEventsDisabled</code></td>
<td><code>function</code></td>
<td>Aktiviert/Deaktiviert Polyline-Events global</td>
</tr>
</tbody>
</table>
<hr />
<h2 id="verwendung">🌐 Verwendung</h2>
<p>In <code>MapComponent.js</code>:</p>
<div class="sourceCode" id="cb1"><pre class="sourceCode js"><code class="sourceCode javascript"><span id="cb1-1"><a href="#cb1-1" aria-hidden="true" tabindex="-1"></a><span class="fu">useInitializeMap</span>(</span>
<span id="cb1-2"><a href="#cb1-2" aria-hidden="true" tabindex="-1"></a> map<span class="op">,</span></span>
<span id="cb1-3"><a href="#cb1-3" aria-hidden="true" tabindex="-1"></a> mapRef<span class="op">,</span></span>
<span id="cb1-4"><a href="#cb1-4" aria-hidden="true" tabindex="-1"></a> setMap<span class="op">,</span></span>
<span id="cb1-5"><a href="#cb1-5" aria-hidden="true" tabindex="-1"></a> setOms<span class="op">,</span></span>
<span id="cb1-6"><a href="#cb1-6" aria-hidden="true" tabindex="-1"></a> setMenuItemAdded<span class="op">,</span></span>
<span id="cb1-7"><a href="#cb1-7" aria-hidden="true" tabindex="-1"></a> addItemsToMapContextMenu<span class="op">,</span></span>
<span id="cb1-8"><a href="#cb1-8" aria-hidden="true" tabindex="-1"></a> hasRights<span class="op">,</span></span>
<span id="cb1-9"><a href="#cb1-9" aria-hidden="true" tabindex="-1"></a> value <span class="kw">=&gt;</span> <span class="fu">dispatch</span>(<span class="fu">setDisabled</span>(value))</span>
<span id="cb1-10"><a href="#cb1-10" aria-hidden="true" tabindex="-1"></a>)<span class="op">;</span></span></code></pre></div>
<hr />
<h2 id="quelle">📁 Quelle</h2>
<p>Wrappt <code>initializeMap()</code> aus
<code>/utils/initializeMap.js</code></p>
<hr />
<p><a href="../../../README.md">Zurück zur Übersicht</a></p>

View File

@@ -0,0 +1,59 @@
<!-- /docs/components/pois/AddPOIModal.md -->
<h1 id="addpoimodal.js"> AddPOIModal.js</h1>
<p>Zeigt ein modales Formular an, um einen neuen POI auf der Karte zu
erstellen.<br />
Die Koordinaten (<code>latlng</code>) werden automatisch übernommen.</p>
<figure>
<img src="../../screenshots/AddPOIModal.png"
alt="POI hinzufügen Modal" />
<figcaption aria-hidden="true">POI hinzufügen Modal</figcaption>
</figure>
<h2 id="funktionen">Funktionen</h2>
<ul>
<li>POI-Name, Typ und zugehöriges Gerät auswählbar</li>
<li>Koordinatenanzeige (<code>lat</code>, <code>lng</code>)</li>
<li>Dynamisches Laden der Gerätedaten und POI-Typen</li>
<li>Fehleranzeige bei fehlgeschlagenem Speichern</li>
<li>Löst <code>addPoiThunk</code> + Refresh-Trigger
(<code>incrementTrigger</code>) aus</li>
</ul>
<h2 id="props">Props</h2>
<table>
<colgroup>
<col style="width: 14%" />
<col style="width: 15%" />
<col style="width: 70%" />
</colgroup>
<thead>
<tr>
<th>Prop</th>
<th>Typ</th>
<th>Beschreibung</th>
</tr>
</thead>
<tbody>
<tr>
<td><code>onClose</code></td>
<td><code>function</code></td>
<td>Schließt das Modal</td>
</tr>
<tr>
<td><code>map</code></td>
<td><code>Leaflet</code></td>
<td>(optional) zum Schließen evtl. offener Popups</td>
</tr>
<tr>
<td><code>latlng</code></td>
<td><code>object</code></td>
<td>Koordinaten für den neuen POI</td>
</tr>
</tbody>
</table>
<h2 id="redux">Redux</h2>
<ul>
<li><code>fetchPoiTypThunk</code>,
<code>fetchPoiIconsDataThunk</code></li>
<li><code>addPoiThunk</code>, <code>resetAddPoiStatus</code></li>
</ul>
<hr />
<p><a href="../../README.md">Zurück zur Übersicht</a></p>

View File

@@ -0,0 +1,51 @@
<!-- /docs/components/pois/PoiUpdateModal.md -->
<h1 id="poiupdatemodal.js">✏️ PoiUpdateModal.js</h1>
<p>Ein Dialog zur Aktualisierung oder Löschung bestehender POIs.</p>
<figure>
<img src="../../screenshots/PoiUpdateModal.png"
alt="POI Update Modal" />
<figcaption aria-hidden="true">POI Update Modal</figcaption>
</figure>
<h2 id="features">Features</h2>
<ul>
<li>Zeigt aktuellen Namen, Beschreibung, Gerät und Typ</li>
<li>Gerät und Typ auswählbar via <code>react-select</code></li>
<li>Unterstützt Löschen und Speichern von POIs</li>
<li>Eingebundene Sicherheitsabfrage bei Löschen</li>
</ul>
<h2 id="props">Props</h2>
<table>
<thead>
<tr>
<th>Prop</th>
<th>Typ</th>
<th>Beschreibung</th>
</tr>
</thead>
<tbody>
<tr>
<td><code>onClose</code></td>
<td><code>function</code></td>
<td>Schließt das Modal</td>
</tr>
<tr>
<td><code>poiData</code></td>
<td><code>object</code></td>
<td>Bestehende POI-Daten zur Bearbeitung</td>
</tr>
</tbody>
</table>
<h2 id="redux">Redux</h2>
<ul>
<li><code>updatePoiThunk</code>, <code>deletePoiThunk</code></li>
<li><code>fetchLocationDevicesThunk</code>,
<code>fetchPoiTypThunk</code></li>
</ul>
<h2 id="technisches">Technisches</h2>
<ul>
<li>Dynamische Gerätegruppenfilterung basierend auf
<code>mapLayersVisibility</code></li>
<li>Formfelder mit <code>react-select</code> für bessere UX</li>
</ul>
<hr />
<p><a href="../../README.md">Zurück zur Übersicht</a></p>

View File

@@ -0,0 +1,10 @@
<h1 id="übersicht-docscomponentspois">📄 Übersicht:
docs/components/pois</h1>
<ul>
<li><a href="AddPOIModal.md">AddPOIModal</a></li>
<li><a href="PoiUpdateModal.md">PoiUpdateModal</a></li>
</ul>
<figure>
<img src="../../screenshots/POIs.png" alt="POIs" />
<figcaption aria-hidden="true">POIs</figcaption>
</figure>

View File

@@ -0,0 +1,154 @@
<!-- /docs/components/uiWidgets/CoordinateInput.md -->
<h1 id="coordinateinput.js">CoordinateInput.js</h1>
<p>Die Komponente <code>CoordinateInput</code> stellt ein einfaches
Eingabefeld für geografische Koordinaten (Latitude, Longitude)
bereit.<br />
Sie dient typischerweise dazu, einen bestimmten Punkt auf der Karte zu
fokussieren bzw. zu markieren.</p>
<hr />
<figure>
<img src="../../screenshots/CoordinateInput.png"
alt="CoordinateInput" />
<figcaption aria-hidden="true">CoordinateInput</figcaption>
</figure>
<hr />
<h2 id="pfad">🔧 Pfad</h2>
<div class="sourceCode" id="cb1"><pre
class="sourceCode bash"><code class="sourceCode bash"><span id="cb1-1"><a href="#cb1-1" aria-hidden="true" tabindex="-1"></a><span class="ex">/components/uiWidgets/CoordinateInput.js</span></span></code></pre></div>
<hr />
<h2 id="zweck">🎯 Zweck</h2>
<ul>
<li>Eingabe von Koordinaten (z.B. <code>53.2,8.1</code>)</li>
<li>Übergabe dieser Koordinaten an eine Callback-Funktion zur weiteren
Verarbeitung</li>
<li>Positioniert sich dauerhaft in der linken oberen Ecke der Seite
(z.B. zur schnellen Navigation)</li>
</ul>
<hr />
<h2 id="props">⚙️ Props</h2>
<table>
<colgroup>
<col style="width: 18%" />
<col style="width: 8%" />
<col style="width: 73%" />
</colgroup>
<thead>
<tr>
<th>Prop</th>
<th>Typ</th>
<th>Beschreibung</th>
</tr>
</thead>
<tbody>
<tr>
<td><code>onCoordinatesSubmit</code></td>
<td><code>function</code></td>
<td>Wird beim Abschicken des Formulars mit dem eingegebenen
Koordinaten-String aufgerufen</td>
</tr>
</tbody>
</table>
<hr />
<h2 id="interne-logik">🧩 Interne Logik</h2>
<div class="sourceCode" id="cb2"><pre class="sourceCode js"><code class="sourceCode javascript"><span id="cb2-1"><a href="#cb2-1" aria-hidden="true" tabindex="-1"></a><span class="kw">const</span> [coordinates<span class="op">,</span> setCoordinates] <span class="op">=</span> <span class="fu">useState</span>(<span class="st">&quot;&quot;</span>)<span class="op">;</span></span></code></pre></div>
<ul>
<li>Der Eingabewert wird im lokalen State gespeichert</li>
<li>Beim Submit (<code>onSubmit</code>) wird
<code>onCoordinatesSubmit(coordinates)</code> aufgerufen, wenn
gesetzt</li>
</ul>
<hr />
<h2 id="ui-aufbau">🧰 UI-Aufbau</h2>
<ul>
<li>Eingabefeld für Text: Erwartet <code>lat,lng</code></li>
<li>Button: „Zu Marker zoomen“</li>
<li>Position: <code>fixed top-5 left-5</code> → dauerhaft sichtbar</li>
</ul>
<hr />
<h2 id="gestaltung-tailwind-css">🎨 Gestaltung (Tailwind CSS)</h2>
<table>
<colgroup>
<col style="width: 12%" />
<col style="width: 87%" />
</colgroup>
<thead>
<tr>
<th>Element</th>
<th>Klassen</th>
</tr>
</thead>
<tbody>
<tr>
<td>Container</td>
<td><code>fixed top-5 left-5 z-50 bg-white shadow-lg rounded-lg p-4 w-72</code></td>
</tr>
<tr>
<td>Input</td>
<td><code>border p-2 rounded w-full mb-2</code></td>
</tr>
<tr>
<td>Button</td>
<td><code>bg-blue-500 text-white p-2 rounded w-full hover:bg-blue-600</code></td>
</tr>
</tbody>
</table>
<hr />
<h2 id="testfälle">🧪 Testfälle</h2>
<table>
<colgroup>
<col style="width: 31%" />
<col style="width: 68%" />
</colgroup>
<thead>
<tr>
<th>Eingabe</th>
<th>Erwartung</th>
</tr>
</thead>
<tbody>
<tr>
<td><code>53.2,8.1</code></td>
<td>Callback <code>onCoordinatesSubmit("53.2,8.1")</code> wird
ausgelöst</td>
</tr>
<tr>
<td>Leer</td>
<td>Callback wird ausgelöst mit leerem String</td>
</tr>
<tr>
<td>Buttonklick</td>
<td>Löst <code>handleSubmit()</code> aus</td>
</tr>
<tr>
<td>Enter-Taste im Eingabefeld</td>
<td>Löst ebenfalls Submit aus</td>
</tr>
</tbody>
</table>
<hr />
<h2 id="erweiterungsideen">💡 Erweiterungsideen</h2>
<ul>
<li>Validierung des Formats (<code>lat,lng</code>) vor dem Absenden</li>
<li>Automatisches Zentrieren der Leaflet-Karte in der
Callback-Funktion</li>
<li>Optionale Markierung des Punkts auf der Karte</li>
</ul>
<hr />
<h2 id="verwendung">📄 Verwendung</h2>
<p>Beispiel in einer Map-Komponente:</p>
<div class="sourceCode" id="cb3"><pre
class="sourceCode jsx"><code class="sourceCode javascriptreact"><span id="cb3-1"><a href="#cb3-1" aria-hidden="true" tabindex="-1"></a><span class="fu">&lt;CoordinateInput</span></span>
<span id="cb3-2"><a href="#cb3-2" aria-hidden="true" tabindex="-1"></a> <span class="ot">onCoordinatesSubmit</span><span class="op">=</span><span class="va">{</span>coords <span class="kw">=&gt;</span> {</span>
<span id="cb3-3"><a href="#cb3-3" aria-hidden="true" tabindex="-1"></a> <span class="kw">const</span> [lat<span class="op">,</span> lng] <span class="op">=</span> coords<span class="op">.</span><span class="fu">split</span>(<span class="st">&quot;,&quot;</span>)<span class="op">.</span><span class="fu">map</span>(<span class="bu">Number</span>)<span class="op">;</span></span>
<span id="cb3-4"><a href="#cb3-4" aria-hidden="true" tabindex="-1"></a> map<span class="op">.</span><span class="fu">setView</span>([lat<span class="op">,</span> lng]<span class="op">,</span> <span class="dv">16</span>)<span class="op">;</span> <span class="co">// Leaflet</span></span>
<span id="cb3-5"><a href="#cb3-5" aria-hidden="true" tabindex="-1"></a> }<span class="va">}</span></span>
<span id="cb3-6"><a href="#cb3-6" aria-hidden="true" tabindex="-1"></a><span class="fu">/&gt;</span></span></code></pre></div>
<hr />
<h2 id="verwandte-komponenten">📦 Verwandte Komponenten</h2>
<ul>
<li><code>MapComponent.js</code> kann die übergebenen Koordinaten zur
Zentrierung oder Marker-Erstellung nutzen</li>
</ul>
<hr />
<hr />
<p><a href="../../README.md">Zurück zur Übersicht</a></p>

View File

@@ -0,0 +1,11 @@
<h1 id="ui-widgets">📄 UI-Widgets</h1>
<ul>
<li><p><a
href="mapLayersControlPanel/mapLayersControlPanel.md">mapLayersControlPanel</a></p></li>
<li><p><a href="CoordinateInput.md">CoordinateInput</a></p></li>
<li><p><a href="VersionInfoModal.md">VersionInfoModal</a></p>
<figure>
<img src="../../screenshots/uiWidgets.png" alt="uiWidgets.png" />
<figcaption aria-hidden="true">uiWidgets.png</figcaption>
</figure></li>
</ul>

View File

@@ -0,0 +1,131 @@
<!-- /docs/components/uiWidgets/VersionInfoModal.md -->
<h1 id="versioninfomodal.js">🪪 VersionInfoModal.js</h1>
<p>Das <code>VersionInfoModal</code> ist ein modales Fenster zur Anzeige
von Unternehmensinformationen und der aktuellen App-Version.<br />
Es wird meist im Footer oder als Info-Schaltfläche in der
Benutzeroberfläche eingeblendet.</p>
<hr />
<figure>
<img src="../../screenshots/VersionInfoModal.png"
alt="VersionInfoModal" />
<figcaption aria-hidden="true">VersionInfoModal</figcaption>
</figure>
<hr />
<h2 id="pfad">🔧 Pfad</h2>
<div class="sourceCode" id="cb1"><pre
class="sourceCode bash"><code class="sourceCode bash"><span id="cb1-1"><a href="#cb1-1" aria-hidden="true" tabindex="-1"></a><span class="ex">/components/uiWidgets/VersionInfoModal.js</span></span></code></pre></div>
<hr />
<h2 id="zweck">🎯 Zweck</h2>
<p>Die Komponente informiert Nutzer über:</p>
<ul>
<li>Die <strong>aktuelle TALAS.Map Version</strong></li>
<li>Die <strong>Firmenadresse und Kontaktdaten</strong> der Littwin
Systemtechnik GmbH &amp; Co. KG</li>
<li>Eine zentral platzierte Grafik mit dem TALAS-Logo</li>
<li>Eine Schaltfläche zum Schließen des Modals<br />
<img src="../../screenshots/VersionInfoModal2.png"
alt="VersionInfoModal" /></li>
</ul>
<hr />
<h2 id="props">⚙️ Props</h2>
<table>
<colgroup>
<col style="width: 18%" />
<col style="width: 8%" />
<col style="width: 73%" />
</colgroup>
<thead>
<tr>
<th>Prop</th>
<th>Typ</th>
<th>Beschreibung</th>
</tr>
</thead>
<tbody>
<tr>
<td><code>showVersionInfoModal</code></td>
<td><code>boolean</code></td>
<td>Steuert, ob das Modal angezeigt wird</td>
</tr>
<tr>
<td><code>closeVersionInfoModal</code></td>
<td><code>function</code></td>
<td>Callback zum Schließen des Modals</td>
</tr>
<tr>
<td><code>APP_VERSION</code></td>
<td><code>string</code></td>
<td>Versionstext (z.B. <code>1.1.188</code>), meist aus
<code>.env.production</code> oder <code>.env.development</code>
geladen</td>
</tr>
</tbody>
</table>
<hr />
<h2 id="verhalten">💡 Verhalten</h2>
<ul>
<li>Wird <code>showVersionInfoModal</code> auf <code>true</code>
gesetzt, erscheint das Modal zentriert über einem halbtransparenten
Overlay</li>
<li>Klick auf den Hintergrund (schwarzes Overlay) oder auf „Schließen“
führt <code>closeVersionInfoModal()</code> aus</li>
</ul>
<hr />
<h2 id="gestaltung">🎨 Gestaltung</h2>
<ul>
<li>Modal-Layout mit Tailwind CSS (<code>fixed</code>,
<code>z-50</code>, <code>bg-white</code>, <code>rounded</code>,
<code>shadow</code>)</li>
<li>Schaltfläche <code>Schließen</code> reagiert auf Hover mit
Farbwechsel (<code>hover:bg-blue-700</code>)</li>
<li>Design folgt der UI-Ästhetik von TALAS.web</li>
</ul>
<hr />
<h2 id="testfälle">🧪 Testfälle</h2>
<table>
<colgroup>
<col style="width: 43%" />
<col style="width: 56%" />
</colgroup>
<thead>
<tr>
<th>Bedingung</th>
<th>Erwartung</th>
</tr>
</thead>
<tbody>
<tr>
<td><code>showVersionInfoModal = true</code></td>
<td>Modal wird angezeigt</td>
</tr>
<tr>
<td>Klick auf Hintergrund</td>
<td>Modal wird geschlossen</td>
</tr>
<tr>
<td>Klick auf „Schließen“-Button</td>
<td>Modal wird geschlossen</td>
</tr>
<tr>
<td>Version <code>APP_VERSION = 1.1.290</code></td>
<td>Text „TALAS.Map Version 1.1.290“ sichtbar</td>
</tr>
</tbody>
</table>
<hr />
<h2 id="verknüpfte-dateien">📦 Verknüpfte Dateien</h2>
<ul>
<li><code>.env.production</code> oder <code>.env.development</code>
enthält z.B. <code>NEXT_PUBLIC_APP_VERSION=1.1.188</code></li>
<li>Aufruf in <code>Footer</code> oder <code>Layout</code> zur Anzeige
bei Klick auf „Version“</li>
</ul>
<hr />
<h2 id="verbesserungsideen">🛠 Verbesserungsideen</h2>
<ul>
<li>ESC-Taste als Schließen-Funktion ergänzen</li>
<li>Automatischer Import von Version via
<code>process.env.NEXT_PUBLIC_APP_VERSION</code></li>
</ul>
<hr />
<p><a href="../../README.md">Zurück zur Übersicht</a></p>

View File

@@ -0,0 +1,109 @@
<!-- /docs/components/uiWidgets/mapLayersControlPanel/EditModeToggle.md -->
<h1 id="editmodetoggle.js">✏️ EditModeToggle.js</h1>
<p>Die Komponente <code>EditModeToggle</code> stellt einen interaktiven
Umschalter für den Bearbeitungsmodus bereit.<br />
Sie ermöglicht das Ein- und Ausschalten des Modus, in dem POIs,
Polylines (Strecken) und Bereiche bearbeitet werden können.</p>
<hr />
<h2 id="pfad">📦 Pfad</h2>
<div class="sourceCode" id="cb1"><pre
class="sourceCode bash"><code class="sourceCode bash"><span id="cb1-1"><a href="#cb1-1" aria-hidden="true" tabindex="-1"></a><span class="ex">/components/uiWidgets/mapLayersControlPanel/EditModeToggle.js</span></span></code></pre></div>
<hr />
<h2 id="zweck">🧩 Zweck</h2>
<p>Der Bearbeitungsmodus wirkt sich auf die Interaktivität der Map
aus:</p>
<ul>
<li>Wenn <strong>aktiv</strong>:
<ul>
<li>Checkboxen für Layer sind deaktiviert</li>
<li>POI-Funktionen (Hinzufügen, Verschieben, Löschen) werden
ermöglicht</li>
</ul></li>
<li>Wenn <strong>inaktiv</strong>:
<ul>
<li>Keine Bearbeitung möglich</li>
<li>UI ist auf Betrachtung beschränkt</li>
</ul></li>
</ul>
<hr />
<h2 id="verhalten">🖱 Verhalten</h2>
<p>Beim Klick auf das Icon:</p>
<ol type="1">
<li>Wird der lokale Zustand <code>editMode</code> umgeschaltet</li>
<li><code>localStorage</code> speichert den neuen Status
(<code>true</code> oder <code>false</code>)</li>
<li>Die Seite wird neu geladen (<code>window.location.reload()</code>),
um globale Effekte zu aktivieren</li>
</ol>
<hr />
<h2 id="interner-zustand">🧠 Interner Zustand</h2>
<div class="sourceCode" id="cb2"><pre class="sourceCode js"><code class="sourceCode javascript"><span id="cb2-1"><a href="#cb2-1" aria-hidden="true" tabindex="-1"></a><span class="kw">const</span> [editMode<span class="op">,</span> setEditMode] <span class="op">=</span> <span class="fu">useState</span>(() <span class="kw">=&gt;</span> localStorage<span class="op">.</span><span class="fu">getItem</span>(<span class="st">&quot;editMode&quot;</span>) <span class="op">===</span> <span class="st">&quot;true&quot;</span>)<span class="op">;</span></span></code></pre></div>
<ul>
<li>Initialisiert aus <code>localStorage</code></li>
<li>Persistente Speicherung des Zustands browserseitig</li>
<li>Aufruf in anderen Komponenten (z.B.
<code>MapLayersControlPanel.js</code>) basiert ebenfalls auf diesem
Wert</li>
</ul>
<hr />
<h2 id="ui-darstellung">🧰 UI-Darstellung</h2>
<ul>
<li>Verwendet <strong>Material-UI-Icons</strong>:
<ul>
<li>🟢 <code>ModeEditIcon</code>: Bearbeitungsmodus <strong>aus</strong>
→ wird angeboten zum <strong>Aktivieren</strong></li>
<li>🔴 <code>EditOffIcon</code>: Bearbeitungsmodus <strong>ein</strong>
→ wird angeboten zum <strong>Deaktivieren</strong></li>
</ul></li>
<li>Tooltip informiert den Nutzer über die jeweilige Aktion</li>
</ul>
<hr />
<h2 id="testfälle">🧪 Testfälle</h2>
<table>
<colgroup>
<col style="width: 25%" />
<col style="width: 75%" />
</colgroup>
<thead>
<tr>
<th>Zustand</th>
<th>Erwartetes Verhalten</th>
</tr>
</thead>
<tbody>
<tr>
<td><code>editMode = false</code></td>
<td>Icon: ✏️ → Tooltip: „Bearbeitungsmodus aktivieren“</td>
</tr>
<tr>
<td><code>editMode = true</code></td>
<td>Icon: 🚫✏️ → Tooltip: „Bearbeitungsmodus deaktivieren“</td>
</tr>
<tr>
<td>Klick auf Icon</td>
<td>Status umschalten, Seite neu laden</td>
</tr>
</tbody>
</table>
<hr />
<h2 id="erweiterungsideen">💡 Erweiterungsideen</h2>
<ul>
<li>🔄 Statt <code>window.location.reload()</code> → globalen Zustand
über Redux-Dispatch steuern</li>
<li>📢 Feedback-Toast nach Umschalten anzeigen (z.B. „Bearbeitungsmodus
aktiviert“)</li>
<li>🧩 Integration in Redux-Store zur globalen Synchronisierung ohne
Reload</li>
</ul>
<hr />
<h2 id="verwandte-komponenten">📄 Verwandte Komponenten</h2>
<ul>
<li><code>MapLayersControlPanel.js</code>: liest
<code>localStorage.editMode</code> und deaktiviert Layer-Checkboxen im
aktiven Modus</li>
<li><code>PoiUpdateModal</code>, <code>AddPOIModal</code>: nutzen den
Bearbeitungsmodus für UI-Freigabe</li>
</ul>
<hr />
<hr />
<p><a href="../../../README.md">Zurück zur Übersicht</a></p>

View File

@@ -0,0 +1,213 @@
<!-- /docs/components/uiWidgets/mapLayersControlPanel/MapLayersControlPanel.md -->
<h1 id="maplayerscontrolpanel.js">🧭 MapLayersControlPanel.js</h1>
<p>Dieses UI-Widget zeigt eine interaktive Steuereinheit für Layer, POIs
und Stationsbereiche auf der rechten Seite der Karte.<br />
Es ist vollständig an den Redux-Store angebunden und reagiert auf
Änderungen der Layer-Sichtbarkeit, Bearbeitungsmodus und
Stationsauswahl.</p>
<hr />
<h2 id="ui-struktur">🧩 UI-Struktur</h2>
<figure>
<img src="../../../screenshots/MapLayersControlPanel.png"
alt="Map layers controll panel" />
<figcaption aria-hidden="true">Map layers controll panel</figcaption>
</figure>
<hr />
<h2 id="pfad">🔧 Pfad</h2>
<div class="sourceCode" id="cb1"><pre
class="sourceCode bash"><code class="sourceCode bash"><span id="cb1-1"><a href="#cb1-1" aria-hidden="true" tabindex="-1"></a><span class="ex">/components/uiWidgets/mapLayersControlPanel/MapLayersControlPanel.js</span></span></code></pre></div>
<hr />
<h2 id="zweck">📌 Zweck</h2>
<p>Das <code>MapLayersControlPanel</code> ermöglicht Nutzern:</p>
<ul>
<li>Die Auswahl eines Stationsbereichs (Dropdown)</li>
<li>Das Aktivieren/Deaktivieren einzelner GIS-Systeme (Checkboxen)</li>
<li>Das Anzeigen von POIs oder Kabelstrecken (TALAS-spezifisch)</li>
<li>Das Ein-/Ausschalten des Bearbeitungsmodus</li>
<li>Die Steuerung der Karten-Zentrierung über ein Icon</li>
</ul>
<hr />
<h2 id="verwendete-redux-slices">🧠 Verwendete Redux-Slices</h2>
<table>
<colgroup>
<col style="width: 35%" />
<col style="width: 64%" />
</colgroup>
<thead>
<tr>
<th>Slice</th>
<th>Zweck</th>
</tr>
</thead>
<tbody>
<tr>
<td><code>gisStationsStaticDistrictSlice</code></td>
<td>Enthält die Gerätebereiche (mit <code>.Points</code>)</td>
</tr>
<tr>
<td><code>gisSystemStaticSlice</code></td>
<td>Enthält die konfigurierten GIS-Systeme mit Anzeigeerlaubnis</td>
</tr>
<tr>
<td><code>mapLayersSlice</code></td>
<td>Speichert die Sichtbarkeit aller Layer</td>
</tr>
<tr>
<td><code>poiLayerVisibleSlice</code></td>
<td>Steuert Sichtbarkeit der POIs</td>
</tr>
<tr>
<td><code>polylineLayerVisibleSlice</code></td>
<td>Steuert Sichtbarkeit der Kabelstrecken (TALAS)</td>
</tr>
<tr>
<td><code>zoomTriggerSlice</code></td>
<td>Löst Neuzentrierung der Karte aus</td>
</tr>
<tr>
<td><code>selectedAreaSlice</code></td>
<td>Speichert den gewählten Bereich/Station</td>
</tr>
</tbody>
</table>
<hr />
<h2 id="logikübersicht">🔄 Logikübersicht</h2>
<ul>
<li><p><strong>Dropdown Stationsauswahl:</strong><br />
Wird dynamisch aus <code>GisStationsStaticDistrict.Points</code>
befüllt<br />
Nur eindeutige <code>Area_Name</code>, wenn <code>System</code> erlaubt
ist</p></li>
<li><p><strong>Checkboxen für Layer:</strong><br />
Zeigen alle Systeme aus <code>GisSystemStatic</code>, bei denen
<code>Allow === 1</code><br />
Sonderfall: <code>TALAS</code> erhält ein Untermenü für
„Kabelstrecken“</p></li>
<li><p><strong>Lokale Speicherung:</strong><br />
Sichtbarkeiten, Bearbeitungsmodus und POI-Zustand werden in
<code>localStorage</code> geschrieben und bei Initialisierung
geladen</p></li>
<li><p><strong>Bearbeitungsmodus:</strong><br />
Wenn aktiv (<code>editMode === true</code>), sind Layer-Checkboxen
deaktiviert</p></li>
</ul>
<hr />
<h2 id="wichtige-funktionen">📥 Wichtige Funktionen</h2>
<table>
<colgroup>
<col style="width: 44%" />
<col style="width: 55%" />
</colgroup>
<thead>
<tr>
<th>Funktion</th>
<th>Zweck</th>
</tr>
</thead>
<tbody>
<tr>
<td><code>handleAreaChange()</code></td>
<td>Setzt <code>selectedArea</code> im Redux Store</td>
</tr>
<tr>
<td><code>handleCheckboxChange()</code></td>
<td>Schaltet Sichtbarkeit einzelner Layer</td>
</tr>
<tr>
<td><code>handlePolylineCheckboxChange()</code></td>
<td>Aktiviert Sichtbarkeit von Kabelstrecken</td>
</tr>
<tr>
<td><code>handlePoiCheckboxChange()</code></td>
<td>Aktiviert Sichtbarkeit von POIs</td>
</tr>
<tr>
<td><code>handleIconClick()</code></td>
<td>Setzt Station zurück und triggert Zoom</td>
</tr>
</tbody>
</table>
<hr />
<h2 id="debug-hinweise">🐞 Debug-Hinweise</h2>
<ul>
<li><p>Debug-Logs:<br />
<code>__debug.gisStations</code><br />
werden ausgegeben, um sicherzustellen, dass Daten korrekt geladen
wurden</p></li>
<li><p>Warnungen:<br />
Falls <code>.Points</code> nicht vorhanden ist, wird dies in der Konsole
gewarnt</p></li>
</ul>
<hr />
<h2 id="todos-erweiterungsideen">🛠 ToDos / Erweiterungsideen</h2>
<ul>
<li>Checkboxen für Bereiche („Bereiche“, „Standorte“) sind bereits
vorbereitet, aber auskommentiert</li>
<li>Möglichkeit, Tooltips zu aktivieren/deaktivieren?</li>
<li>Gruppierung von Layern nach Typ (z.B. Linien, Geräte, POIs)</li>
</ul>
<hr />
<h2 id="verwendete-komponenten">📄 Verwendete Komponenten</h2>
<ul>
<li><code>MapLayersControlPanel</code></li>
<li><code>EditModeToggle</code> Schaltfläche für Umschalten des
Bearbeitungsmodus</li>
</ul>
<hr />
<h2 id="zustand-lokal-global">✅ Zustand: Lokal &amp; Global</h2>
<ul>
<li><strong>Global:</strong> <code>useSelector(...)</code> aus
Redux</li>
<li><strong>Lokal:</strong> <code>useState(...)</code> für editMode,
stationListing, systemListing</li>
</ul>
<hr />
<h2 id="lokalestorage-keys">📦 LokaleStorage-Keys</h2>
<table>
<thead>
<tr>
<th>Key</th>
<th>Beschreibung</th>
</tr>
</thead>
<tbody>
<tr>
<td><code>poiVisible</code></td>
<td>Sichtbarkeit der POI-Marker</td>
</tr>
<tr>
<td><code>polylineVisible</code></td>
<td>Sichtbarkeit der Kabelstrecken</td>
</tr>
<tr>
<td><code>mapLayersVisibility</code></td>
<td>Sichtbarkeiten der einzelnen Systeme</td>
</tr>
<tr>
<td><code>editMode</code></td>
<td>Zustand des Bearbeitungsmodus (true/false)</td>
</tr>
</tbody>
</table>
<hr />
<h2 id="testempfehlung">🧪 Testempfehlung</h2>
<ul>
<li>Dropdown zeigt erwartete <code>Area_Name</code>-Werte?</li>
<li>Layer-Checkboxen werden korrekt gespeichert?</li>
<li>Bei <code>TALAS</code> erscheint zusätzlich: „Kabelstrecken“?</li>
<li>Bei Wechsel der Station wird <code>setSelectedArea</code>
ausgelöst?</li>
</ul>
<hr />
<h2 id="verknüpfte-dateien">🧩 Verknüpfte Dateien</h2>
<ul>
<li><code>redux/slices/webservice/gisStationsStaticDistrictSlice.js</code></li>
<li><code>redux/slices/webservice/gisSystemStaticSlice.js</code></li>
<li><code>redux/slices/mapLayersSlice.js</code></li>
<li><code>redux/slices/selectedAreaSlice.js</code></li>
<li><code>redux/slices/database/polylines/polylineLayerVisibleSlice.js</code></li>
<li><code>redux/slices/database/pois/poiLayerVisibleSlice.js</code></li>
</ul>
<hr />
<hr />
<p><a href="../../../README.md">Zurück zur Übersicht</a></p>

View File

@@ -0,0 +1,6 @@
<h1 id="übersicht-docscomponentsuiwidgetsmaplayerscontrolpanel">📄
Übersicht: docs/components/uiWidgets/mapLayersControlPanel</h1>
<ul>
<li><a href="EditModeToggle.md">EditModeToggle</a></li>
<li><a href="MapLayersControlPanel.md">MapLayersControlPanel</a></li>
</ul>

View File

@@ -0,0 +1,107 @@
<!-- /docs/config/README.md -->
<h1 id="konfigurationsübersicht-config">⚙️ Konfigurationsübersicht
(/config)</h1>
<p>Dieses Verzeichnis enthält die zentrale Pfad-Konfigurationsdatei, die
für konsistente URL-Generierung in NodeMap zuständig ist.</p>
<hr />
<h2 id="paths.js">📁 <a href="./paths.md"><code>paths.js</code></a></h2>
<ul>
<li>Berechnet den Basis-Pfad aus <code>.env.production</code> oder
<code>.env.development</code></li>
<li>Liefert <code>BASE_URL</code>, z.B. <code>/talas5</code></li>
<li>Wird in der gesamten App zur dynamischen URL-Erzeugung
verwendet</li>
</ul>
<hr />
<p>Diese Konfiguration stellt sicher, dass NodeMap unabhängig von
Hostname, Port oder Verzeichnisstruktur funktioniert.</p>
<p>📄 Pfad: <code>/config/paths.js</code></p>
<hr />
<h2 id="osmbasierte-open-quellen">OSMbasierte, „open“ Quellen</h2>
<p>Diese sind datenrechtlich offen (ODbL bzw. Community-Lizenzen), aber
das „kostenlos“ gilt nicht im Sinne unbegrenzter TileNutzung. Die
TileServer werden als CommunityRessource bereitgestellt bitte
Policies respektieren.</p>
<ul>
<li>osm-standard (OpenStreetMap)</li>
<li><ul>
<li>Key: Nein</li>
</ul></li>
<li><ul>
<li>Nutzung: FairUse; für produktive/hohe Last eigenen
TileServer/Provider verwenden.</li>
</ul></li>
<li><ul>
<li>Attribution: „© OpenStreetMap contributors“</li>
</ul></li>
<li>osm-humanitarian (HOT)</li>
<li><ul>
<li>Key: Nein</li>
</ul></li>
<li><ul>
<li>Nutzung: FairUse; für größere Last die Betreiber kontaktieren bzw.
andere Infrastruktur nutzen.</li>
</ul></li>
<li><ul>
<li>Attribution: „© OpenStreetMap contributors <br></li>
</ul></li>
<li></li>
<li>Humanitarian OpenStreetMap Team“ cyclosm</li>
<li><ul>
<li>Key: Nein</li>
</ul></li>
<li><ul>
<li>Nutzung: FairUse (bereitgestellt u. a. über OSM France). Für höhere
Last Unterstützung/Hostingoptionen prüfen.</li>
</ul></li>
<li><ul>
<li>Attribution: „CyclOSM“ + „OpenStreetMap contributors“</li>
</ul></li>
<li>PraxisTipps Kleine bis mittlere Nutzung: Die oben genannten
„keyless“ Quellen sind oft ausreichend, solange du Attribution setzt und
Limits respektierst. Produktion/hohe Last: Nutze einen Anbieter mit
Vertrag/Key (z. B. Thunderforest, Tracestrack, MapTiler, Mapbox) oder
hoste Tiles selbst. Schlüssel im Client: Für Thunderforest/Tracestrack
stehen die Keys im Frontend. Das ist üblich, aber der Key ist sichtbar.
Wenn du ihn verbergen willst, richte einen kleinen TileProxy auf deinem
Server ein, der den Key serverseitig anhängt und optional cached.
Attribution: Dein BaseMapPanel setzt bereits Attributionsstrings aus
config.json. Achte darauf, dass sie je Quelle korrekt sind.</li>
</ul>
<hr />
<p>Kurzantwort: Für kommerzielle Nutzung sind OSMCommunityTileServer
nicht geeignet. Nutze einen bezahlten Anbieter (z.B. Thunderforest,
Tracestrack) oder hoste selbst. Attribution ist immer Pflicht.</p>
<p>Links und Hinweise je Layer/Provider:</p>
<p>OpenStreetMap Standard (osm-standard)</p>
<p>Lizenz/Daten: ODbL, Attribution Pflicht Tile-Server-Policy (keine
Produktion/hohe Last):
https://operations.osmfoundation.org/policies/tiles/
Urheberrecht/Attribution: https://www.openstreetmap.org/copyright HOT
Humanitarian (osm-humanitarian)</p>
<p>Community-Server (OSM France); keine Produktion/hohe Last Info/Policy
OSM France Tiles: https://tile.openstreetmap.fr/ HOT:
https://www.hotosm.org/ CyclOSM (cyclosm)</p>
<p>Community-Server (OSM France); keine Produktion/hohe Last
Projektseite: https://www.cyclosm.org/ Hinweise/Policy (OSM France):
https://tile.openstreetmap.fr/ Wiki:
https://wiki.openstreetmap.org/wiki/CyclOSM Carto Light (carto-light /
Positron)</p>
<p>Keylos nutzbar mit Attribution; FairUse, für hohe Last über
CARTOPläne Basemaps: https://carto.com/basemaps/ Attribution:
https://carto.com/attributions Pricing (Plattform):
https://carto.com/pricing/ (bei großem Volumen Sales kontaktieren)
Thunderforest (Cycle/Transport u. a.)</p>
<p>Kommerziell mit APIKey; Pläne von Free bis Pro Pricing:
https://www.thunderforest.com/pricing/ Terms/Attribution:
https://www.thunderforest.com/terms/ Tracestrack Topo</p>
<p>APIKey erforderlich; kostenlose und bezahlte Pläne
Übersicht/Pricing: https://www.tracestrack.com/en/maps/
Nutzungsbedingungen: https://www.tracestrack.com/en/terms/
Empfehlung:</p>
<p>Produktion: Nimm Thunderforest oder Tracestrack (oder MapTiler:
https://www.maptiler.com/pricing/) oder hoste Tiles selbst. Attribution
in der Karte anzeigen (OSM + jeweiliger Anbieter). Soll ich diese Links
samt klarer Hinweise kompakt in eure README.md einpflegen?</p>
<hr />
<p><a href="../README.md">Zurück zur Übersicht</a></p>

View File

@@ -0,0 +1,17 @@
<!-- /docs/config/paths.md -->
<h1 id="paths.js">📁 paths.js</h1>
<p>Berechnet den sauberen <code>BASE_URL</code>-Pfad basierend auf
<code>.env.production</code> oder
<code>public/config.json → basePath</code>.<br />
Entfernt führende und abschließende Slashes.</p>
<h2 id="beispiel">Beispiel</h2>
<p>Wenn <code>basePath = "/talas5/"</code> in config.json gesetzt ist,
wird <code>BASE_URL = "/talas5"</code> verwendet.</p>
<div class="sourceCode" id="cb1"><pre class="sourceCode js"><code class="sourceCode javascript"><span id="cb1-1"><a href="#cb1-1" aria-hidden="true" tabindex="-1"></a><span class="kw">const</span> BASE_PATH <span class="op">=</span> basePathRaw<span class="op">.</span><span class="fu">replace</span>(<span class="ss">/</span><span class="sc">^\/|\/$</span><span class="ss">/g</span><span class="op">,</span> <span class="st">&quot;&quot;</span>)<span class="op">;</span></span>
<span id="cb1-2"><a href="#cb1-2" aria-hidden="true" tabindex="-1"></a><span class="im">export</span> <span class="kw">const</span> BASE_URL <span class="op">=</span> BASE_PATH <span class="op">?</span> <span class="vs">`/</span><span class="sc">${</span>BASE_PATH<span class="sc">}</span><span class="vs">`</span> <span class="op">:</span> <span class="st">&quot;&quot;</span><span class="op">;</span></span></code></pre></div>
<h2 id="nutzung">Nutzung</h2>
<ul>
<li>Für konsistente Pfadangaben im gesamten Projekt</li>
</ul>
<hr />
<p><a href="../README.md">Zurück zur Übersicht</a></p>

View File

@@ -0,0 +1,10 @@
<!-- /docs/env.local -->
<h3 id="docsenv.local.schema.md">/docs/env.local.schema.md</h3>
<ul>
<li><code>NEXT_PUBLIC_API_HOST</code> → Webservice-DNS oder IP</li>
<li><code>NEXT_PUBLIC_API_BASE_PATH</code> → z.B. <code>talas5</code>,
per Deployment steuerbar</li>
<li><code>DB_NAME</code> → hängt vom Kundenprojekt ab</li>
</ul>
<hr />
<p><a href="README.md">Zurück zur Übersicht</a></p>

View File

@@ -0,0 +1,14 @@
<h1 id="übersicht-docsguide">📄 Übersicht: docs/guide</h1>
<ul>
<li><a href="dependencies.md">dependencies</a></li>
<li><a href="env.md">env</a></li>
<li><a href="faq.md">faq</a></li>
<li><a href="glossar.md">glossar</a></li>
<li><a href="mock-data.md">mock-data</a></li>
<li><a href="onboarding-checklist.md">onboarding-checklist</a></li>
<li><a href="project-structure.md">project-structure</a></li>
<li><a href="redux-zustand.md">redux-zustand</a></li>
<li><a href="setup-dev.md">setup-dev</a></li>
<li><a href="user-guide.md">user-guide</a></li>
<li><a href="webservices.md">webservices</a></li>
</ul>

View File

@@ -0,0 +1,272 @@
<!-- /docs/guide/dependencies.md-->
<h1 id="abhängigkeiten-in-nodemap-stand-2025">📂 Abhängigkeiten in
NodeMap (Stand: 2025)</h1>
<p>Diese Datei listet alle Drittanbieter-Abhängigkeiten aus der Datei
<code>package.json</code> mit einer kurzen Erklärung, wofür sie im
Projekt verwendet werden.</p>
<hr />
<h2 id="frameworks-tooling">✨ Frameworks &amp; Tooling</h2>
<table>
<colgroup>
<col style="width: 32%" />
<col style="width: 67%" />
</colgroup>
<thead>
<tr>
<th>Paket</th>
<th>Zweck &amp; Beschreibung</th>
</tr>
</thead>
<tbody>
<tr>
<td><strong>next</strong></td>
<td>Hauptframework (Next.js) zur Erstellung von React-basierten
SSR/SSG-Apps.</td>
</tr>
<tr>
<td><strong>react</strong> / <strong>react-dom</strong></td>
<td>Grundlage für UI-Komponenten im Projekt.</td>
</tr>
<tr>
<td><strong>tailwindcss</strong> / <strong>postcss</strong> /
<strong>autoprefixer</strong></td>
<td>Styling mit Tailwind. PostCSS verarbeitet CSS, Autoprefixer fügt
vendor-spezifische Präfixe hinzu.</td>
</tr>
<tr>
<td><strong>dotenv</strong></td>
<td>Ermöglicht das Einlesen von <code>.env</code>-Dateien zur Laufzeit
auf dem Server.</td>
</tr>
</tbody>
</table>
<hr />
<h2 id="leaflet-karten">🌐 Leaflet &amp; Karten</h2>
<table>
<colgroup>
<col style="width: 40%" />
<col style="width: 59%" />
</colgroup>
<thead>
<tr>
<th>Paket</th>
<th>Zweck &amp; Beschreibung</th>
</tr>
</thead>
<tbody>
<tr>
<td><strong>leaflet</strong></td>
<td>Basiskartenbibliothek zur Darstellung interaktiver Karten.</td>
</tr>
<tr>
<td><strong>leaflet-contextmenu</strong></td>
<td>Kontextmenüs per Rechtsklick in Leaflet.</td>
</tr>
<tr>
<td><strong>leaflet-control-geocoder</strong></td>
<td>Steuerelement für Suche/Geokodierung in Karten.</td>
</tr>
<tr>
<td><strong>leaflet.smooth_marker_bouncing</strong></td>
<td>Animation für Marker-Bounces (visuelles Feedback).</td>
</tr>
<tr>
<td><strong>overlapping-marker-spiderfier-leaflet</strong></td>
<td>Marker-Overlapping-Management mit Spiderfy-Effekt bei Klick.</td>
</tr>
</tbody>
</table>
<hr />
<h2 id="zustand-redux">🪄 Zustand &amp; Redux</h2>
<table>
<colgroup>
<col style="width: 33%" />
<col style="width: 66%" />
</colgroup>
<thead>
<tr>
<th>Paket</th>
<th>Zweck &amp; Beschreibung</th>
</tr>
</thead>
<tbody>
<tr>
<td><strong><span class="citation"
data-cites="reduxjs/toolkit">@reduxjs/toolkit</span></strong></td>
<td>Vereinfachte Redux-Nutzung mit Slices &amp; Thunks.</td>
</tr>
<tr>
<td><strong>redux</strong> / <strong>react-redux</strong></td>
<td>Core-State-Management in React-Komponenten.</td>
</tr>
<tr>
<td><strong>redux-thunk</strong></td>
<td>Middleware zur Verarbeitung von asynchronen Aktionen.</td>
</tr>
</tbody>
</table>
<hr />
<h2 id="ui-styling">🦜 UI &amp; Styling</h2>
<table>
<colgroup>
<col style="width: 43%" />
<col style="width: 56%" />
</colgroup>
<thead>
<tr>
<th>Paket</th>
<th>Zweck &amp; Beschreibung</th>
</tr>
</thead>
<tbody>
<tr>
<td><strong><span class="citation"
data-cites="mui/icons-material">@mui/icons-material</span></strong></td>
<td>UI-Icons aus der Material UI Sammlung.</td>
</tr>
<tr>
<td><strong><span class="citation"
data-cites="emotion/react">@emotion/react</span></strong>, <strong><span
class="citation"
data-cites="emotion/styled">@emotion/styled</span></strong></td>
<td>Styled Components-Engine, benötigt für MUI Styling.</td>
</tr>
<tr>
<td><strong><span class="citation"
data-cites="heroicons/react">@heroicons/react</span></strong></td>
<td>React-Icons im Hero-Stil für UI.</td>
</tr>
<tr>
<td><strong>react-select</strong></td>
<td>Erweiterte Dropdown-Komponente mit Suchfunktion.</td>
</tr>
<tr>
<td><strong>react-toastify</strong></td>
<td>Benachrichtigungs-Tool für Toast-Meldungen.</td>
</tr>
</tbody>
</table>
<hr />
<h2 id="daten-kommunikation">📁 Daten &amp; Kommunikation</h2>
<table>
<colgroup>
<col style="width: 35%" />
<col style="width: 64%" />
</colgroup>
<thead>
<tr>
<th>Paket</th>
<th>Zweck &amp; Beschreibung</th>
</tr>
</thead>
<tbody>
<tr>
<td><strong>axios</strong></td>
<td>HTTP-Client zur API-Kommunikation (z.B. zu Webservices).</td>
</tr>
<tr>
<td><strong>cookies</strong></td>
<td>Zugriff &amp; Verwaltung von Cookies auf Server/Client.</td>
</tr>
<tr>
<td><strong>ws</strong></td>
<td>WebSocket-Kommunikation mit z.B. GMA-Live-Daten.</td>
</tr>
<tr>
<td><strong>xml2js</strong> / <strong>fast-xml-parser</strong></td>
<td>Parsen von XML-Antworten aus Webservices.</td>
</tr>
<tr>
<td><strong>mysql</strong> / <strong>mysql2</strong></td>
<td>Zugriff auf MySQL-Datenbanken.</td>
</tr>
</tbody>
</table>
<hr />
<h2 id="sicherheit-netzwerk">🚫 Sicherheit &amp; Netzwerk</h2>
<table>
<colgroup>
<col style="width: 30%" />
<col style="width: 69%" />
</colgroup>
<thead>
<tr>
<th>Paket</th>
<th>Zweck &amp; Beschreibung</th>
</tr>
</thead>
<tbody>
<tr>
<td><strong>http-proxy-middleware</strong></td>
<td>API-Routing &amp; Proxy-Zugriff z.B. für lokale Entwicklung.</td>
</tr>
<tr>
<td><strong>nextjs-cors</strong></td>
<td>CORS-Konfiguration für Next.js API-Routen.</td>
</tr>
</tbody>
</table>
<hr />
<h2 id="entwicklung-test-nur-devdependencies">🎓 Entwicklung &amp; Test
(nur devDependencies)</h2>
<table>
<colgroup>
<col style="width: 60%" />
<col style="width: 39%" />
</colgroup>
<thead>
<tr>
<th>Paket</th>
<th>Zweck &amp; Beschreibung</th>
</tr>
</thead>
<tbody>
<tr>
<td><strong>cypress</strong></td>
<td>End-to-End-Testing.</td>
</tr>
<tr>
<td><strong>jest-environment-jsdom</strong> /
<strong>jest-fetch-mock</strong> / <strong>jest-junit</strong></td>
<td>Unit- &amp; Integrationstests.</td>
</tr>
<tr>
<td><strong>identity-obj-proxy</strong></td>
<td>Mocking für CSS-Module im Jest-Testkontext.</td>
</tr>
<tr>
<td><strong>node-fetch</strong>, <strong>node-mocks-http</strong></td>
<td>HTTP-Mocks für Tests.</td>
</tr>
<tr>
<td><strong>husky</strong></td>
<td>Git-Hook-Management (Pre-Commit etc.).</td>
</tr>
<tr>
<td><strong>raw-loader</strong></td>
<td>Import von Rohdaten (z.B. SVG) in Webpack.</td>
</tr>
</tbody>
</table>
<hr />
<h2 id="weitere-tools-hilfen">📄 Weitere Tools &amp; Hilfen</h2>
<table>
<thead>
<tr>
<th>Paket</th>
<th>Zweck &amp; Beschreibung</th>
</tr>
</thead>
<tbody>
<tr>
<td><strong>prepare</strong></td>
<td>Wird durch Husky benötigt zum Setup von Hooks.</td>
</tr>
<tr>
<td><strong>bump-version</strong></td>
<td>Interner Versionsbump-Script für <code>appVersion.js</code>.</td>
</tr>
</tbody>
</table>
<hr />
<p><a href="../README.md">Zurück zur Übersicht</a></p>

View File

@@ -0,0 +1,108 @@
<!-- /docs/guide/env.md-->
<h1 id="umgebungsvariablen-.env.production-.env.development">🌐
Umgebungsvariablen (<code>.env.production</code> /
<code>.env.development</code>)</h1>
<p>NodeMap verwendet Umgebungsvariablen zur Steuerung von API-Verhalten,
Serverpfaden und Moduswahl (Mock oder Produktion).</p>
<h2 id="speicherort">📂 Speicherort</h2>
<ul>
<li><strong>Entwicklung</strong>: <code>.env.development</code></li>
<li><strong>Produktion</strong>: <code>.env.production</code> (für
<code>npm run build</code> &amp; <code>npm start</code>)</li>
</ul>
<h2 id="wichtige-variablen">🔧 Wichtige Variablen</h2>
<table>
<colgroup>
<col style="width: 18%" />
<col style="width: 12%" />
<col style="width: 69%" />
</colgroup>
<thead>
<tr>
<th>Variable</th>
<th>Beispielwert</th>
<th>Beschreibung</th>
</tr>
</thead>
<tbody>
<tr>
<td><code>DB_HOST</code></td>
<td><code>localhost</code></td>
<td>Adresse des Datenbankservers (MySQL)</td>
</tr>
<tr>
<td><code>DB_PORT</code></td>
<td><code>3306</code></td>
<td>Port für die Datenbankverbindung</td>
</tr>
<tr>
<td><code>DB_NAME</code></td>
<td><code>talas</code></td>
<td>Datenbankname</td>
</tr>
<tr>
<td><code>DB_USER</code></td>
<td><code>root</code></td>
<td>Benutzername für MySQL</td>
</tr>
<tr>
<td><code>DB_PASSWORD</code></td>
<td><code>geheim</code></td>
<td>Passwort für MySQL</td>
</tr>
<tr>
<td><code>NEXT_PUBLIC_API_PORT_MODE</code></td>
<td><code>prod</code> oder <code>dev</code></td>
<td>Steuert API-Routing bei Services (z.B. Portwechsel für lokal)</td>
</tr>
<tr>
<td><code>NEXT_PUBLIC_USE_MOCKS</code></td>
<td><code>true</code> oder <code>false</code></td>
<td>Aktiviert den Mockdaten-Modus über <code>/api/mocks/...</code></td>
</tr>
<tr>
<td><code>basePath</code> (in config.json)</td>
<td><code>/talas5</code> oder leer</td>
<td>Optionaler Pfad, falls App unter Subpfad läuft (z.B. IIS). Wird
jetzt in <code>public/config.json</code> gepflegt.</td>
</tr>
<tr>
<td><code>NEXT_PUBLIC_DEBUG</code></td>
<td><code>true</code> oder <code>false</code></td>
<td>Aktiviert zusätzliche <code>console.log</code> Ausgaben für
Debugging im Browser</td>
</tr>
</tbody>
</table>
<h2 id="beispiel-.env.production">📦 Beispiel
<code>.env.production</code></h2>
<pre class="env"><code>DB_HOST=localhost
DB_PORT=3306
DB_NAME=talas
DB_USER=root
DB_PASSWORD=geheim
NEXT_PUBLIC_API_PORT_MODE=prod
NEXT_PUBLIC_USE_MOCKS=false
// public/config.json
{
...
&quot;basePath&quot;: &quot;/talas5&quot;
}
NEXT_PUBLIC_DEBUG=false</code></pre>
<h2 id="beispiel-.env.development">📦 Beispiel
<code>.env.development</code></h2>
<pre class="env"><code>DB_HOST=localhost
DB_PORT=3306
DB_NAME=talas
DB_USER=root
DB_PASSWORD=geheim
NEXT_PUBLIC_API_PORT_MODE=dev
NEXT_PUBLIC_USE_MOCKS=true
// public/config.json
{
...
&quot;basePath&quot;: &quot;/talas5&quot;
}
NEXT_PUBLIC_DEBUG=true</code></pre>
<hr />
<p><a href="../README.md">Zurück zur Übersicht</a></p>

View File

@@ -0,0 +1,85 @@
<!-- /docs/guide/faq.md -->
<h1 id="faq-häufige-fragen">❓ FAQ Häufige Fragen</h1>
<hr />
<h3 id="warum-sehe-ich-nur-eine-weiße-seite">🔹 Warum sehe ich nur eine
weiße Seite?</h3>
<ul>
<li>Stelle sicher, dass <code>.env.production</code> korrekt
konfiguriert ist.</li>
<li>Prüfe, ob <code>NEXT_PUBLIC_USE_MOCKS=false</code> gesetzt ist (nur
in Produktion).</li>
<li>Starte den Dienst neu (<strong>NodeMapService</strong>) oder führe
<code>npm start</code> im Terminal aus.</li>
</ul>
<hr />
<h3 id="was-bedeutet-die-url-m12u484">🔹 Was bedeutet die URL
<code>?m=12&amp;u=484</code>?</h3>
<ul>
<li><code>m=12</code> ist die <strong>Map-ID</strong> (z.B.
Leverkusen).</li>
<li><code>u=484</code> ist die <strong>User-ID</strong>.</li>
<li>Diese IDs werden vom übergeordneten System
(<strong>TALAS.web</strong>) übergeben und steuern, was angezeigt
wird.</li>
</ul>
<hr />
<h3 id="wie-kann-ich-pois-hinzufügen-oder-bearbeiten">🔹 Wie kann ich
POIs hinzufügen oder bearbeiten?</h3>
<ul>
<li>Rechtsklick auf die Karte → <strong>„POI hinzufügen“</strong> oder
<strong>„bearbeiten“</strong>.</li>
<li>Daten werden automatisch gespeichert, wenn du das Formular
bestätigst.</li>
</ul>
<hr />
<h3 id="wie-kann-ich-die-karte-lokal-testen-ohne-backend">🔹 Wie kann
ich die Karte lokal testen, ohne Backend?</h3>
<ul>
<li>Setze in <code>.env.development</code> die Variable
<code>NEXT_PUBLIC_USE_MOCKS=true</code>.</li>
<li>Starte mit <code>npm run dev</code>.</li>
<li>Die App lädt jetzt Mockdaten aus <code>/mockData/</code>.</li>
</ul>
<hr />
<h3 id="was-mache-ich-wenn-keine-marker-angezeigt-werden">🔹 Was mache
ich, wenn keine Marker angezeigt werden?</h3>
<ul>
<li>Prüfe die Verbindung zum Webservice:<br />
<code>http://&lt;ip&gt;/talas5/ClientData/WebServiceMap.asmx</code></li>
<li>Stelle sicher, dass die <strong>Map-ID</strong> und
<strong>User-ID</strong> in der URL gültig sind.</li>
</ul>
<hr />
<h3
id="wie-erkenne-ich-ob-mein-layer-z.-b.-talas-wago-gma-geladen-ist">🔹
Wie erkenne ich, ob mein Layer (z.B. TALAS, WAGO, GMA) geladen
ist?</h3>
<ul>
<li>Im rechten Panel (<strong>LayerControl</strong>) sollten Checkboxen
für jeden Layer erscheinen.</li>
<li>Wenn keine Layer sichtbar sind, prüfe
<code>redux/mapLayersSlice</code> und den Webservice
<code>GisSystemStatic</code>.</li>
</ul>
<hr />
<h3 id="was-tun-bei-der-meldung-fehler-beim-laden-der-kartenkacheln">🔹
Was tun bei der Meldung „Fehler beim Laden der Kartenkacheln“?</h3>
<ul>
<li>Verzeichnis <code>C:\inetpub\wwwroot\talas5\TileMap</code>
prüfen.</li>
<li>Kartenkacheln müssen im <code>public/</code>-Pfad korrekt verlinkt
sein (z.B. <code>mapTiles/...</code>).</li>
</ul>
<hr />
<h3 id="wie-kann-ich-die-anwendung-aktualisieren">🔹 Wie kann ich die
Anwendung aktualisieren?</h3>
<ul>
<li><strong>Kleines Update:</strong> Nur <code>.next/</code>
kopieren.</li>
<li><strong>Größeres Update:</strong> Gesamte App inkl.
<code>node_modules</code>, <code>.env.production</code> und
<code>public/</code> ersetzen.</li>
<li>Dienst neu starten.</li>
</ul>
<hr />
<p><a href="../README.md">Zurück zur Übersicht</a></p>

View File

@@ -0,0 +1,128 @@
<h1 id="glossar">📘 Glossar</h1>
<p>Eine Übersicht wichtiger Begriffe rund um NodeMap und die verwendeten
Technologien.</p>
<table>
<colgroup>
<col style="width: 18%" />
<col style="width: 81%" />
</colgroup>
<thead>
<tr>
<th>Begriff</th>
<th>Erklärung</th>
</tr>
</thead>
<tbody>
<tr>
<td><strong>NodeMap</strong></td>
<td>Die Kartenanwendung zur Darstellung von GIS-Daten (z.B. POIs,
Geräte) in TALAS.web.</td>
</tr>
<tr>
<td><strong>Next.js</strong></td>
<td>Ein Webframework für React, das Server-Rendering und Routing
vereinfacht.</td>
</tr>
<tr>
<td><strong>React</strong></td>
<td>Eine JavaScript-Bibliothek zur Erstellung von Benutzeroberflächen
(UI).</td>
</tr>
<tr>
<td><strong>Redux Toolkit</strong></td>
<td>Ein Tool zur einfacheren Zustandverwaltung (State Management) für
React.</td>
</tr>
<tr>
<td><strong>Tailwind CSS</strong></td>
<td>Ein CSS-Framework mit vordefinierten Klassen für schnelles
UI-Design.</td>
</tr>
<tr>
<td><strong>Leaflet</strong></td>
<td>Eine JavaScript-Bibliothek für interaktive Karten auf
Webseiten.</td>
</tr>
<tr>
<td><strong>POI</strong></td>
<td>„Point of Interest“ Ein Marker auf der Karte (z.B. ein Gerät,
Schacht oder Messpunkt).</td>
</tr>
<tr>
<td><strong>MapComponent</strong></td>
<td>Die Hauptkomponente, die die Karte lädt und alle Inhalte darauf
anzeigt.</td>
</tr>
<tr>
<td><strong>WebService</strong></td>
<td>Ein Serverdienst, der Daten wie POIs, Linien, Geräte liefert (z.B.
aus TALAS).</td>
</tr>
<tr>
<td><strong>.env.production</strong></td>
<td>Eine Konfigurationsdatei mit Zugangsdaten und Einstellungen für den
Live-Betrieb.</td>
</tr>
<tr>
<td><strong>Mockdaten</strong></td>
<td>Testdaten, die lokal geladen werden, wenn kein Server verfügbar ist
(<code>USE_MOCKS=true</code>).</td>
</tr>
<tr>
<td><strong>iFrame</strong></td>
<td>Ein HTML-Element, mit dem eine andere Webseite innerhalb einer Seite
eingebettet wird.</td>
</tr>
<tr>
<td><strong>nssm.exe</strong></td>
<td>Ein Tool, um Node.js-Anwendungen als Windows-Dienst laufen zu
lassen.</td>
</tr>
<tr>
<td><strong>Port 3000</strong></td>
<td>Der lokale Entwicklungs-Port, unter dem NodeMap im Browser
erreichbar ist.</td>
</tr>
<tr>
<td><strong>Redux Slice</strong></td>
<td>Ein Teil des globalen Redux-Zustands, der z.B. POIs oder Linien
speichert.</td>
</tr>
<tr>
<td><strong>Thunk</strong></td>
<td>Eine asynchrone Funktion in Redux, z.B. um Daten vom Server zu
laden.</td>
</tr>
<tr>
<td><strong>Contextmenü</strong></td>
<td>Ein Rechtsklick-Menü mit Funktionen wie „POI hinzufügen“, „Station
öffnen“.</td>
</tr>
<tr>
<td><strong>Layer</strong></td>
<td>Ein Karten-Overlay (z.B. Geräte, Linien), das ein- oder
ausgeblendet werden kann.</td>
</tr>
<tr>
<td><strong>IdSystem / IdMap</strong></td>
<td>Interne IDs zur Zuordnung von Layern und Karten in TALAS.</td>
</tr>
<tr>
<td><strong>GisSystemStatic</strong></td>
<td>Eine Webservice-Antwort mit Systeminformationen für die
Kartendarstellung.</td>
</tr>
<tr>
<td><strong>mapTiles</strong></td>
<td>Bildkacheln (z.B. <code>.png</code>), die die Grundkarte darstellen
wie bei Google Maps.</td>
</tr>
<tr>
<td><strong>TALAS.web</strong></td>
<td>Die bestehende (ältere) Verwaltungssoftware, in die NodeMap
eingebettet wird.</td>
</tr>
</tbody>
</table>
<hr />
<p><a href="../README.md">Zurück zur Übersicht</a></p>

View File

@@ -0,0 +1,65 @@
<h1 id="mockdaten-modus-next_public_use_mockstrue">🧪 Mockdaten-Modus
(<code>NEXT_PUBLIC_USE_MOCKS=true</code>)</h1>
<p>Dieses Projekt unterstützt einen optionalen
<strong>Mockdaten-Modus</strong>, um Entwicklung und Tests ohne
Backend/Webservice durchzuführen.</p>
<hr />
<h2 id="zweck-nutzen">🔍 Zweck &amp; Nutzen</h2>
<ul>
<li>Schneller Entwicklungsstart ohne aktive Serververbindung</li>
<li>Stabilere Testszenarien mit festen JSON-Daten</li>
<li>Vollständige Isolation von Backend-Fehlern während der
UI-Entwicklung</li>
</ul>
<hr />
<h2 id="aktivierung">⚙️ Aktivierung</h2>
<p>Mockdaten werden aktiviert durch folgende Umgebungsvariable:</p>
<pre class="env"><code>NEXT_PUBLIC_USE_MOCKS=true</code></pre>
<p>Diese Variable wird in <code>.env.development</code> gesetzt und
<strong>nicht</strong> für die Produktionsumgebung verwendet.<br />
Im Produktivbetrieb steht:</p>
<pre class="env"><code>NEXT_PUBLIC_USE_MOCKS=false</code></pre>
<hr />
<h2 id="funktionsweise">🧩 Funktionsweise</h2>
<p>Wenn <code>NEXT_PUBLIC_USE_MOCKS=true</code> gesetzt ist:</p>
<ul>
<li>Statt realer Webservices werden Endpunkte unter
<code>/pages/api/mocks/webservice/*.js</code> aufgerufen</li>
<li>Diese geben vorbereitete JSON-Dateien unter
<code>/mockData/*.json</code> zurück</li>
</ul>
<hr />
<h2 id="beispiel-aufruf-im-mockmodus">📂 Beispiel-Aufruf im
Mockmodus</h2>
<div class="sourceCode" id="cb3"><pre class="sourceCode ts"><code class="sourceCode typescript"><span id="cb3-1"><a href="#cb3-1" aria-hidden="true" tabindex="-1"></a><span class="co">// Beispiel aus fetchGisSystemStaticService.js</span></span>
<span id="cb3-2"><a href="#cb3-2" aria-hidden="true" tabindex="-1"></a><span class="kw">const</span> url <span class="op">=</span> useMocks</span>
<span id="cb3-3"><a href="#cb3-3" aria-hidden="true" tabindex="-1"></a> <span class="op">?</span> <span class="st">&quot;/api/mocks/webservice/GisSystemStatic&quot;</span></span>
<span id="cb3-4"><a href="#cb3-4" aria-hidden="true" tabindex="-1"></a> <span class="op">:</span> <span class="vs">`</span><span class="sc">${</span>apiUrl<span class="sc">}</span><span class="vs">/WebServiceMap.asmx/GisSystemStatic`</span><span class="op">;</span></span></code></pre></div>
<hr />
<h2 id="sicherheit-versionskontrolle">🛡️ Sicherheit &amp;
Versionskontrolle</h2>
<ul>
<li>Alle <code>.json</code>-Dateien im Ordner <code>/mockData/</code>
sind über <code>.gitignore</code> vom Repository ausgeschlossen</li>
<li>So wird verhindert, dass versehentlich sensible Testdaten
veröffentlicht werden</li>
</ul>
<hr />
<h2 id="hinweise">💡 Hinweise</h2>
<ul>
<li>Mockdaten sollen nur die wichtigsten API-Schnittstellen
simulieren</li>
<li>Bei Änderungen am Datenmodell sollten auch die Mockdaten
aktualisiert werden</li>
<li>Eine zentrale Thunk- &amp; Service-Logik entscheidet automatisch, ob
<code>mock</code> oder <code>real</code></li>
</ul>
<hr />
<h2 id="weitere-informationen">🔗 Weitere Informationen</h2>
<ul>
<li><a href="env.md">📄 Umgebungsvariablen</a></li>
<li><a href="webservices.md">📄 Webservices im Detail</a></li>
<li><a href="redux-zustand.md">📄 Zustandverwaltung (Redux)</a></li>
</ul>
<hr />
<p><a href="../README.md">Zurück zur Übersicht</a></p>

View File

@@ -0,0 +1,59 @@
<h2 id="onboarding-checkliste-für-neue-entwickler-bei-nodemap">
Onboarding-Checkliste für neue Entwickler bei NodeMap</h2>
<p>Willkommen im NodeMap-Team! Diese Checkliste begleitet dich Schritt
für Schritt beim Einstieg ins Projekt.</p>
<hr />
<h3 id="schritte-zum-start">🚦 Schritte zum Start</h3>
<ol type="1">
<li><p><strong>README.md lesen</strong><br />
<em>Verschaffe dir einen Überblick über das Projekt.</em><br />
☐ Erledigt</p></li>
<li><p><strong>Repository clonen &amp; installieren</strong></p>
<div class="sourceCode" id="cb1"><pre
class="sourceCode bash"><code class="sourceCode bash"><span id="cb1-1"><a href="#cb1-1" aria-hidden="true" tabindex="-1"></a><span class="fu">git</span> clone http://10.10.0.12:3000/ISA/nodeMap</span>
<span id="cb1-2"><a href="#cb1-2" aria-hidden="true" tabindex="-1"></a><span class="bu">cd</span> nodeMap</span>
<span id="cb1-3"><a href="#cb1-3" aria-hidden="true" tabindex="-1"></a><span class="ex">npm</span> install</span></code></pre></div>
<p>☐ Erledigt</p></li>
<li><p><strong><code>.env.development</code> anlegen</strong><br />
<em>Siehe <a href="docs/guide/env.md">env.md</a> für Details.</em><br />
☐ Erledigt</p></li>
<li><p><strong>Mock-Modus aktivieren</strong></p>
<pre class="env"><code>NEXT_PUBLIC_USE_MOCKS=true</code></pre>
<p>☐ Erledigt</p></li>
<li><p><strong>Projekt starten</strong></p>
<div class="sourceCode" id="cb3"><pre
class="sourceCode bash"><code class="sourceCode bash"><span id="cb3-1"><a href="#cb3-1" aria-hidden="true" tabindex="-1"></a><span class="ex">npm</span> run dev</span></code></pre></div>
<p>☐ Erledigt</p></li>
<li><p><strong>App im Browser öffnen</strong><br />
<em>Gehe zu:</em> <a
href="http://localhost:3000">http://localhost:3000</a><br />
☐ Erledigt</p></li>
<li><p><strong>POIs testen</strong><br />
<em>Hinzufügen, Verschieben, Löschen siehe <a
href="docs/guide/user-guide.md">user-guide.md</a>.</em><br />
☐ Erledigt</p></li>
<li><p><strong>Redux DevTools installieren &amp; testen</strong><br />
<em>Empfohlen für Debugging.</em><br />
☐ Erledigt</p></li>
<li><p><strong>Projektstruktur ansehen</strong><br />
<em>Wichtige Ordner: <code>components/</code>, <code>redux/</code>,
<code>services/</code> siehe <a
href="docs/guide/project-structure.md">project-structure.md</a>.</em><br />
☐ Erledigt</p></li>
<li><p><strong>Webservices überfliegen</strong><br />
<em>Siehe <a
href="docs/guide/webservices.md">webservices.md</a>.</em><br />
☐ Erledigt</p></li>
<li><p><strong>Fehlerbehandlung beachten</strong><br />
<em>Hinweise dazu findest du im README.</em><br />
☐ Erledigt</p></li>
<li><p><strong>Fragen notieren &amp; klären</strong><br />
<em>Sammle offene Punkte und sprich sie im Team an.</em><br />
☐ Erledigt</p></li>
</ol>
<hr />
<p><strong>Tipp:</strong> Hake jeden Schritt ab, sobald du ihn erledigt
hast.<br />
Viel Erfolg beim Einstieg! 🎉</p>
<hr />
<p><a href="../README.md">Zurück zur Übersicht</a></p>

View File

@@ -0,0 +1,135 @@
<h2 id="projektstruktur">🧱 Projektstruktur</h2>
<div class="sourceCode" id="cb1"><pre
class="sourceCode bash"><code class="sourceCode bash"><span id="cb1-1"><a href="#cb1-1" aria-hidden="true" tabindex="-1"></a><span class="ex">components/</span> → UI-Komponenten inkl. Karte und Layer-Control-Panel <span class="er">(</span><span class="kw">`</span><span class="ex">MapLayersControlPanel</span><span class="kw">`)</span></span>
<span id="cb1-2"><a href="#cb1-2" aria-hidden="true" tabindex="-1"></a></span>
<span id="cb1-3"><a href="#cb1-3" aria-hidden="true" tabindex="-1"></a><span class="ex">📦components</span></span>
<span id="cb1-4"><a href="#cb1-4" aria-hidden="true" tabindex="-1"></a> <span class="ex"></span> 📂contextmenu</span>
<span id="cb1-5"><a href="#cb1-5" aria-hidden="true" tabindex="-1"></a> <span class="ex"></span> ┣ 📜CoordinatePopup.js</span>
<span id="cb1-6"><a href="#cb1-6" aria-hidden="true" tabindex="-1"></a> <span class="ex"></span> ┗ 📜useMapContextMenu.js</span>
<span id="cb1-7"><a href="#cb1-7" aria-hidden="true" tabindex="-1"></a> <span class="ex"></span> 📂gisPolylines</span>
<span id="cb1-8"><a href="#cb1-8" aria-hidden="true" tabindex="-1"></a> <span class="ex"></span> ┣ 📂icons</span>
<span id="cb1-9"><a href="#cb1-9" aria-hidden="true" tabindex="-1"></a> <span class="ex"></span> ┃ ┣ 📜CircleIcon.js</span>
<span id="cb1-10"><a href="#cb1-10" aria-hidden="true" tabindex="-1"></a> <span class="ex"></span> ┃ ┣ 📜EndIcon.js</span>
<span id="cb1-11"><a href="#cb1-11" aria-hidden="true" tabindex="-1"></a> <span class="ex"></span> ┃ ┣ 📜StartIcon.js</span>
<span id="cb1-12"><a href="#cb1-12" aria-hidden="true" tabindex="-1"></a> <span class="ex"></span> ┃ ┗ 📜SupportPointIcons.js</span>
<span id="cb1-13"><a href="#cb1-13" aria-hidden="true" tabindex="-1"></a> <span class="ex"></span> ┗ 📜PolylineContextMenu.js</span>
<span id="cb1-14"><a href="#cb1-14" aria-hidden="true" tabindex="-1"></a> <span class="ex"></span> 📂icons</span>
<span id="cb1-15"><a href="#cb1-15" aria-hidden="true" tabindex="-1"></a> <span class="ex"></span> ┗ 📂devices</span>
<span id="cb1-16"><a href="#cb1-16" aria-hidden="true" tabindex="-1"></a> <span class="ex"></span> ┃ ┗ 📂overlapping</span>
<span id="cb1-17"><a href="#cb1-17" aria-hidden="true" tabindex="-1"></a> <span class="ex"></span> ┃ ┃ ┗ 📜PlusRoundIcon.js</span>
<span id="cb1-18"><a href="#cb1-18" aria-hidden="true" tabindex="-1"></a> <span class="ex"></span> 📂mainComponent</span>
<span id="cb1-19"><a href="#cb1-19" aria-hidden="true" tabindex="-1"></a> <span class="ex"></span> ┣ 📂hooks</span>
<span id="cb1-20"><a href="#cb1-20" aria-hidden="true" tabindex="-1"></a> <span class="ex"></span> ┃ ┗ 📜useInitializeMap.js</span>
<span id="cb1-21"><a href="#cb1-21" aria-hidden="true" tabindex="-1"></a> <span class="ex"></span> ┗ 📜MapComponent.js</span>
<span id="cb1-22"><a href="#cb1-22" aria-hidden="true" tabindex="-1"></a> <span class="ex"></span> 📂pois</span>
<span id="cb1-23"><a href="#cb1-23" aria-hidden="true" tabindex="-1"></a> <span class="ex"></span> ┣ 📜AddPOIModal.js</span>
<span id="cb1-24"><a href="#cb1-24" aria-hidden="true" tabindex="-1"></a> <span class="ex"></span> ┗ 📜PoiUpdateModal.js</span>
<span id="cb1-25"><a href="#cb1-25" aria-hidden="true" tabindex="-1"></a> <span class="ex"></span> 📂uiWidgets</span>
<span id="cb1-26"><a href="#cb1-26" aria-hidden="true" tabindex="-1"></a> <span class="ex"></span> ┣ 📂mapLayersControlPanel</span>
<span id="cb1-27"><a href="#cb1-27" aria-hidden="true" tabindex="-1"></a> <span class="ex"></span> ┃ ┣ 📜EditModeToggle.js</span>
<span id="cb1-28"><a href="#cb1-28" aria-hidden="true" tabindex="-1"></a> <span class="ex"></span> ┃ ┗ 📜MapLayersControlPanel.js</span>
<span id="cb1-29"><a href="#cb1-29" aria-hidden="true" tabindex="-1"></a> <span class="ex"></span> ┣ 📜CoordinateInput.js</span>
<span id="cb1-30"><a href="#cb1-30" aria-hidden="true" tabindex="-1"></a> <span class="ex"></span> ┗ 📜VersionInfoModal.js</span>
<span id="cb1-31"><a href="#cb1-31" aria-hidden="true" tabindex="-1"></a> <span class="ex"></span> 📜TestScript.js</span>
<span id="cb1-32"><a href="#cb1-32" aria-hidden="true" tabindex="-1"></a></span>
<span id="cb1-33"><a href="#cb1-33" aria-hidden="true" tabindex="-1"></a><span class="ex">config/</span> → zentrale Variablen <span class="er">(</span><span class="ex">.env.development,</span> .env.production<span class="kw">)</span></span>
<span id="cb1-34"><a href="#cb1-34" aria-hidden="true" tabindex="-1"></a></span>
<span id="cb1-35"><a href="#cb1-35" aria-hidden="true" tabindex="-1"></a><span class="ex">hooks/</span> → eigene React-Hooks</span>
<span id="cb1-36"><a href="#cb1-36" aria-hidden="true" tabindex="-1"></a></span>
<span id="cb1-37"><a href="#cb1-37" aria-hidden="true" tabindex="-1"></a><span class="ex">utils/</span> → POI- und Linienverarbeitung</span>
<span id="cb1-38"><a href="#cb1-38" aria-hidden="true" tabindex="-1"></a></span>
<span id="cb1-39"><a href="#cb1-39" aria-hidden="true" tabindex="-1"></a><span class="ex">lib/</span> → Formatierungen, Umrechnungen</span>
<span id="cb1-40"><a href="#cb1-40" aria-hidden="true" tabindex="-1"></a></span>
<span id="cb1-41"><a href="#cb1-41" aria-hidden="true" tabindex="-1"></a><span class="ex">public/</span> → Bilder, Icons, mapTiles sind nicht im nodeMap Projekt Verzeichnis sondern in TALAS Verzeichnis</span>
<span id="cb1-42"><a href="#cb1-42" aria-hidden="true" tabindex="-1"></a></span>
<span id="cb1-43"><a href="#cb1-43" aria-hidden="true" tabindex="-1"></a><span class="ex">pages/</span> → Next.js Seiten <span class="kw">&amp;</span> <span class="ex">Routen</span></span>
<span id="cb1-44"><a href="#cb1-44" aria-hidden="true" tabindex="-1"></a></span>
<span id="cb1-45"><a href="#cb1-45" aria-hidden="true" tabindex="-1"></a><span class="ex">scripts/</span> → lokale Tools <span class="er">(</span><span class="ex">nur</span> Dev<span class="kw">)</span></span>
<span id="cb1-46"><a href="#cb1-46" aria-hidden="true" tabindex="-1"></a></span>
<span id="cb1-47"><a href="#cb1-47" aria-hidden="true" tabindex="-1"></a><span class="ex">redux/</span> → globale Zustände <span class="er">(</span><span class="ex">Slices</span><span class="kw">)</span></span>
<span id="cb1-48"><a href="#cb1-48" aria-hidden="true" tabindex="-1"></a><span class="ex">📦redux</span></span>
<span id="cb1-49"><a href="#cb1-49" aria-hidden="true" tabindex="-1"></a> <span class="ex"></span> 📂slices</span>
<span id="cb1-50"><a href="#cb1-50" aria-hidden="true" tabindex="-1"></a> <span class="ex"></span> ┣ 📂database</span>
<span id="cb1-51"><a href="#cb1-51" aria-hidden="true" tabindex="-1"></a> <span class="ex"></span> ┃ ┣ 📂pois</span>
<span id="cb1-52"><a href="#cb1-52" aria-hidden="true" tabindex="-1"></a> <span class="ex"></span> ┃ ┃ ┣ 📜addPoiOnPolylineSlice.js</span>
<span id="cb1-53"><a href="#cb1-53" aria-hidden="true" tabindex="-1"></a> <span class="ex"></span> ┃ ┃ ┣ 📜addPoiSlice.js</span>
<span id="cb1-54"><a href="#cb1-54" aria-hidden="true" tabindex="-1"></a> <span class="ex"></span> ┃ ┃ ┣ 📜currentPoiSlice.js</span>
<span id="cb1-55"><a href="#cb1-55" aria-hidden="true" tabindex="-1"></a> <span class="ex"></span> ┃ ┃ ┣ 📜poiIconsDataSlice.js</span>
<span id="cb1-56"><a href="#cb1-56" aria-hidden="true" tabindex="-1"></a> <span class="ex"></span> ┃ ┃ ┣ 📜poiLayerVisibleSlice.js</span>
<span id="cb1-57"><a href="#cb1-57" aria-hidden="true" tabindex="-1"></a> <span class="ex"></span> ┃ ┃ ┣ 📜poiReadFromDbTriggerSlice.js</span>
<span id="cb1-58"><a href="#cb1-58" aria-hidden="true" tabindex="-1"></a> <span class="ex"></span> ┃ ┃ ┣ 📜poiTypesSlice.js</span>
<span id="cb1-59"><a href="#cb1-59" aria-hidden="true" tabindex="-1"></a> <span class="ex"></span> ┃ ┃ ┣ 📜poiTypSlice.js</span>
<span id="cb1-60"><a href="#cb1-60" aria-hidden="true" tabindex="-1"></a> <span class="ex"></span> ┃ ┃ ┣ 📜readPoiMarkersStoreSlice.js</span>
<span id="cb1-61"><a href="#cb1-61" aria-hidden="true" tabindex="-1"></a> <span class="ex"></span> ┃ ┃ ┗ 📜selectedPoiSlice.js</span>
<span id="cb1-62"><a href="#cb1-62" aria-hidden="true" tabindex="-1"></a> <span class="ex"></span> ┃ ┣ 📂polylines</span>
<span id="cb1-63"><a href="#cb1-63" aria-hidden="true" tabindex="-1"></a> <span class="ex"></span> ┃ ┃ ┣ 📜gisLinesSlice.js</span>
<span id="cb1-64"><a href="#cb1-64" aria-hidden="true" tabindex="-1"></a> <span class="ex"></span> ┃ ┃ ┣ 📜polylineContextMenuSlice.js</span>
<span id="cb1-65"><a href="#cb1-65" aria-hidden="true" tabindex="-1"></a> <span class="ex"></span> ┃ ┃ ┣ 📜polylineEventsDisabledSlice.js</span>
<span id="cb1-66"><a href="#cb1-66" aria-hidden="true" tabindex="-1"></a> <span class="ex"></span> ┃ ┃ ┗ 📜polylineLayerVisibleSlice.js</span>
<span id="cb1-67"><a href="#cb1-67" aria-hidden="true" tabindex="-1"></a> <span class="ex"></span> ┃ ┣ 📜locationDevicesFromDBSlice.js</span>
<span id="cb1-68"><a href="#cb1-68" aria-hidden="true" tabindex="-1"></a> <span class="ex"></span> ┃ ┣ 📜locationDevicesSlice.js</span>
<span id="cb1-69"><a href="#cb1-69" aria-hidden="true" tabindex="-1"></a> <span class="ex"></span> ┃ ┗ 📜priorityConfigSlice.js</span>
<span id="cb1-70"><a href="#cb1-70" aria-hidden="true" tabindex="-1"></a> <span class="ex"></span> ┣ 📂webservice</span>
<span id="cb1-71"><a href="#cb1-71" aria-hidden="true" tabindex="-1"></a> <span class="ex"></span> ┃ ┣ 📜gisLinesStatusSlice.js</span>
<span id="cb1-72"><a href="#cb1-72" aria-hidden="true" tabindex="-1"></a> <span class="ex"></span> ┃ ┣ 📜gisStationsMeasurementsSlice.js</span>
<span id="cb1-73"><a href="#cb1-73" aria-hidden="true" tabindex="-1"></a> <span class="ex"></span> ┃ ┣ 📜gisStationsStaticDistrictSlice.js</span>
<span id="cb1-74"><a href="#cb1-74" aria-hidden="true" tabindex="-1"></a> <span class="ex"></span> ┃ ┣ 📜gisStationsStatusDistrictSlice.js</span>
<span id="cb1-75"><a href="#cb1-75" aria-hidden="true" tabindex="-1"></a> <span class="ex"></span> ┃ ┣ 📜gisSystemStaticSlice.js</span>
<span id="cb1-76"><a href="#cb1-76" aria-hidden="true" tabindex="-1"></a> <span class="ex"></span> ┃ ┗ 📜userRightsSlice.js</span>
<span id="cb1-77"><a href="#cb1-77" aria-hidden="true" tabindex="-1"></a> <span class="ex"></span> ┣ 📜lineVisibilitySlice.js</span>
<span id="cb1-78"><a href="#cb1-78" aria-hidden="true" tabindex="-1"></a> <span class="ex"></span> ┣ 📜mapLayersSlice.js</span>
<span id="cb1-79"><a href="#cb1-79" aria-hidden="true" tabindex="-1"></a> <span class="ex"></span> ┣ 📜selectedAreaSlice.js</span>
<span id="cb1-80"><a href="#cb1-80" aria-hidden="true" tabindex="-1"></a> <span class="ex"></span> ┣ 📜selectedDeviceSlice.js</span>
<span id="cb1-81"><a href="#cb1-81" aria-hidden="true" tabindex="-1"></a> <span class="ex"></span> ┣ 📜urlParameterSlice.js</span>
<span id="cb1-82"><a href="#cb1-82" aria-hidden="true" tabindex="-1"></a> <span class="ex"></span> ┗ 📜zoomTriggerSlice.js</span>
<span id="cb1-83"><a href="#cb1-83" aria-hidden="true" tabindex="-1"></a> <span class="ex"></span> 📂thunks</span>
<span id="cb1-84"><a href="#cb1-84" aria-hidden="true" tabindex="-1"></a> <span class="ex"></span> ┣ 📂database</span>
<span id="cb1-85"><a href="#cb1-85" aria-hidden="true" tabindex="-1"></a> <span class="ex"></span> ┃ ┣ 📂pois</span>
<span id="cb1-86"><a href="#cb1-86" aria-hidden="true" tabindex="-1"></a> <span class="ex"></span> ┃ ┃ ┣ 📜addPoiThunk.js</span>
<span id="cb1-87"><a href="#cb1-87" aria-hidden="true" tabindex="-1"></a> <span class="ex"></span> ┃ ┃ ┣ 📜deletePoiThunk.js</span>
<span id="cb1-88"><a href="#cb1-88" aria-hidden="true" tabindex="-1"></a> <span class="ex"></span> ┃ ┃ ┣ 📜fetchPoiIconsDataThunk.js</span>
<span id="cb1-89"><a href="#cb1-89" aria-hidden="true" tabindex="-1"></a> <span class="ex"></span> ┃ ┃ ┣ 📜fetchPoiTypThunk.js</span>
<span id="cb1-90"><a href="#cb1-90" aria-hidden="true" tabindex="-1"></a> <span class="ex"></span> ┃ ┃ ┗ 📜updatePoiThunk.js</span>
<span id="cb1-91"><a href="#cb1-91" aria-hidden="true" tabindex="-1"></a> <span class="ex"></span> ┃ ┣ 📂polylines</span>
<span id="cb1-92"><a href="#cb1-92" aria-hidden="true" tabindex="-1"></a> <span class="ex"></span> ┃ ┃ ┗ 📜fetchGisLinesThunk.js</span>
<span id="cb1-93"><a href="#cb1-93" aria-hidden="true" tabindex="-1"></a> <span class="ex"></span> ┃ ┣ 📜fetchLocationDevicesThunk.js</span>
<span id="cb1-94"><a href="#cb1-94" aria-hidden="true" tabindex="-1"></a> <span class="ex"></span> ┃ ┣ 📜fetchPriorityConfigThunk.js</span>
<span id="cb1-95"><a href="#cb1-95" aria-hidden="true" tabindex="-1"></a> <span class="ex"></span> ┃ ┗ 📜getDeviceIdByNameThunk.js</span>
<span id="cb1-96"><a href="#cb1-96" aria-hidden="true" tabindex="-1"></a> <span class="ex"></span> ┗ 📂webservice</span>
<span id="cb1-97"><a href="#cb1-97" aria-hidden="true" tabindex="-1"></a> <span class="ex"></span> ┃ ┣ 📜fetchGisLinesStatusThunk.js</span>
<span id="cb1-98"><a href="#cb1-98" aria-hidden="true" tabindex="-1"></a> <span class="ex"></span> ┃ ┣ 📜fetchGisStationsMeasurementsThunk.js</span>
<span id="cb1-99"><a href="#cb1-99" aria-hidden="true" tabindex="-1"></a> <span class="ex"></span> ┃ ┣ 📜fetchGisStationsStaticDistrictThunk.js</span>
<span id="cb1-100"><a href="#cb1-100" aria-hidden="true" tabindex="-1"></a> <span class="ex"></span> ┃ ┣ 📜fetchGisStationsStatusDistrictThunk.js</span>
<span id="cb1-101"><a href="#cb1-101" aria-hidden="true" tabindex="-1"></a> <span class="ex"></span> ┃ ┣ 📜fetchGisSystemStaticThunk.js</span>
<span id="cb1-102"><a href="#cb1-102" aria-hidden="true" tabindex="-1"></a> <span class="ex"></span> ┃ ┗ 📜fetchUserRightsThunk.js</span>
<span id="cb1-103"><a href="#cb1-103" aria-hidden="true" tabindex="-1"></a> <span class="ex"></span> 📜store.js</span>
<span id="cb1-104"><a href="#cb1-104" aria-hidden="true" tabindex="-1"></a></span>
<span id="cb1-105"><a href="#cb1-105" aria-hidden="true" tabindex="-1"></a><span class="ex">services/</span> → API-Kommunikation, Mock-Logik</span>
<span id="cb1-106"><a href="#cb1-106" aria-hidden="true" tabindex="-1"></a><span class="ex">📦services</span></span>
<span id="cb1-107"><a href="#cb1-107" aria-hidden="true" tabindex="-1"></a> <span class="ex"></span> 📂database</span>
<span id="cb1-108"><a href="#cb1-108" aria-hidden="true" tabindex="-1"></a> <span class="ex"></span> ┣ 📂pois</span>
<span id="cb1-109"><a href="#cb1-109" aria-hidden="true" tabindex="-1"></a> <span class="ex"></span> ┃ ┣ 📜addPoiService.js</span>
<span id="cb1-110"><a href="#cb1-110" aria-hidden="true" tabindex="-1"></a> <span class="ex"></span> ┃ ┣ 📜deletePoiService.js</span>
<span id="cb1-111"><a href="#cb1-111" aria-hidden="true" tabindex="-1"></a> <span class="ex"></span> ┃ ┣ 📜fetchPoiDataByIdService.js</span>
<span id="cb1-112"><a href="#cb1-112" aria-hidden="true" tabindex="-1"></a> <span class="ex"></span> ┃ ┣ 📜fetchPoiDataService.js</span>
<span id="cb1-113"><a href="#cb1-113" aria-hidden="true" tabindex="-1"></a> <span class="ex"></span> ┃ ┣ 📜fetchPoiIconsDataService.js</span>
<span id="cb1-114"><a href="#cb1-114" aria-hidden="true" tabindex="-1"></a> <span class="ex"></span> ┃ ┣ 📜fetchPoiTypService.js</span>
<span id="cb1-115"><a href="#cb1-115" aria-hidden="true" tabindex="-1"></a> <span class="ex"></span> ┃ ┗ 📜updatePoiService.js</span>
<span id="cb1-116"><a href="#cb1-116" aria-hidden="true" tabindex="-1"></a> <span class="ex"></span> ┣ 📂polylines</span>
<span id="cb1-117"><a href="#cb1-117" aria-hidden="true" tabindex="-1"></a> <span class="ex"></span> ┃ ┗ 📜fetchGisLinesService.js</span>
<span id="cb1-118"><a href="#cb1-118" aria-hidden="true" tabindex="-1"></a> <span class="ex"></span> ┣ 📜fetchDeviceNameByIdService.js</span>
<span id="cb1-119"><a href="#cb1-119" aria-hidden="true" tabindex="-1"></a> <span class="ex"></span> ┣ 📜fetchLocationDevicesService.js</span>
<span id="cb1-120"><a href="#cb1-120" aria-hidden="true" tabindex="-1"></a> <span class="ex"></span> ┣ 📜fetchPriorityConfigService.js</span>
<span id="cb1-121"><a href="#cb1-121" aria-hidden="true" tabindex="-1"></a> <span class="ex"></span> ┣ 📜getDeviceIdByNameService.js</span>
<span id="cb1-122"><a href="#cb1-122" aria-hidden="true" tabindex="-1"></a> <span class="ex"></span> ┗ 📜updateLocationInDatabaseService.js</span>
<span id="cb1-123"><a href="#cb1-123" aria-hidden="true" tabindex="-1"></a> <span class="ex"></span> 📂utils</span>
<span id="cb1-124"><a href="#cb1-124" aria-hidden="true" tabindex="-1"></a> <span class="ex"></span> ┗ 📜fetchWithTimeout.js</span>
<span id="cb1-125"><a href="#cb1-125" aria-hidden="true" tabindex="-1"></a> <span class="ex"></span> 📂webservice</span>
<span id="cb1-126"><a href="#cb1-126" aria-hidden="true" tabindex="-1"></a> <span class="ex"></span> ┣ 📜fetchGisLinesStatusService.js</span>
<span id="cb1-127"><a href="#cb1-127" aria-hidden="true" tabindex="-1"></a> <span class="ex"></span> ┣ 📜fetchGisStationsMeasurementsService.js</span>
<span id="cb1-128"><a href="#cb1-128" aria-hidden="true" tabindex="-1"></a> <span class="ex"></span> ┣ 📜fetchGisStationsStaticDistrictService.js</span>
<span id="cb1-129"><a href="#cb1-129" aria-hidden="true" tabindex="-1"></a> <span class="ex"></span> ┣ 📜fetchGisStationsStatusDistrictService.js</span>
<span id="cb1-130"><a href="#cb1-130" aria-hidden="true" tabindex="-1"></a> <span class="ex"></span> ┣ 📜fetchGisSystemStaticService.js</span>
<span id="cb1-131"><a href="#cb1-131" aria-hidden="true" tabindex="-1"></a> <span class="ex"></span> ┗ 📜fetchUserRightsService.js</span></code></pre></div>
<hr />
<p><a href="../README.md">Zurück zur Übersicht</a></p>

View File

@@ -0,0 +1,23 @@
<!-- /docs/guide/redux-zustand.md-->
<h2 id="zustand-redux">🧠 Zustand: Redux</h2>
<p>Die Anwendung verwendet vollständig <strong>Redux Toolkit</strong>
für die globale Zustandverwaltung.</p>
<ul>
<li>Dynamische Gerätegruppen (Layer) werden automatisch über
<code>IdSystem</code> aus <code>GisSystemStatic</code>
initialisiert</li>
<li>Layer-Steuerung erfolgt über <code>system-&lt;IdSystem&gt;</code>
Keys im Redux <code>mapLayersSlice</code></li>
<li>Marker für Geräte werden über Vergleich <code>System</code> ↔︎
<code>IdSystem</code> angezeigt</li>
</ul>
<h3 id="gründe-für-redux">Gründe für Redux :</h3>
<ul>
<li>Bessere Nachvollziehbarkeit durch zentrale Store-Struktur</li>
<li>Unterstützung für DevTools, Logging, Debugging</li>
<li>Einheitliche Behandlung von Status, auch bei komplexen
Komponenten</li>
</ul>
<p>➡ Neue Features bitte ausschließlich mit Redux umsetzen!</p>
<hr />
<p><a href="../README.md">Zurück zur Übersicht</a></p>

View File

@@ -0,0 +1,46 @@
<h1 id="lokale-entwicklung-mit-nodemap">🧑‍💻 Lokale Entwicklung mit
NodeMap</h1>
<p>Diese Anleitung richtet sich an Entwickler, die NodeMap lokal
weiterentwickeln möchten.</p>
<hr />
<h2 id="voraussetzungen">Voraussetzungen</h2>
<ul>
<li>Node.js v18+</li>
<li>NPM</li>
<li>Chrome / Edge / Firefox</li>
</ul>
<hr />
<h2 id="schritte">Schritte</h2>
<div class="sourceCode" id="cb1"><pre
class="sourceCode bash"><code class="sourceCode bash"><span id="cb1-1"><a href="#cb1-1" aria-hidden="true" tabindex="-1"></a><span class="ex">npm</span> install</span>
<span id="cb1-2"><a href="#cb1-2" aria-hidden="true" tabindex="-1"></a><span class="ex">npm</span> run dev</span></code></pre></div>
<hr />
<h2 id="optionen">Optionen</h2>
<ul>
<li><strong>Mockdaten-Modus aktivieren:</strong></li>
</ul>
<pre class="env"><code>NEXT_PUBLIC_USE_MOCKS=true</code></pre>
<ul>
<li><strong>Umgebungsvariablen lokal definieren:</strong></li>
</ul>
<p>Datei <code>.env.development</code> mit Inhalten wie:</p>
<pre><code>NEXT_PUBLIC_API_URL=http://localhost:3000
NEXT_PUBLIC_USE_MOCKS=true</code></pre>
<hr />
<h2 id="debugging">Debugging</h2>
<ul>
<li>Verwende <code>console.log</code> in Komponenten oder
Redux-Slices</li>
<li>Browser-DevTools &amp; Redux DevTools empfohlen</li>
</ul>
<hr />
<h2 id="weitere-dokumentation">Weitere Dokumentation</h2>
<ul>
<li>Projektstruktur: <a
href="project-structure.md">project-structure.md</a></li>
<li>Webservices: <a href="webservices.md">webservices.md</a></li>
<li>Zustandverwaltung: <a
href="redux-zustand.md">redux-zustand.md</a></li>
</ul>
<hr />
<p><a href="../README.md">Zurück zur Übersicht</a></p>

View File

@@ -0,0 +1,22 @@
<!-- /docs/quide/user-guide.md -->
<h2 id="benutzeranleitung">🧭 Benutzeranleitung</h2>
<ul>
<li><strong>Station öffnen:</strong> Rechte Maustaste → “Station öffnen”
oder “Station öffnen (Tab)”</li>
<li><strong>POI hinzufügen:</strong> Rechtsklick → “POI hinzufügen” →
Formular ausfüllen</li>
<li><strong>POI bearbeiten/löschen:</strong> Kontextmenü verwenden</li>
<li><strong>POI verschieben:</strong> Drag &amp; Drop des Markers,
automatische DB-Aktualisierung</li>
<li><strong>Koordinaten anzeigen:</strong> Kontextmenüoption nutzen</li>
<li><strong>Zoom:</strong> Mausrad oder Kontextmenüoption</li>
<li><strong>Layer steuern:</strong> GIS-Geräte-Layer (z.B. TALAS, WAGO,
GMA) ein-/ausblenden über Checkboxen im rechten Panel
(<code>MapLayersControlPanel</code>)</li>
<li><strong>Station auswählen:</strong> Dropdown oben rechts</li>
<li><strong>Zentrieren:</strong> Rechtsklick → “Hier zentrieren”</li>
<li><strong>Geräte-Kontextmenü:</strong> Rechtsklick auf Marker →
„Station öffnen (Tab)“</li>
</ul>
<hr />
<p><a href="../README.md">Zurück zur Übersicht</a></p>

View File

@@ -0,0 +1,67 @@
<h2 id="webservice-anbindung-backend-talas.web">📡 Webservice-Anbindung
(Backend: TALAS.web)</h2>
<p>NodeMap verwendet verschiedene Webservices, die von <strong>TALAS
V5/TALAS.web</strong> im IIS bereitgestellt werden.<br />
Diese Services liefern dynamische GIS-, Geräte- und Statusdaten für die
MapComponent.</p>
<h3 id="url-des-webservice">URL des Webservice:</h3>
<pre><code>http://localhost/talas5/ClientData/WebServiceMap.asmx</code></pre>
<blockquote>
<p>🔧 In <code>.env.production</code> oder <code>config.js</code> muss
die Adresse je nach Umgebung angepasst werden (z.B.
<code>http://10.10.0.13/talas5/...</code>)</p>
</blockquote>
<h3 id="verfügbare-methoden-auszug">Verfügbare Methoden (Auszug):</h3>
<table>
<colgroup>
<col style="width: 39%" />
<col style="width: 60%" />
</colgroup>
<thead>
<tr>
<th>Endpunkt</th>
<th>Zweck / Datenquelle</th>
</tr>
</thead>
<tbody>
<tr>
<td><code>CablesStatic</code></td>
<td>Liste aller Stränge</td>
</tr>
<tr>
<td><code>GetIconsStatic</code></td>
<td>Liste aller Icons</td>
</tr>
<tr>
<td><code>GisLinesStatus</code></td>
<td>Liste aller Status der Linien</td>
</tr>
<tr>
<td><code>GisStationsMeasurements</code></td>
<td>Liste aller Messungen der Geräte</td>
</tr>
<tr>
<td><code>GisStationsStaticDistrict</code></td>
<td>Liste aller Geräte einer bestimmten Karte</td>
</tr>
<tr>
<td><code>GisStationsStatusDistrict</code></td>
<td>Liste aller Statis der Geräte</td>
</tr>
<tr>
<td><code>GisSystemStatic</code></td>
<td>Liste aller angezeigten Systeme</td>
</tr>
</tbody>
</table>
<p>Die Webservices liefern JSON und werden im Frontend über
<code>services/*.js</code> abgefragt.<br />
Die Daten werden verarbeitet, zwischengespeichert und z.T. über Redux
in der Karte dargestellt.</p>
<p>➡ Damit alles funktioniert, müssen:</p>
<ul>
<li>der IIS laufen</li>
<li>der <code>WebServiceMap.asmx</code> erreichbar sein</li>
</ul>
<hr />
<p><a href="../README.md">Zurück zur Übersicht</a></p>

View File

@@ -0,0 +1,11 @@
<h1 id="übersicht-docshooks">📄 Übersicht: docs/hooks</h1>
<ul>
<li><a href="useCreateAndSetDevices.md">useCreateAndSetDevices</a></li>
<li><a href="useDynamicMarkerLayers.md">useDynamicMarkerLayers</a></li>
<li><a href="useLayerVisibility.md">useLayerVisibility</a></li>
<li><a href="useLineData.md">useLineData</a></li>
<li><a href="useMapComponentState.md">useMapComponentState</a></li>
<li><a href="useMarkerLayers.md">useMarkerLayers</a></li>
<li><a
href="usePolylineTooltipLayer.md">usePolylineTooltipLayer</a></li>
</ul>

Some files were not shown because too many files have changed in this diff Show More