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

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

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

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

This change addresses the requirement to hide stations/devices when their corresponding
IdSystem in GisSystemStatic.json has Allow=0, improving the map display by showing
only authorized/allowed systems to users.
2025-07-29 08:11:31 +02:00
ISA
37aec649ee feat: Geräte/Stations-Marker werden nur für Systeme mit Allow=1 angezeigt
- Geräte mit Allow=0 in GisSystemStatic werden auf der Karte ausgeblendet
- Daten bleiben unverändert, nur die Anzeige/Zeichnung ist
2025-07-28 16:21:48 +02:00
ISA
9d7a696f91 feat: Add persistent localStorage for Kabelstrecken (polylines) visibility
- Add kabelstreckenVisible state with localStorage persistence
- Implement dual localStorage variables (kabelstreckenVisible + polylineVisible) for compatibility
- Add event system for cross-component polyline visibility updates
- Update MapComponent to listen for polylineVisibilityChanged events
- Ensure polylines display correctly on browser reload
- Migrate from Redux-only state to localStorage-first approach
- Add comprehensive debug logging for troubleshooting

Fixes issue where Kabelstrecken checkbox state was lost on page reload
and polylines were not displayed until manual toggle.
2025-07-25 13:09:18 +02:00
ISA
56a2e305f1 feat: Dienst Dateien hinzugefügt 2025-07-25 10:15:38 +02:00
ISA
e1bfe7496b fix: Kabelstrecken-Checkbox und Anzeige an TALAS Allow-Status gekoppelt
- Checkbox für Kabelstrecken (polylines) wird deaktiviert, wenn TALAS (IdSystem: 1) Allow: 0 ist
- Polylinien werden auf der Karte nur angezeigt, wenn Allow: 1 für TALAS gesetzt ist
- Synchronisation zwischen UI
2025-07-25 10:08:45 +02:00
ISA
4e396a1b10 docs(gisPolylines): ✍️ README mit Datenfluss-Diagramm und Erklärung ergänzt
- Datenfluss zwischen Redux Slices, Thunks, Services und Komponenten visualisiert
- Mermaid-UML hinzugefügt zur Darstellung der Architektur
- Technischer Hintergrund für Einsteiger dokumentiert
- Hinweise zur Datenherkunft (Datenbank vs. TALAS.web WebService)
- Hilfe zur Fehlerbehebung z. B. bei fehlendem LD_Name im Tooltip
2025-06-25 11:02:43 +02:00
ISA
bc331eb7b2 docs: Karten Material Link in README.md hinzugefügt 2025-06-25 09:23:43 +02:00
ISA
016e1a927d refactor: Tooltip-Generierung für Linien ausgelagert und Anzeige von Stationsnamen (LD_Name) hinzugefügt
- `generateLineTooltipContent` in eigene Datei extrahiert
- `useLineData` angepasst, um Stationen aus `gisStationsStaticDistrict` zu übergeben
- Anzeige der Station im Tooltip nun über `LD_Name` statt `matchingLine.name`
- Bugfix: "Station: N/A" wird nun korrekt angezeigt
2025-06-25 08:43:26 +02:00
ISA
89403164c6 refactor: Tooltip-HTML-Generierung aus useLineData ausgelagert
- Funktion `generateLineTooltipContent` aus `useLineData.js` extrahiert
- Neue Datei: `components/gisPolylines/tooltip/generateLineTooltipContent.js`
- Trennung von Logik (Hook) und Darstellung (Tooltip-HTML)
- Verbesserte Lesbarkeit und Wiederverwendbarkeit der Tooltip-Generierung
2025-06-25 07:49:52 +02:00
ISA
7c4fbc3988 refactor: Komponenten-Hooks strukturiert und in passende UI-Unterverzeichnisse verschoben
- useLineData.js → components/gisPolylines/tooltip/
- useLayerVisibility.js → components/mapLayersControlPanel/hooks/
- useAreaMarkersLayer.js → components/area/hooks/
- useDynamicDeviceLayers.js → components/devices/hooks/
- useDataUpdater.js & useMapComponentState.js → components/hooks/

💡 Ziel: Alle UI-bezogenen Hooks an logische Stellen verschoben, um Wartbarkeit zu verbessern.
🔍 Vorteil: Schnellere Navigation bei UI-Fehlern oder Layout-Anpassungen.
2025-06-25 07:21:05 +02:00
ISA
4070429193 docs: 2025-06-24 15:07:00 +02:00
ISA
b6c6fad3b3 docs: uiWidgets icon bug in Gitea 2025-06-24 12:29:51 +02:00
ISA
12fec717d8 docs: uiWidget 2025-06-24 12:01:49 +02:00
ISA
fe3cc48769 docs: Version Info Moda 2025-06-24 11:46:53 +02:00
ISA
82963ead23 docs: VersionInfoModal 2025-06-24 11:29:47 +02:00
416 changed files with 14593 additions and 3214 deletions

View File

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

View File

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

View File

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

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

View File

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

BIN
README.pdf Normal file

Binary file not shown.

6
Start-Dev.ps1 Normal file
View File

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

1
StartNodeApp.bat Normal file
View File

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

52
TODO.md
View File

@@ -51,3 +51,55 @@ die Daten von DB auch mit WebSocket gelöst werden
- [x] TODO: POI bearbeiten funktioniert es nicht
- [ ] TODO: Linien Links noch mit Port 3000
- [ ] TODO: Checkliste für README.md vorbereiten
## 🐞 Aktuelle Bugs
- [ ] Tooltip zeigt `unknown` bei bestimmten Linien
→ prüfen in `setupPolylines.js`, ob alle Geräte korrekt benannt sind
- [ ] Tooltip zeigt `N/A` bei Station → untersuchen, ob `station.Name` in `createAndSetDevices.js`
gesetzt wird
## ✨ Ideen & Verbesserungen
- [ ] ESC-Taste schließt `VersionInfoModal`
- [ ] Zurück-Link in jedem `.md` Footer automatisieren
## 🧹 Technische Schulden
- [ ] Redundante Kontextmenülogik auflösen
- [ ] Bessere Trennung zwischen Mock- und Live-API in Service-Funktionen
---
28.07.2025 IdSystem 11 GMA Glätemeldeanlagen, werden neu neu laden das Browser nich mehr geladen in
DB maps idsystem ändern und testen
# 12.09.2025
Die aktuelle Ansicht ist bei kleineren Auflösungen unübersichtlich bzw. es wird zuviel von der
eigentlichen Karte verdeckt. Unquittierter Alarm, critical
Zu Marker zoomen
Station suchen
- [ ] TODO: Unquittierter Alarm, critical 🚨 Alarm
- [ ] TODO: Zu Marker zoomen: Dropdown-Menu Station auswählen und hinein zoomen bis zu ausgewählte
in einem betimmten Zoom-Stufe der Leaflet (OSM) Station mit flyto in Leaflet 📍 POI
- [ ] TODO: Station suchen: CoordinateInput.js Modal soll über einem Icon 'Suche / Lupe' oben rechts
eingeblendet und ausgeblendet um mehr von der Karte zu sehen 🔍 Suche
- [ ] TODO: Editiermodus: EditMode Stift Icon aktivieren und deaktivieren um POI Position ändern zu
können wenn der User Berechtigung hat ✏️ Edit
- [ ] TODO: Vergrössern: Maximieren Icon Button um rauszoomen zu einem bestimmten Bereich, z.B.
Deutschland Karte im Fenster sichtbar ⬜ Fenster maximieren
- [ ] TODO: Ebenen (Openstreetmap): Stack/Stapel/Ebenen Icon um den Ansicht der Karten zu ändern 🗂️
Stapel
- [ ] TODO: Menü öffenen mit Kiste der Systeme: MapLayerControlPanel.js Modal soll über einem Icon
'Hamburger menu' oben rechts eingeblendet und ausgeblendet um mehr von der Karte zu sehen ☰
Hamburger-Menü
- [ ] TODO: Info Karte: VersionInfoModal.js Modal soll über einem Icon 'Info' oben rechts
eingeblendet und ausgeblendet um mehr von der Karte zu sehen Info
https://www.openstreetmap.org/#map=13/51.80097/9.33495&layers=P

View File

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

View File

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

View File

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

View File

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

View File

@@ -0,0 +1,40 @@
// /components/gisPolylines/tooltip/generateLineTooltipContent.js
// KEIN useSelector hier!
export function generateLineTooltipContent(statis, matchingLine, values, stations) {
const station = stations.find(s => s.IdLD === statis.IdLD);
const stationName = station?.LD_Name || "N/A";
const messageDisplay = values.messages
.map(
msg =>
`<span class="inline-block text-gray-800">
<span class="inline-block w-2 h-2 rounded-full mr-2" style="background-color: ${msg.prioColor};"></span>
${msg.message}
</span><br>`
)
.join("");
return `
<div class="bg-white rounded-lg m-0 p-2 w-[210px]">
<span class="text-lg font-semibold text-gray-900">${statis.ModulName || "Unknown"}</span><br>
<span class="text-md font-bold text-gray-800">${statis.ModulTyp || "N/A"}</span><br>
<span class="text-md font-bold text-gray-800">Slot: ${statis.Modul || "N/A"}</span><br>
<span class="text-md font-bold text-gray-800">Station: ${stationName}</span>
<br>
<div style="max-width: 100%; overflow-wrap: break-word; word-break: break-word; white-space: normal;">
${messageDisplay}
</div>
<br>
${
values.messwert
? `<span class="inline-block text-gray-800">Messwert: ${values.messwert}</span><br>`
: ""
}
${
values.schleifenwert
? `<span class="inline-block text-gray-800">Schleifenwert: ${values.schleifenwert}</span>`
: ""
}
</div>
`;
}

View File

@@ -0,0 +1,91 @@
// /components/gisPolylines/tooltip/useLineData.js
import { useEffect, useState } from "react";
import { useSelector, useDispatch } from "react-redux";
import { selectGisLinesStatusFromWebservice } from "@/redux/slices/webservice/gisLinesStatusSlice";
import { fetchGisLinesThunk } from "@/redux/thunks/database/polylines/fetchGisLinesThunk";
import { fetchGisLinesStatusThunk } from "@/../redux/thunks/webservice/fetchGisLinesStatusThunk";
import { generateLineTooltipContent } from "./generateLineTooltipContent";
import { selectGisStationsStaticDistrict } from "@/redux/slices/webservice/gisStationsStaticDistrictSlice";
const useLineData = () => {
const dispatch = useDispatch();
const { data: statisData } = useSelector(selectGisLinesStatusFromWebservice);
const linesData = useSelector(state => state.gisLinesFromDatabase.data);
const [lineColors, setLineColors] = useState({});
const [tooltipContents, setTooltipContents] = useState({});
const stations = useSelector(selectGisStationsStaticDistrict)?.Points ?? [];
useEffect(() => {
dispatch(fetchGisLinesThunk());
dispatch(fetchGisLinesStatusThunk());
}, [dispatch]);
useEffect(() => {
if (!statisData || !Array.isArray(statisData)) return;
const colorsByModule = {};
const newTooltipContents = {};
const valueMap = {};
const sortedStatis = [...statisData].sort((a, b) => a.Level - b.Level);
sortedStatis.forEach(statis => {
const key = `${statis.IdLD}-${statis.Modul}`;
if (!valueMap[key]) {
valueMap[key] = {
messages: [],
messwert: undefined,
schleifenwert: undefined,
};
}
if (
statis.DpName.endsWith("_Messwert") &&
statis.Value !== "True" &&
!valueMap[key].messwert
) {
valueMap[key].messwert = statis.Value;
}
if (statis.DpName.endsWith("_Schleifenwert") && !valueMap[key].schleifenwert) {
valueMap[key].schleifenwert = statis.Value;
}
if (statis.Message && statis.Message !== "?") {
valueMap[key].messages.push({
message: statis.Message,
prioColor:
statis.PrioColor && statis.PrioColor !== "#ffffff" ? statis.PrioColor : "green",
});
}
});
sortedStatis.forEach(statis => {
const key = `${statis.IdLD}-${statis.Modul}`;
const matchingLine = linesData.find(
item => item.idLD === statis.IdLD && item.idModul === statis.Modul
);
if (matchingLine) {
const values = valueMap[key];
colorsByModule[key] = values.messages.length > 0 ? values.messages[0].prioColor : "green";
newTooltipContents[key] = generateLineTooltipContent(
statis,
matchingLine,
values,
stations
);
}
});
setLineColors(colorsByModule);
setTooltipContents(newTooltipContents);
}, [statisData, linesData, stations]);
return { lineColors, tooltipContents };
};
export default useLineData;

View File

@@ -4,11 +4,11 @@ import { useDispatch } from "react-redux";
// import type { AppDispatch } from "../redux/store";
// ✅ Thunks aus korrektem Pfad importieren
import { fetchGisLinesStatusThunk } from "../redux/thunks/webservice/fetchGisLinesStatusThunk";
import { fetchGisStationsMeasurementsThunk } from "../redux/thunks/webservice/fetchGisStationsMeasurementsThunk";
import { fetchGisStationsStaticDistrictThunk } from "../redux/thunks/webservice/fetchGisStationsStaticDistrictThunk";
import { fetchGisStationsStatusDistrictThunk } from "../redux/thunks/webservice/fetchGisStationsStatusDistrictThunk";
import { fetchGisSystemStaticThunk } from "../redux/thunks/webservice/fetchGisSystemStaticThunk";
import { fetchGisLinesStatusThunk } from "@/redux/thunks/webservice/fetchGisLinesStatusThunk";
import { fetchGisStationsMeasurementsThunk } from "@/redux/thunks/webservice/fetchGisStationsMeasurementsThunk";
import { fetchGisStationsStaticDistrictThunk } from "@/redux/thunks/webservice/fetchGisStationsStaticDistrictThunk";
import { fetchGisStationsStatusDistrictThunk } from "@/redux/thunks/webservice/fetchGisStationsStatusDistrictThunk";
import { fetchGisSystemStaticThunk } from "@/redux/thunks/webservice/fetchGisSystemStaticThunk";
const REFRESH_INTERVAL = parseInt(process.env.NEXT_PUBLIC_REFRESH_INTERVAL || "10000");

View File

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,23 +6,38 @@ 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";
import StartIcon from "@/components/gisPolylines/icons/StartIcon.js";
import EndIcon from "@/components/gisPolylines/icons/EndIcon.js";
import CircleIcon from "@/components/gisPolylines/icons/CircleIcon.js";
import { restoreMapSettings, checkOverlappingMarkers } from "../../utils/mapUtils.js";
import addItemsToMapContextMenu from "@/components/contextmenu/useMapContextMenu.js";
import useAreaMarkersLayer from "@/hooks/useAreaMarkersLayer.js";
import useAreaMarkersLayer from "@/components/area/hooks/useAreaMarkersLayer.js";
import { setupPolylines } from "@/utils/polylines/setupPolylines.js";
import { setupPOIs } from "@/utils/setupPOIs.js";
import useLineData from "@/hooks/useLineData.js";
import { useMapComponentState } from "@/hooks/useMapComponentState.js";
import useLineData from "@/components/gisPolylines/tooltip/useLineData.js";
import { useMapComponentState } from "@/components/hooks/useMapComponentState.js";
import CoordinatePopup from "@/components/contextmenu/CoordinatePopup.js";
//----------Ui Widgets----------------
import MapLayersControlPanel from "@/components/uiWidgets/mapLayersControlPanel/MapLayersControlPanel.js";
import 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";
@@ -35,7 +50,9 @@ import { useSelector, useDispatch } from "react-redux";
import { setSelectedPoi } from "@/redux/slices/database/pois/selectedPoiSlice.js";
import { setDisabled } from "@/redux/slices/database/polylines/polylineEventsDisabledSlice.js";
import { setMapId, setUserId } from "@/redux/slices/urlParameterSlice";
import { selectMapLayersState } from "@/redux/slices/mapLayersSlice";
import { selectMapLayersState, setLayerVisibility } from "@/redux/slices/mapLayersSlice";
import { 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";
@@ -54,6 +71,7 @@ import {
import {
selectPolylineVisible,
setPolylineVisible,
initializePolylineFromLocalStorageThunk,
} from "@/redux/slices/database/polylines/polylineLayerVisibleSlice.js";
import { selectGisStationsStaticDistrict } from "@/redux/slices/webservice/gisStationsStaticDistrictSlice.js";
import {
@@ -74,9 +92,9 @@ import { fetchPoiIconsDataThunk } from "@/redux/thunks/database/pois/fetchPoiIco
import { fetchPoiTypThunk } from "@/redux/thunks/database/pois/fetchPoiTypThunk.js";
import { updateAreaThunk } from "@/redux/thunks/database/area/updateAreaThunk";
import useDynamicDeviceLayers from "@/hooks/useDynamicDeviceLayers.js";
import useDynamicDeviceLayers from "@/components/devices/hooks/useDynamicDeviceLayers.js";
import useDataUpdater from "@/hooks/useDataUpdater";
import useDataUpdater from "@/components/hooks/useDataUpdater.js";
import { cleanupPolylinesForMemory } from "@/utils/polylines/cleanupPolylinesForMemory";
import { cleanupMarkers } from "@/utils/common/cleanupMarkers";
import { monitorHeapAndReload } from "@/utils/common/monitorMemory";
@@ -84,6 +102,7 @@ import { monitorHeapWithRedux } from "@/utils/common/monitorMemory";
import { io } from "socket.io-client";
import { setGisStationsStaticDistrict } from "@/redux/slices/webservice/gisStationsStaticDistrictSlice.js";
import { getDebugLog } from "../../utils/configUtils";
//-----------------------------------------------------------------------------------------------------
const MapComponent = ({ locations, onLocationUpdate, lineCoordinates }) => {
//-------------------------------
@@ -96,6 +115,14 @@ const MapComponent = ({ locations, onLocationUpdate, lineCoordinates }) => {
const countdownActive = useSelector(state => state.polylineContextMenu.countdownActive);
const isPolylineContextMenuOpen = useSelector(state => state.polylineContextMenu.isOpen);
const polylineVisible = useSelector(selectPolylineVisible);
const polylineInitialized = useSelector(state => state.polylineLayerVisible.isInitialized);
const GisSystemStatic = useSelector(selectGisSystemStatic);
// Prüfen, ob TALAS (IdSystem 1) erlaubt ist
const isTalasAllowed = Array.isArray(GisSystemStatic)
? GisSystemStatic.some(
system => system.IdSystem === 1 && system.Allow === 1 && system.Map === 1
)
: false;
const isPoiTypLoaded = useSelector(state => state.poiTypes.status === "succeeded");
const statusMeasurements = useSelector(state => state.gisStationsMeasurements.status);
@@ -106,8 +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);
const GisSystemStatic = useSelector(selectGisSystemStatic);
// 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) || {};
@@ -117,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);
@@ -135,10 +196,75 @@ 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
if (typeof window !== "undefined" && window.userToggledPolyline === undefined) {
window.userToggledPolyline = false;
}
//-----userRights----------------
const isRightsLoaded = useSelector(
@@ -170,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);
@@ -191,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 => {
@@ -200,6 +360,16 @@ const MapComponent = ({ locations, onLocationUpdate, lineCoordinates }) => {
}
};
//-----------------------------Map Initialisierung----------------
// Default map options for Leaflet
const mapOptions = {
center: currentCenter,
zoom: currentZoom,
zoomControl: true,
contextmenu: true,
contextmenuWidth: 180,
contextmenuItems: [],
};
useInitializeMap(
map,
mapRef,
@@ -208,10 +378,85 @@ const MapComponent = ({ locations, onLocationUpdate, lineCoordinates }) => {
setMenuItemAdded,
addItemsToMapContextMenu,
hasRights,
value => dispatch(setDisabled(value))
value => dispatch(setDisabled(value)),
mapOptions // pass mapOptions
);
//-------------------------React Hooks--------------------------------
// URL-Parameter extrahieren und kartenspezifische localStorage-Keys verwenden
useEffect(() => {
// Immer beim Umschalten der Kabelstrecken-Checkbox prüfen!
if (map) {
if (!polylineVisible) {
map.eachLayer(layer => {
// Entferne alle Marker mit StartIcon, EndIcon oder CircleIcon (Stützpunkt)
if (
layer instanceof L.Marker &&
layer.options &&
layer.options.icon &&
(layer.options.icon === StartIcon ||
layer.options.icon === EndIcon ||
layer.options.icon === CircleIcon)
) {
map.removeLayer(layer);
}
});
}
}
// Initialisierung der Layer-Visibility und Polyline-Redux-State nur beim Mount
if (typeof window !== "undefined") {
const params = new URLSearchParams(window.location.search);
const mapId = params.get("m");
const userId = params.get("u");
if (mapId && userId) {
// Speichere aktuelle Map- und User-ID
localStorage.setItem("currentMapId", mapId);
localStorage.setItem("currentUserId", userId);
// Kartenspezifischer localStorage-Key
const mapStorageKey = `mapLayersVisibility_m${mapId}_u${userId}`;
const storedMapLayersVisibility = localStorage.getItem(mapStorageKey);
if (storedMapLayersVisibility) {
try {
const parsedVisibility = JSON.parse(storedMapLayersVisibility);
// Nur initial setzen, wenn Nutzer noch nicht manuell eingegriffen hat
if (!userToggledPolyline.current) {
Object.keys(parsedVisibility).forEach(key => {
dispatch(setLayerVisibility({ layer: key, visibility: parsedVisibility[key] }));
});
console.log(
`🔄 mapLayersVisibility für Map ${mapId}/User ${userId} geladen:`,
parsedVisibility
);
}
} catch (error) {
console.error("❌ Fehler beim Laden von mapLayersVisibility:", error);
}
} else {
console.log(
`📝 Keine gespeicherten Einstellungen für Map ${mapId}/User ${userId} gefunden`
);
}
// Redux Polyline Sichtbarkeit initialisieren (map/user spezifisch)
if (!userToggledPolyline.current) {
dispatch(initializePolylineFromLocalStorageThunk());
}
}
}
}, [dispatch, polylineVisible, map]);
// Callback für Checkbox-Umschaltung (Kabelstrecken)
const handlePolylineCheckboxChange = useCallback(
checked => {
if (typeof window !== "undefined") {
window.userToggledPolyline = true;
}
dispatch(setPolylineVisible(checked));
},
[dispatch]
);
useEffect(() => {
if (linesData && Array.isArray(linesData)) {
const transformed = linesData.map(item => ({
@@ -222,6 +467,7 @@ const MapComponent = ({ locations, onLocationUpdate, lineCoordinates }) => {
setLinePositions(transformed);
}
}, [linesData]);
//--------------------------------------------
useEffect(() => {
dispatch(fetchPoiIconsDataThunk());
@@ -309,72 +555,106 @@ const MapComponent = ({ locations, onLocationUpdate, lineCoordinates }) => {
//Tooltip an mouse position anzeigen für die Linien
useEffect(() => {
if (!map) return;
console.log(
"[MapComponent/useEffect] polylineVisible:",
polylineVisible,
"isTalasAllowed:",
isTalasAllowed,
"poiLayerVisible:",
poiLayerVisible
);
// Wenn TALAS nicht erlaubt ist, Polyline-Checkbox und Anzeige deaktivieren
if (!isTalasAllowed) {
cleanupPolylinesForMemory(polylines, map);
setPolylines([]);
return;
}
// Die Sichtbarkeit der Polylines hängt nur noch vom Redux-Slice ab
// vorherige Marker & Polylinien vollständig bereinigen
markers.forEach(marker => {
(Array.isArray(markers) ? markers : []).forEach(marker => {
marker.remove();
});
cleanupPolylinesForMemory(polylines, map);
console.log("[MapComponent/useEffect] Nach cleanupPolylinesForMemory, polylines:", polylines);
// Setze neue Marker und Polylinien mit den aktuellen Daten
const { markers: newMarkers, polylines: newPolylines } = setupPolylines(
map,
linePositions,
lineColors,
tooltipContents,
setNewCoords,
tempMarker,
currentZoom,
currentCenter,
polylineVisible // kommt aus Redux
);
// Setze neue Marker und Polylinien mit den aktuellen Daten (asynchron!)
const updatePolylines = async () => {
if (polylineVisible) {
const { markers: newMarkers, polylines: newPolylines } = await setupPolylines(
map,
linePositions,
lineColors,
tooltipContents,
setNewCoords,
tempMarker,
currentZoom,
currentCenter,
polylineVisible
);
newPolylines.forEach((polyline, index) => {
const tooltipContent =
tooltipContents[`${linePositions[index].idLD}-${linePositions[index].idModul}`] ||
"Die Linie ist noch nicht in Webservice vorhanden oder bekommt keine Daten";
(Array.isArray(newPolylines) ? newPolylines : []).forEach((polyline, index) => {
const tooltipContent =
tooltipContents[`${linePositions[index].idLD}-${linePositions[index].idModul}`] ||
"Die Linie ist noch nicht in Webservice vorhanden oder bekommt keine Daten";
polyline.bindTooltip(tooltipContent, {
permanent: false,
direction: "auto",
sticky: true,
offset: [20, 0],
pane: "tooltipPane",
});
polyline.bindTooltip(tooltipContent, {
permanent: false,
direction: "auto",
sticky: true,
offset: [20, 0],
pane: "tooltipPane",
});
polyline.on("mouseover", e => {
const tooltip = polyline.getTooltip();
if (tooltip) {
const mousePos = e.containerPoint;
const mapSize = map.getSize();
polyline.on("mouseover", e => {
const tooltip = polyline.getTooltip();
if (tooltip) {
const mousePos = e.containerPoint;
const mapSize = map.getSize();
let direction = "right";
let direction = "right";
if (mousePos.x > mapSize.x - 100) {
direction = "left";
} else if (mousePos.x < 100) {
direction = "right";
}
if (mousePos.x > mapSize.x - 100) {
direction = "left";
} else if (mousePos.x < 100) {
direction = "right";
}
if (mousePos.y > mapSize.y - 100) {
direction = "top";
} else if (mousePos.y < 100) {
direction = "bottom";
}
if (mousePos.y > mapSize.y - 100) {
direction = "top";
} else if (mousePos.y < 100) {
direction = "bottom";
}
tooltip.options.direction = direction;
polyline.openTooltip(e.latlng);
tooltip.options.direction = direction;
polyline.openTooltip(e.latlng);
}
});
polyline.on("mouseout", () => {
polyline.closeTooltip();
});
});
cleanupMarkers(markers, oms);
setMarkers(newMarkers);
setPolylines(newPolylines);
console.log("[MapComponent/useEffect] setPolylines (sichtbar):", newPolylines);
} else {
// Entferne wirklich alle Polylinien-Layer von der Karte
if (map) {
map.eachLayer(layer => {
if (layer instanceof L.Polyline) {
map.removeLayer(layer);
}
});
}
});
polyline.on("mouseout", () => {
polyline.closeTooltip();
});
});
cleanupMarkers(markers, oms);
setMarkers(newMarkers);
setPolylines(newPolylines);
cleanupPolylinesForMemory(polylines, map);
setPolylines([]);
console.log("[MapComponent/useEffect] setPolylines ([]), alle Polylinien entfernt");
}
};
updatePolylines();
}, [
map,
linePositions,
@@ -384,6 +664,8 @@ const MapComponent = ({ locations, onLocationUpdate, lineCoordinates }) => {
newCoords,
tempMarker,
polylineVisible,
isTalasAllowed,
poiLayerVisible,
]);
//--------------------------------------------
@@ -391,7 +673,7 @@ const MapComponent = ({ locations, onLocationUpdate, lineCoordinates }) => {
//Test in useEffect
useEffect(() => {
if (map) {
if (process.env.NEXT_PUBLIC_DEBUG_LOG === "true") {
if (getDebugLog()) {
console.log("🗺️ Map-Einstellungen werden wiederhergestellt...");
}
restoreMapSettings(map);
@@ -400,7 +682,7 @@ const MapComponent = ({ locations, onLocationUpdate, lineCoordinates }) => {
//--------------------------------------------
useEffect(() => {
if (map) {
if (process.env.NEXT_PUBLIC_DEBUG_LOG === "true") {
if (getDebugLog()) {
console.log("map in MapComponent: ", map);
}
const handleMapMoveEnd = event => {
@@ -433,7 +715,7 @@ const MapComponent = ({ locations, onLocationUpdate, lineCoordinates }) => {
const station = points.find(s => s.Area_Name === selectedArea);
if (station) {
if (process.env.NEXT_PUBLIC_DEBUG_LOG === "true") {
if (getDebugLog()) {
console.log("📌 Gefundene Station:", station);
}
map.flyTo([station.X, station.Y], 14);
@@ -479,7 +761,7 @@ const MapComponent = ({ locations, onLocationUpdate, lineCoordinates }) => {
//--------------------------------------------
useEffect(() => {
if (map) {
if (process.env.NEXT_PUBLIC_DEBUG_LOG === "true") {
if (getDebugLog()) {
console.log("6- Karteninstanz (map) wurde jetzt erfolgreich initialisiert");
}
}
@@ -492,7 +774,7 @@ const MapComponent = ({ locations, onLocationUpdate, lineCoordinates }) => {
map.whenReady(() => {
timeoutId = setTimeout(() => {
if (map.contextmenu) {
if (process.env.NEXT_PUBLIC_DEBUG_LOG === "true") {
if (getDebugLog()) {
console.log("Contextmenu ist vorhanden");
}
} else {
@@ -523,7 +805,7 @@ const MapComponent = ({ locations, onLocationUpdate, lineCoordinates }) => {
const handleLocationUpdate = async (idLocation, idMap, newCoords) => {
try {
await dispatch(updateAreaThunk({ idLocation, idMap, newCoords })).unwrap();
if (process.env.NEXT_PUBLIC_DEBUG_LOG === "true") {
if (getDebugLog()) {
console.log("Koordinaten erfolgreich aktualisiert:", result);
}
} catch (error) {
@@ -547,14 +829,14 @@ const MapComponent = ({ locations, onLocationUpdate, lineCoordinates }) => {
// Entferne alle Marker aus der Karte
if (!map) return; // Sicherstellen, dass map existiert
areaMarkers.forEach(marker => {
(Array.isArray(areaMarkers) ? areaMarkers : []).forEach(marker => {
if (map.hasLayer(marker)) {
map.removeLayer(marker);
}
});
} else {
// Wenn editMode aktiviert ist, füge die Marker hinzu und aktiviere Dragging
areaMarkers.forEach(marker => {
(Array.isArray(areaMarkers) ? areaMarkers : []).forEach(marker => {
if (!map.hasLayer(marker)) {
marker.addTo(map); // Layer hinzufügen
}
@@ -615,11 +897,10 @@ const MapComponent = ({ locations, onLocationUpdate, lineCoordinates }) => {
}, [dispatch]);
//--------------------------------------------
// Beim ersten Client-Render den Wert aus localStorage laden
useEffect(() => {
const storedPolylineVisible = localStorage.getItem("polylineVisible") === "true";
dispatch(setPolylineVisible(storedPolylineVisible));
}, [dispatch]);
// (Initialisierung erfolgt in MapLayersControlPanel)
//--------------------------------------------
// MapComponent reagiert nicht mehr direkt auf localStorage-Events für polylineVisible
//--------------------------------------------
useEffect(() => {
if (statusStaticDistrict === "idle") {
@@ -693,7 +974,7 @@ const MapComponent = ({ locations, onLocationUpdate, lineCoordinates }) => {
// console.log(`⏳ Redux Countdown: ${countdown} Sekunden`);
if (countdown <= 2) {
if (process.env.NEXT_PUBLIC_DEBUG_LOG === "true") {
if (getDebugLog()) {
console.log("🚀 Kontextmenü wird wegen Countdown < 2 geschlossen.");
}
dispatch(closePolylineContextMenu());
@@ -738,7 +1019,9 @@ const MapComponent = ({ locations, onLocationUpdate, lineCoordinates }) => {
useEffect(() => {
if (poiTypStatus === "succeeded" && Array.isArray(poiTypData)) {
const map = new Map();
poiTypData.forEach(item => map.set(item.idPoiTyp, item.name));
(Array.isArray(poiTypData) ? poiTypData : []).forEach(item =>
map.set(item.idPoiTyp, item.name)
);
setPoiTypMap(map);
}
}, [poiTypData, poiTypStatus]);
@@ -749,30 +1032,6 @@ const MapComponent = ({ locations, onLocationUpdate, lineCoordinates }) => {
}
}, [poiIconsData, poiIconsStatus]);
//-----------------------------------------------------------------
useEffect(() => {
if (!map) return;
const editMode = localStorage.getItem("editMode") === "true";
Object.entries(markerStates).forEach(([systemName, markers]) => {
const isVisible = mapLayersVisibility[systemName];
markers.forEach(marker => {
const hasLayer = map.hasLayer(marker);
if (editMode || !isVisible) {
if (hasLayer) map.removeLayer(marker);
} else {
if (!hasLayer) marker.addTo(map);
}
});
});
// optional für alle zusammen
const allMarkers = Object.values(markerStates)
.filter(entry => Array.isArray(entry))
.flat();
checkOverlappingMarkers(map, allMarkers, plusRoundIcon);
}, [map, markerStates, mapLayersVisibility]);
//----------------------------------------------
useEffect(() => {
@@ -848,7 +1107,45 @@ const MapComponent = ({ locations, onLocationUpdate, lineCoordinates }) => {
}, [GisStationsStaticDistrict]);
const { Points = [] } = useSelector(selectGisStationsStaticDistrict);
useEffect(() => {}, [triggerUpdate]);
//--------------------------------------------------------------------------------
useEffect(() => {
console.log("📊 GisSystemStatic:", GisSystemStatic);
}, [GisSystemStatic]);
useEffect(() => {
if (Array.isArray(GisSystemStatic)) {
(Array.isArray(GisSystemStatic) ? GisSystemStatic : []).forEach(system => {
const key = `system-${system.IdSystem}`;
if (!(key in mapLayersVisibility)) {
dispatch(setLayerVisibility({ key, value: true })); // Sichtbarkeit aktivieren
}
});
}
}, [GisSystemStatic, mapLayersVisibility, dispatch]);
//---------------------------------------------
//--------------------------------------------
// 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 (
<>
@@ -903,28 +1200,136 @@ const MapComponent = ({ locations, onLocationUpdate, lineCoordinates }) => {
)}
</div>
{GisStationsStaticDistrict && GisStationsStaticDistrict.Points?.length > 0 && (
<MapLayersControlPanel className="z-50" />
)}
{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">
<div className="flex justify-between items-center">
<div>
<span className="text-black text-lg font-semibold"> TALAS.Map </span>
<br />
<span className="text-black text-lg">Version {appVersion}</span>
</div>
<div>
<button onClick={openVersionInfoModal}>
<InformationCircleIcon className="text-blue-900 h-8 w-8 pr-1" title="Weitere Infos" />
</button>
{showAppInfoCard && (
<div className="absolute top-16 right-3 w-72 p-4 bg-white rounded-lg shadow-md z-50">
<div className="flex justify-between items-center">
<div>
<span className="text-black text-lg font-semibold"> TALAS.Map </span>
<br />
<span className="text-black text-lg">Version {appVersion}</span>
</div>
<div>
<button onClick={openVersionInfoModal}>
<InfoIcon className="h-8 w-8 pr-1" title="Weitere Infos" />
</button>
</div>
</div>
</div>
</div>
)}
<VersionInfoModal
showVersionInfoModal={showVersionInfoModal}
closeVersionInfoModal={closeVersionInfoModal}

View File

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

View File

@@ -159,7 +159,7 @@ const PoiUpdateModal = ({ onClose, poiData }) => {
>
<div className="flex flex-col mb-4">
<label htmlFor="description" className="block mb-2 font-bold text-sm text-gray-700">
Beschreibung:
Bezeichnung:
</label>
<input
type="text"
@@ -167,7 +167,7 @@ const PoiUpdateModal = ({ onClose, poiData }) => {
name="description"
value={description}
onChange={e => setDescription(e.target.value)}
placeholder="Beschreibung der Station"
placeholder="Bezeichnung eingeben..."
className="block p-2 w-full border-2 border-gray-200 rounded-md text-sm"
/>
</div>

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

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

View File

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

View File

@@ -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."

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

Binary file not shown.

View File

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

View File

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

View File

@@ -4,6 +4,8 @@
Zeigt ein modales Fenster mit Koordinateninformationen an, z.B. aus einem Kontextmenü heraus.
![CoordinatePopup](../../screenshots/CoordinatePopup.png)
## Features
- Darstellung eines Koordinatenwerts (`lat,lng`)

View File

@@ -2,9 +2,11 @@
# 🖱️ `contextmenu/` Kontextmenü-Komponenten
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.
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.
---
## ![useMapContextMenu](../../screenshots/useMapContextMenu.png)
## 📂 Enthaltene Dateien

View File

@@ -5,6 +5,8 @@
Initialisiert Kontextmenüeinträge für die Leaflet-Karte.
Wird typischerweise in `initializeMap()` oder `MapComponent` verwendet.
![useMapContextMenu](../../screenshots/useMapContextMenu.png)
## Kontextmenüeinträge
| Eintrag | Funktion |

View File

@@ -4,6 +4,8 @@
Ein einfaches benutzerdefiniertes Kontextmenü zur Interaktion mit Linien (Polylinien) auf der Karte.
![GIS Ployline contextmenu](../../screenshots/PolylineContextMenu.png)
## Zweck
Das Menü erlaubt folgende Interaktionen:

View File

@@ -1,3 +1,124 @@
<!-- /docs/components/gisPolylines/README.md -->
# 📄 Übersicht: docs/components/gisPolylines
- [PolylineContextMenu](PolylineContextMenu.md)
Diese Komponente verwaltet die Darstellung und Interaktion von GIS-Polylinien in der
Leaflet-Karte.
Sie kombiniert Statusdaten, statische Linien und Stationsdaten zu Tooltips und Farben.
---
## 🧬 Data Flow Diagram
Dieses Diagramm zeigt den **Datenfluss** zwischen den Redux-Slices, Thunks, Service-Funktionen und
den React-Komponenten,
die für die Anzeige der **GIS-Polylinien** zuständig sind.
### 📖 Ablauf erklärt
1. **Beim Laden der Seite** ruft die Hook `useLineData` mehrere Thunks auf:
- Diese laden Linien, Statusdaten und Stationsdaten über entsprechende Service-Funktionen.
2. Die Thunks speichern die geladenen Daten in Redux-Slices.
3. `useLineData` liest diese Redux-Daten aus und kombiniert sie:
- Zuordnung nach `idLD` und `Modul`
4. Daraus entsteht:
- Eine Prioritätsfarbe für die Linie
- Ein Tooltip-HTML mit Modulname, Slot, Station (LD_Name) und Statusmeldungen.
5. Diese Daten werden weitergegeben an:
- `generateLineTooltipContent` → für Tooltips bei Hover
- `PolylineContextMenu` → für Kontextmenü bei Rechtsklick
- Leaflet Polyline-Komponente → für farbige Darstellung
```mermaid
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 --> S1
T2 --> S2
T3 --> S3
T1 --> A1
T2 --> A2
T3 --> A3
A1 --> C1
A2 --> C1
A3 --> C1
C1 --> C2
C2 --> C4
C1 --> C3
```
---
## 📦 Wichtige Dateien
| Datei | Zweck |
| ------------------------------------------------------------------------ | -------------------------------------------------------------------- |
| [`useLineData.js`](tooltip/useLineData.js) | Holt Linien-, Status- und Stationsdaten aus Redux und kombiniert sie |
| [`generateLineTooltipContent.js`](tooltip/generateLineTooltipContent.js) | Baut HTML für die Tooltips |
| [`PolylineContextMenu.js`](PolylineContextMenu.md) | Kontextmenü zur Interaktion mit Linien |
---
![GIS Polylines](../../screenshots/gisPolylines.png)
---
[Zurück zur Übersicht](../../README.md)
---
## 📘 Technischer Hintergrund für Einsteiger
Diese Komponente verbindet Daten aus **zwei unterschiedlichen Systemen**:
1. **NodeMap App (Next.js API)**
→ liefert die **Geometrie der Linien** direkt aus der Datenbank (ohne Webservice).
2. **TALAS.web WebService**
→ liefert **Statusinformationen und Stationsnamen** (LD_Name).
### 🔄 Ablauf im Detail
- **Liniengeometrie** (`idLD`, `idModul`, `points`) kommt über `fetchGisLinesThunk` aus der
Datenbank.
- **Statusinformationen** (Meldungen, Farben, Modulname, Slot) kommen über
`fetchGisLinesStatusThunk`.
- **Stationsnamen** (LD_Name) kommen über `fetchGisStationsStaticDistrictThunk`.
- Die Hook `useLineData.js` verbindet alle Infos → erzeugt Tooltip-HTML & Farblogik.
- `generateLineTooltipContent.js` erstellt den konkreten Tooltip-HTML-String.
### 🧠 Wichtig für Debugging
- **Zuordnung** erfolgt immer über `idLD` und `Modul`.
- Stationen findest du im Slice `gisStationsStaticDistrict.Points[] → LD_Name`
- Linien findest du im Slice `gisLinesFromDatabase`
- Statusinfos findest du im Slice `gisLinesStatusFromWebservice`
🛠 **Fehler wie "Station: N/A"** entstehen, wenn `idLD` im Status vorhanden ist, aber keine passende
Station in `gisStationsStaticDistrict` gefunden wurde.

View File

@@ -4,6 +4,8 @@
Ein einfacher, grauer runder Marker als Stützpunkt in einer Polyline.
![CircleIcon](../../../screenshots/CircleIcon.png)
## Eigenschaften
- Stil: grauer Kreis mit schwarzem Rand

View File

@@ -4,6 +4,8 @@
Ein Viereck zur Markierung des Endpunkts einer Polyline.
![EndIcon](../../../screenshots/EndIcon.png)
## Eigenschaften
- Stil: graues Quadrat mit schwarzem Rand

View File

@@ -3,4 +3,6 @@
- [CircleIcon](CircleIcon.md)
- [EndIcon](EndIcon.md)
- [StartIcon](StartIcon.md)
- [SupportPointIcons](SupportPointIcons.md)
- [SupportPointIcons](SupportPointIcons.md)
![gisPolylinesIcons](../../../screenshots/gisPolylinesIcons.png)

View File

@@ -4,6 +4,8 @@
Ein SVG-Dreieck zur Markierung des Startpunkts einer Polyline.
![StartIcon](../../../screenshots/StartIcon.png)
## Eigenschaften
- Schwarzes Dreieck mit grauem Overlay (Polygon SVG)

View File

@@ -4,6 +4,8 @@
Definiert zwei Icons für interaktive Stützpunkte in einer Polyline:
![CircleIcon](../../../screenshots/CircleIcon.png)
## AddSupportPointIcon
- Grüner Kreis mit weißem Rand und Pluszeichen

View File

@@ -5,6 +5,8 @@
Ein einfaches Leaflet-Icon, das ein rundes Pluszeichen darstellt.
Wird für zusätzliche UI-Markierungen auf Geräten oder überlappenden Icons verwendet.
![PlusRoundIcon](../../../../screenshots/PlusRoundIcon.png)
## Eigenschaften
| Attribut | Wert |

View File

@@ -1,3 +1,5 @@
# 📄 Übersicht: docs/components/icons/devices/overlapping
- [PlusRoundIcon](PlusRoundIcon.md)
- [PlusRoundIcon](PlusRoundIcon.md)
![PlusRoundIcon](../../../../screenshots/PlusRoundIcon.png)

View File

@@ -5,7 +5,7 @@
Die zentrale React-Komponente zur Darstellung und Steuerung der Leaflet-Karte.
Bindet alle Marker, Layer, POIs, Linien und das Kontextmenü dynamisch ein.
---
![Overview](../../screenshots/overview1.png)
## 🎯 Zweck
@@ -41,6 +41,8 @@ Verwendet umfangreiche Redux-Slices zur Steuerung von:
- Sichtbarkeit einzelner Layergruppen
- Aktuelle Selektion (Area, Gerät, POI)
![ReduxSlices](../../screenshots/ReaduxSlices.png)
---
## 🔧 Lokale Steuerung
@@ -49,6 +51,8 @@ Verwendet umfangreiche Redux-Slices zur Steuerung von:
- Karte speichert Zoom & Center dauerhaft im Browser
- Kontextmenü-Einträge ändern sich je nach Rechten & Modus
![LocalStorage](../../screenshots/LocalStorage.png)
---
## 🧪 Besonderheiten

View File

@@ -1,3 +1,8 @@
# 📄 Übersicht: docs/components/mainComponent
- [MapComponent](MapComponent.md)
Die zentrale React-Komponente zur Darstellung und Steuerung der Leaflet-Karte.
Bindet alle Marker, Layer, POIs, Linien und das Kontextmenü dynamisch ein.
- [MapComponent](MapComponent.md)
![Overview](../../screenshots/overview1.png)

View File

@@ -5,6 +5,8 @@
Zeigt ein modales Formular an, um einen neuen POI auf der Karte zu erstellen.
Die Koordinaten (`latlng`) werden automatisch übernommen.
![POI hinzufügen Modal](../../screenshots/AddPOIModal.png)
## Funktionen
- POI-Name, Typ und zugehöriges Gerät auswählbar

View File

@@ -4,6 +4,8 @@
Ein Dialog zur Aktualisierung oder Löschung bestehender POIs.
![POI Update Modal](../../screenshots/PoiUpdateModal.png)
## Features
- Zeigt aktuellen Namen, Beschreibung, Gerät und Typ

View File

@@ -1,4 +1,6 @@
# 📄 Übersicht: docs/components/pois
- [AddPOIModal](AddPOIModal.md)
- [PoiUpdateModal](PoiUpdateModal.md)
- [PoiUpdateModal](PoiUpdateModal.md)
![POIs](../../screenshots/POIs.png)

View File

@@ -1,6 +1,6 @@
<!-- /docs/components/uiWidgets/CoordinateInput.md -->
# 📍 CoordinateInput.js
# CoordinateInput.js
Die Komponente `CoordinateInput` stellt ein einfaches Eingabefeld für geografische Koordinaten
(Latitude, Longitude) bereit.

View File

@@ -1,4 +1,7 @@
# 📄 Übersicht: docs/components/uiWidgets
# 📄 UI-Widgets
- [mapLayersControlPanel](mapLayersControlPanel/mapLayersControlPanel.md)
- [CoordinateInput](CoordinateInput.md)
- [VersionInfoModal](VersionInfoModal.md)
- [VersionInfoModal](VersionInfoModal.md)
![uiWidgets.png](../../screenshots/uiWidgets.png)

View File

@@ -8,6 +8,10 @@ Es wird meist im Footer oder als Info-Schaltfläche in der Benutzeroberfläche e
---
![VersionInfoModal](../../screenshots/VersionInfoModal.png)
---
## 🔧 Pfad
```bash
@@ -23,7 +27,8 @@ Die Komponente informiert Nutzer über:
- Die **aktuelle TALAS.Map Version**
- Die **Firmenadresse und Kontaktdaten** der Littwin Systemtechnik GmbH & Co. KG
- Eine zentral platzierte Grafik mit dem TALAS-Logo
- Eine Schaltfläche zum Schließen des Modals
- Eine Schaltfläche zum Schließen des Modals
![VersionInfoModal](../../screenshots/VersionInfoModal2.png)
---
@@ -46,20 +51,6 @@ Die Komponente informiert Nutzer über:
---
## 🧩 Inhalt im Modal
```plaintext
+--------------------------+
| [Logo_TALAS.png] |
| Littwin GmbH Adresse |
| Telefon & E-Mail |
| Version: 1.1.188 |
| [Schließen] Button |
+--------------------------+
```
---
## 🎨 Gestaltung
- Modal-Layout mit Tailwind CSS (`fixed`, `z-50`, `bg-white`, `rounded`, `shadow`)
@@ -75,7 +66,7 @@ Die Komponente informiert Nutzer über:
| `showVersionInfoModal = true` | Modal wird angezeigt |
| Klick auf Hintergrund | Modal wird geschlossen |
| Klick auf „Schließen“-Button | Modal wird geschlossen |
| Version `APP_VERSION = 1.1.188` | Text „TALAS.Map Version 1.1.188“ sichtbar |
| Version `APP_VERSION = 1.1.290` | Text „TALAS.Map Version 1.1.290“ sichtbar |
---
@@ -89,7 +80,6 @@ Die Komponente informiert Nutzer über:
## 🛠 Verbesserungsideen
- ESC-Taste als Schließen-Funktion ergänzen
- Option für dynamische Anzeige von Changelog-Link
- Automatischer Import von Version via `process.env.NEXT_PUBLIC_APP_VERSION`
---

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

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

View File

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

View File

@@ -1,15 +0,0 @@
<!-- /docs/hooks/layers/useCiscoRouterMarkersLayer.md -->
# 🌐 useCiscoRouterMarkersLayer.js
Hook zur Verwaltung aller Cisco-Router-Marker in der Leaflet-Karte.
## Funktionen
- Lädt Geräte per `createAndSetDevices(6, ...)`
- Fügt Marker hinzu & registriert Popup/Kontextmenü
- Verwendet `checkOverlappingMarkers(...)`
---
[Zurück zur Übersicht](../../README.md)

View File

@@ -1,15 +0,0 @@
<!-- /docs/hooks/layers/useDauzMarkersLayer.md -->
# 🔧 useDauzMarkersLayer.js
Spezialisierter Hook zur Verwaltung von DAUZ-Gerätemarkern (System-ID: 110)
## Verhalten
- Marker mit Popup & Kontextmenü
- Nutzung von `createAndSetDevices(...)`
- Sichtbarkeit direkt über Kartenlayer steuerbar
---
[Zurück zur Übersicht](../../README.md)

View File

@@ -1,15 +0,0 @@
<!-- /docs/hooks/layers/useEciMarkersLayer.md -->
# 🛰️ useEciMarkersLayer.js
Verwaltet die Darstellung und Events für ECI-Marker (System-ID: 2)
## Features
- Kontextmenü & Popup für jeden Marker
- Erkennung überlappender Marker (`checkOverlappingMarkers`)
- Nutzung von `createAndSetDevices(...)`
---
[Zurück zur Übersicht](../../README.md)

View File

@@ -1,15 +0,0 @@
<!-- /docs/hooks/layers/useGmaMarkersLayer.md -->
# 🌡️ useGmaMarkersLayer.js
Spezialhook für GMA-Marker mit Messwertanzeige (LT, FBT, GT, RLF).
## Besonderheiten
- Tooltip enthält Temperatur-/Feuchtigkeitswerte aus Redux
- Eigenes Kontextmenü mit Zoom/Zentrieren
- Verwendet `marker.options.areaName` zur Messzuordnung
---
[Zurück zur Übersicht](../../README.md)

View File

@@ -1,14 +0,0 @@
<!-- /docs/hooks/layers/useLteModemMarkersLayer.md -->
# 📶 useLteModemMarkersLayer.js
Steuert Marker vom Typ LTE-Modem (System-ID: 5)
## Features
- Standard-Kontextmenü + Popup
- Integration mit OMS und Overlap-Check
---
[Zurück zur Übersicht](../../README.md)

View File

@@ -1,14 +0,0 @@
<!-- /docs/hooks/layers/useMessstellenMarkersLayer.md -->
# 🧾 useMessstellenMarkersLayer.js
Für Messstellen-Marker (System-ID: 13)
## Verhalten
- Einfache Marker mit Tooltip
- Nutzung von `createAndSetDevices(...)` + Kontextmenü
---
[Zurück zur Übersicht](../../README.md)

View File

@@ -1,14 +0,0 @@
<!-- /docs/hooks/layers/useOtdrMarkersLayer.md -->
# 🔍 useOtdrMarkersLayer.js
Darstellung von OTDR-Messpunkten (System-ID: 9)
## Funktionen
- Popup-Interaktion beim Hover
- Marker mit Kontextmenü via `addContextMenuToMarker`
---
[Zurück zur Übersicht](../../README.md)

View File

@@ -1,11 +0,0 @@
# 🏭 useSiemensMarkersLayer.js
Für Siemens-Geräte (System-ID: 8).
- Marker mit Kontextmenü und Overlap-Prüfung
- Integration mit OMS
- Nutzung von `checkOverlappingMarkers(...)`
---
[Zurück zur Übersicht](../../README.md)

View File

@@ -1,11 +0,0 @@
# 📡 useSmsfunkmodemMarkersLayer.js
Filtert `GisSystemStatic` nach SMS Modem (System 111 oder Name).
- Icon: `/img/icons/pois/sms-funkmodem.png`
- Kontextmenü & Popup
- Sichtbarkeit über `isVisible` steuerbar
---
[Zurück zur Übersicht](../../README.md)

View File

@@ -1,11 +0,0 @@
# ❔ useSonstigeMarkersLayer.js
Für alle Geräte mit System-ID 200 (Sonstige).
- Klassische Leaflet-Marker
- Kontextmenü und Popup
- Nutzung von `createAndSetDevices(...)`
---
[Zurück zur Übersicht](../../README.md)

View File

@@ -1,10 +0,0 @@
# 🌐 useTalasMarkersLayer.js
Für TALAS-Systeme (System-ID: 1).
- Popup + Kontextmenü auf Marker
- Fügt Marker zuerst zu OMS, dann zu Karte hinzu
---
[Zurück zur Übersicht](../../README.md)

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