From 2946f55246153ad2d2b45a51af28bd61f0ba1007 Mon Sep 17 00:00:00 2001 From: ISA Date: Wed, 28 May 2025 09:51:46 +0200 Subject: [PATCH] docs --- config/appVersion.js | 2 +- docs/docs/NodeMap.pdf | Bin 0 -> 112662 bytes docs/docs/README.md | 14 ++ docs/docs/architecture.md | 90 +++++++++++ docs/docs/build-and-deploy.md | 46 ++++++ docs/docs/checklist.md | 42 +++++ docs/docs/components/README.md | 77 +++++++++ docs/docs/components/TestScript.md | 44 +++++ .../components/contextmenu/CoordinatePopup.md | 32 ++++ docs/docs/components/contextmenu/README.md | 43 +++++ .../contextmenu/useMapContextMenu.md | 30 ++++ .../gisPolylines/PolylineContextMenu.md | 34 ++++ .../gisPolylines/icons/CircleIcon.md | 16 ++ .../components/gisPolylines/icons/EndIcon.md | 15 ++ .../gisPolylines/icons/StartIcon.md | 15 ++ .../gisPolylines/icons/SupportPointIcons.md | 20 +++ .../devices/overlapping/PlusRoundIcon.md | 26 +++ .../components/mainComponent/MapComponent.md | 71 +++++++++ .../mainComponent/hooks/useInitializeMap.md | 53 +++++++ docs/docs/components/pois/AddPOIModal.md | 28 ++++ docs/docs/components/pois/PoiUpdateModal.md | 29 ++++ .../components/uiWidgets/CoordinateInput.md | 101 ++++++++++++ .../components/uiWidgets/VersionInfoModal.md | 92 +++++++++++ .../mapLayersControlPanel/EditModeToggle.md | 85 ++++++++++ .../MapLayersControlPanel.md | 150 ++++++++++++++++++ docs/docs/config/README.md | 38 +++++ docs/docs/config/appVersion.md | 16 ++ docs/docs/config/config.md | 60 +++++++ docs/docs/config/layers.md | 21 +++ docs/docs/config/paths.md | 19 +++ docs/docs/config/urls.md | 18 +++ docs/docs/env.local..md | 7 + docs/docs/hooks/layers/useAreaMarkersLayer.md | 13 ++ .../layers/useCiscoRouterMarkersLayer.md | 11 ++ docs/docs/hooks/layers/useDauzMarkersLayer.md | 11 ++ docs/docs/hooks/layers/useDrawLines.md | 11 ++ docs/docs/hooks/layers/useEciMarkersLayer.md | 11 ++ docs/docs/hooks/layers/useGmaMarkersLayer.md | 11 ++ .../hooks/layers/useLteModemMarkersLayer.md | 10 ++ .../layers/useMessstellenMarkersLayer.md | 10 ++ docs/docs/hooks/layers/useOtdrMarkersLayer.md | 10 ++ .../hooks/layers/useSiemensMarkersLayer.md | 7 + .../layers/useSmsfunkmodemMarkersLayer.md | 7 + .../hooks/layers/useSonstigeMarkersLayer.md | 7 + .../docs/hooks/layers/useTalasMarkersLayer.md | 6 + .../hooks/layers/useTalasiclMarkersLayer.md | 6 + .../layers/useTkComponentsMarkersLayer.md | 6 + docs/docs/hooks/layers/useUlafMarkersLayer.md | 7 + docs/docs/hooks/layers/useWagoMarkersLayer.md | 6 + docs/docs/hooks/layers/useWdmMarkersLayer.md | 7 + docs/docs/hooks/useCreateAndSetDevices.md | 17 ++ docs/docs/hooks/useDynamicMarkerLayers.md | 17 ++ docs/docs/hooks/useLayerVisibility.md | 15 ++ docs/docs/hooks/useLineData.md | 19 +++ docs/docs/hooks/useMapComponentState.md | 18 +++ docs/docs/hooks/useMarkerLayers.md | 16 ++ docs/docs/hooks/usePolylineTooltipLayer.md | 15 ++ docs/docs/nssm-exe-installation.md | 56 +++++++ docs/docs/pages/_app.md | 26 +++ docs/docs/pages/api/[...path].md | 45 ++++++ .../pages/api/talas_v5_DB/area/readArea.md | 28 ++++ .../pages/api/talas_v5_DB/area/updateArea.md | 32 ++++ .../talas_v5_DB/device/getAllStationsNames.md | 29 ++++ .../api/talas_v5_DB/device/getDevices.md | 34 ++++ .../api/talas_v5_DB/gisLines/readGisLines.md | 25 +++ .../gisLines/updateLineCoordinates.md | 30 ++++ .../talas_v5_DB/locationDevice/getDeviceId.md | 26 +++ .../locationDevice/locationDeviceNameById.md | 26 +++ .../locationDevice/locationDevices.md | 27 ++++ .../api/talas_v5_DB/poiTyp/readPoiTyp.md | 36 +++++ .../docs/pages/api/talas_v5_DB/pois/addPoi.md | 25 +++ .../pages/api/talas_v5_DB/pois/deletePoi.md | 20 +++ .../pages/api/talas_v5_DB/pois/getPoiById.md | 21 +++ .../pages/api/talas_v5_DB/pois/poi-icons.md | 21 +++ .../pages/api/talas_v5_DB/pois/readAllPOIs.md | 17 ++ .../api/talas_v5_DB/pois/updateLocation.md | 23 +++ .../pages/api/talas_v5_DB/pois/updatePoi.md | 25 +++ .../pages/api/talas_v5_DB/priorityConfig.md | 83 ++++++++++ .../station/getAllStationsNames.md | 29 ++++ .../api/talas_v5_DB/station/getDevices.md | 40 +++++ docs/docs/pages/index.md | 32 ++++ .../slices/database/area/updateAreaSlice.md | 40 +++++ .../locationDevice/locationDevicesSlice.md | 45 ++++++ .../database/locationDevicesFromDBSlice.md | 25 +++ .../slices/database/locationDevicesSlice.md | 23 +++ .../database/pois/addPoiOnPolylineSlice.md | 3 + .../redux/slices/database/pois/addPoiSlice.md | 3 + .../slices/database/pois/currentPoiSlice.md | 3 + .../slices/database/pois/poiIconsDataSlice.md | 3 + .../database/pois/poiLayerVisibleSlice.md | 3 + .../slices/database/pois/poiMarkersSlice.md | 3 + .../pois/poiReadFromDbTriggerSlice.md | 3 + .../redux/slices/database/pois/poiTypSlice.md | 3 + .../slices/database/pois/poiTypesSlice.md | 3 + .../database/pois/readPoiMarkersStoreSlice.md | 3 + .../slices/database/pois/selectedPoiSlice.md | 3 + .../database/polylines/gisLinesSlice.md | 25 +++ .../polylines/polylineContextMenuSlice.md | 25 +++ .../polylines/polylineEventsDisabledSlice.md | 16 ++ .../polylines/polylineLayerVisibleSlice.md | 21 +++ .../updatePolylineCoordinatesSlice.md | 22 +++ .../slices/database/priorityConfigSlice.md | 24 +++ docs/docs/redux/slices/lineVisibilitySlice.md | 24 +++ docs/docs/redux/slices/mapLayersSlice.md | 27 ++++ docs/docs/redux/slices/selectedAreaSlice.md | 16 ++ docs/docs/redux/slices/selectedDeviceSlice.md | 16 ++ docs/docs/redux/slices/urlParameterSlice.md | 20 +++ .../slices/webService/gisLinesStatusSlice.md | 25 +++ .../gisStationsMeasurementsSlice.md | 25 +++ .../gisStationsStaticDistrictSlice.md | 25 +++ .../gisStationsStatusDistrictSlice.md | 25 +++ .../slices/webService/gisSystemStaticSlice.md | 25 +++ .../slices/webService/userRightsSlice.md | 26 +++ docs/docs/redux/slices/zoomTriggerSlice.md | 16 ++ docs/docs/redux/store.md | 64 ++++++++ .../thunks/database/area/updateAreaThunk.md | 25 +++ .../database/fetchLocationDevicesThunk.md | 15 ++ .../database/fetchPriorityConfigThunk.md | 15 ++ .../thunks/database/getDeviceIdByNameThunk.md | 16 ++ .../fetchLocationDevicesThunk (1).md | 37 +++++ .../redux/thunks/database/pois/addPoiThunk.md | 21 +++ .../thunks/database/pois/deletePoiThunk.md | 16 ++ .../database/pois/fetchPoiIconsDataThunk.md | 15 ++ .../database/pois/fetchPoiMarkersThunk.md | 15 ++ .../thunks/database/pois/fetchPoiTypThunk.md | 15 ++ .../thunks/database/pois/updatePoiThunk.md | 20 +++ .../database/polylines/fetchGisLinesThunk.md | 16 ++ .../updatePolylineCoordinatesThunk.md | 20 +++ .../webservice/fetchGisLinesStatusThunk.md | 16 ++ .../fetchGisStationsMeasurementsThunk.md | 15 ++ .../fetchGisStationsStaticDistrictThunk.md | 15 ++ .../fetchGisStationsStatusDistrictThunk.md | 15 ++ .../webservice/fetchGisSystemStaticThunk.md | 15 ++ .../thunks/webservice/fetchUserRightsThunk.md | 16 ++ .../database/area/updateAreaService.md | 37 +++++ .../database/fetchDeviceNameByIdService.md | 22 +++ .../database/fetchLocationDevicesService.md | 21 +++ .../database/fetchPriorityConfigService.md | 17 ++ .../database/getDeviceIdByNameService.md | 22 +++ .../fetchLocationDevicesService.md | 21 +++ .../services/database/pois/addPoiService.md | 22 +++ .../database/pois/deletePoiService.md | 15 ++ .../database/pois/fetchPoiDataByIdService.md | 15 ++ .../database/pois/fetchPoiDataService.md | 15 ++ .../database/pois/fetchPoiIconsDataService.md | 15 ++ .../database/pois/fetchPoiMarkersService.md | 15 ++ .../database/pois/fetchPoiTypService.md | 15 ++ .../database/pois/updatePoiService.md | 21 +++ .../polylines/fetchGisLinesService.md | 19 +++ .../updatePolylineCoordinatesService.md | 24 +++ .../updateLocationInDatabaseService.md | 21 +++ docs/docs/services/utils/fetchWithTimeout.md | 46 ++++++ .../webservice/fetchGisLinesStatusService.md | 60 +++++++ .../fetchGisStationsMeasurementsService.md | 16 ++ .../fetchGisStationsStaticDistrictService.md | 16 ++ .../fetchGisStationsStatusDistrictService.md | 16 ++ .../webservice/fetchGisSystemStaticService.md | 16 ++ .../webservice/fetchUserRightsService.md | 16 ++ docs/docs/utils/addContextMenuToMarker.md | 28 ++++ docs/docs/utils/contextMenuUtils.md | 14 ++ .../docs/utils/devices/createAndSetDevices.md | 107 +++++++++++++ docs/docs/utils/geometryUtils.md | 14 ++ docs/docs/utils/initializeMap.md | 15 ++ docs/docs/utils/mapUtils.md | 10 ++ docs/docs/utils/markerUtils.md | 14 ++ docs/docs/utils/mysqlPool.md | 16 ++ docs/docs/utils/openInNewTab.md | 15 ++ docs/docs/utils/openInSameWindow.md | 15 ++ docs/docs/utils/poiUtils.md | 14 ++ docs/docs/utils/polylines/contextMenu.md | 10 ++ docs/docs/utils/polylines/eventHandlers.md | 10 ++ .../utils/polylines/monitorContextMenu.md | 10 ++ .../utils/polylines/polylineSubscription.md | 10 ++ docs/docs/utils/polylines/redrawPolyline.md | 14 ++ docs/docs/utils/polylines/setupPolylines.md | 76 +++++++++ docs/docs/utils/setupDevices.md | 15 ++ docs/docs/utils/setupPOIs.md | 32 ++++ docs/docs/utils/zoomAndCenterUtils.md | 10 ++ 178 files changed, 4306 insertions(+), 1 deletion(-) create mode 100644 docs/docs/NodeMap.pdf create mode 100644 docs/docs/README.md create mode 100644 docs/docs/architecture.md create mode 100644 docs/docs/build-and-deploy.md create mode 100644 docs/docs/checklist.md create mode 100644 docs/docs/components/README.md create mode 100644 docs/docs/components/TestScript.md create mode 100644 docs/docs/components/contextmenu/CoordinatePopup.md create mode 100644 docs/docs/components/contextmenu/README.md create mode 100644 docs/docs/components/contextmenu/useMapContextMenu.md create mode 100644 docs/docs/components/gisPolylines/PolylineContextMenu.md create mode 100644 docs/docs/components/gisPolylines/icons/CircleIcon.md create mode 100644 docs/docs/components/gisPolylines/icons/EndIcon.md create mode 100644 docs/docs/components/gisPolylines/icons/StartIcon.md create mode 100644 docs/docs/components/gisPolylines/icons/SupportPointIcons.md create mode 100644 docs/docs/components/icons/devices/overlapping/PlusRoundIcon.md create mode 100644 docs/docs/components/mainComponent/MapComponent.md create mode 100644 docs/docs/components/mainComponent/hooks/useInitializeMap.md create mode 100644 docs/docs/components/pois/AddPOIModal.md create mode 100644 docs/docs/components/pois/PoiUpdateModal.md create mode 100644 docs/docs/components/uiWidgets/CoordinateInput.md create mode 100644 docs/docs/components/uiWidgets/VersionInfoModal.md create mode 100644 docs/docs/components/uiWidgets/mapLayersControlPanel/EditModeToggle.md create mode 100644 docs/docs/components/uiWidgets/mapLayersControlPanel/MapLayersControlPanel.md create mode 100644 docs/docs/config/README.md create mode 100644 docs/docs/config/appVersion.md create mode 100644 docs/docs/config/config.md create mode 100644 docs/docs/config/layers.md create mode 100644 docs/docs/config/paths.md create mode 100644 docs/docs/config/urls.md create mode 100644 docs/docs/env.local..md create mode 100644 docs/docs/hooks/layers/useAreaMarkersLayer.md create mode 100644 docs/docs/hooks/layers/useCiscoRouterMarkersLayer.md create mode 100644 docs/docs/hooks/layers/useDauzMarkersLayer.md create mode 100644 docs/docs/hooks/layers/useDrawLines.md create mode 100644 docs/docs/hooks/layers/useEciMarkersLayer.md create mode 100644 docs/docs/hooks/layers/useGmaMarkersLayer.md create mode 100644 docs/docs/hooks/layers/useLteModemMarkersLayer.md create mode 100644 docs/docs/hooks/layers/useMessstellenMarkersLayer.md create mode 100644 docs/docs/hooks/layers/useOtdrMarkersLayer.md create mode 100644 docs/docs/hooks/layers/useSiemensMarkersLayer.md create mode 100644 docs/docs/hooks/layers/useSmsfunkmodemMarkersLayer.md create mode 100644 docs/docs/hooks/layers/useSonstigeMarkersLayer.md create mode 100644 docs/docs/hooks/layers/useTalasMarkersLayer.md create mode 100644 docs/docs/hooks/layers/useTalasiclMarkersLayer.md create mode 100644 docs/docs/hooks/layers/useTkComponentsMarkersLayer.md create mode 100644 docs/docs/hooks/layers/useUlafMarkersLayer.md create mode 100644 docs/docs/hooks/layers/useWagoMarkersLayer.md create mode 100644 docs/docs/hooks/layers/useWdmMarkersLayer.md create mode 100644 docs/docs/hooks/useCreateAndSetDevices.md create mode 100644 docs/docs/hooks/useDynamicMarkerLayers.md create mode 100644 docs/docs/hooks/useLayerVisibility.md create mode 100644 docs/docs/hooks/useLineData.md create mode 100644 docs/docs/hooks/useMapComponentState.md create mode 100644 docs/docs/hooks/useMarkerLayers.md create mode 100644 docs/docs/hooks/usePolylineTooltipLayer.md create mode 100644 docs/docs/nssm-exe-installation.md create mode 100644 docs/docs/pages/_app.md create mode 100644 docs/docs/pages/api/[...path].md create mode 100644 docs/docs/pages/api/talas_v5_DB/area/readArea.md create mode 100644 docs/docs/pages/api/talas_v5_DB/area/updateArea.md create mode 100644 docs/docs/pages/api/talas_v5_DB/device/getAllStationsNames.md create mode 100644 docs/docs/pages/api/talas_v5_DB/device/getDevices.md create mode 100644 docs/docs/pages/api/talas_v5_DB/gisLines/readGisLines.md create mode 100644 docs/docs/pages/api/talas_v5_DB/gisLines/updateLineCoordinates.md create mode 100644 docs/docs/pages/api/talas_v5_DB/locationDevice/getDeviceId.md create mode 100644 docs/docs/pages/api/talas_v5_DB/locationDevice/locationDeviceNameById.md create mode 100644 docs/docs/pages/api/talas_v5_DB/locationDevice/locationDevices.md create mode 100644 docs/docs/pages/api/talas_v5_DB/poiTyp/readPoiTyp.md create mode 100644 docs/docs/pages/api/talas_v5_DB/pois/addPoi.md create mode 100644 docs/docs/pages/api/talas_v5_DB/pois/deletePoi.md create mode 100644 docs/docs/pages/api/talas_v5_DB/pois/getPoiById.md create mode 100644 docs/docs/pages/api/talas_v5_DB/pois/poi-icons.md create mode 100644 docs/docs/pages/api/talas_v5_DB/pois/readAllPOIs.md create mode 100644 docs/docs/pages/api/talas_v5_DB/pois/updateLocation.md create mode 100644 docs/docs/pages/api/talas_v5_DB/pois/updatePoi.md create mode 100644 docs/docs/pages/api/talas_v5_DB/priorityConfig.md create mode 100644 docs/docs/pages/api/talas_v5_DB/station/getAllStationsNames.md create mode 100644 docs/docs/pages/api/talas_v5_DB/station/getDevices.md create mode 100644 docs/docs/pages/index.md create mode 100644 docs/docs/redux/slices/database/area/updateAreaSlice.md create mode 100644 docs/docs/redux/slices/database/locationDevice/locationDevicesSlice.md create mode 100644 docs/docs/redux/slices/database/locationDevicesFromDBSlice.md create mode 100644 docs/docs/redux/slices/database/locationDevicesSlice.md create mode 100644 docs/docs/redux/slices/database/pois/addPoiOnPolylineSlice.md create mode 100644 docs/docs/redux/slices/database/pois/addPoiSlice.md create mode 100644 docs/docs/redux/slices/database/pois/currentPoiSlice.md create mode 100644 docs/docs/redux/slices/database/pois/poiIconsDataSlice.md create mode 100644 docs/docs/redux/slices/database/pois/poiLayerVisibleSlice.md create mode 100644 docs/docs/redux/slices/database/pois/poiMarkersSlice.md create mode 100644 docs/docs/redux/slices/database/pois/poiReadFromDbTriggerSlice.md create mode 100644 docs/docs/redux/slices/database/pois/poiTypSlice.md create mode 100644 docs/docs/redux/slices/database/pois/poiTypesSlice.md create mode 100644 docs/docs/redux/slices/database/pois/readPoiMarkersStoreSlice.md create mode 100644 docs/docs/redux/slices/database/pois/selectedPoiSlice.md create mode 100644 docs/docs/redux/slices/database/polylines/gisLinesSlice.md create mode 100644 docs/docs/redux/slices/database/polylines/polylineContextMenuSlice.md create mode 100644 docs/docs/redux/slices/database/polylines/polylineEventsDisabledSlice.md create mode 100644 docs/docs/redux/slices/database/polylines/polylineLayerVisibleSlice.md create mode 100644 docs/docs/redux/slices/database/polylines/updatePolylineCoordinatesSlice.md create mode 100644 docs/docs/redux/slices/database/priorityConfigSlice.md create mode 100644 docs/docs/redux/slices/lineVisibilitySlice.md create mode 100644 docs/docs/redux/slices/mapLayersSlice.md create mode 100644 docs/docs/redux/slices/selectedAreaSlice.md create mode 100644 docs/docs/redux/slices/selectedDeviceSlice.md create mode 100644 docs/docs/redux/slices/urlParameterSlice.md create mode 100644 docs/docs/redux/slices/webService/gisLinesStatusSlice.md create mode 100644 docs/docs/redux/slices/webService/gisStationsMeasurementsSlice.md create mode 100644 docs/docs/redux/slices/webService/gisStationsStaticDistrictSlice.md create mode 100644 docs/docs/redux/slices/webService/gisStationsStatusDistrictSlice.md create mode 100644 docs/docs/redux/slices/webService/gisSystemStaticSlice.md create mode 100644 docs/docs/redux/slices/webService/userRightsSlice.md create mode 100644 docs/docs/redux/slices/zoomTriggerSlice.md create mode 100644 docs/docs/redux/store.md create mode 100644 docs/docs/redux/thunks/database/area/updateAreaThunk.md create mode 100644 docs/docs/redux/thunks/database/fetchLocationDevicesThunk.md create mode 100644 docs/docs/redux/thunks/database/fetchPriorityConfigThunk.md create mode 100644 docs/docs/redux/thunks/database/getDeviceIdByNameThunk.md create mode 100644 docs/docs/redux/thunks/database/locationDevice/fetchLocationDevicesThunk (1).md create mode 100644 docs/docs/redux/thunks/database/pois/addPoiThunk.md create mode 100644 docs/docs/redux/thunks/database/pois/deletePoiThunk.md create mode 100644 docs/docs/redux/thunks/database/pois/fetchPoiIconsDataThunk.md create mode 100644 docs/docs/redux/thunks/database/pois/fetchPoiMarkersThunk.md create mode 100644 docs/docs/redux/thunks/database/pois/fetchPoiTypThunk.md create mode 100644 docs/docs/redux/thunks/database/pois/updatePoiThunk.md create mode 100644 docs/docs/redux/thunks/database/polylines/fetchGisLinesThunk.md create mode 100644 docs/docs/redux/thunks/database/polylines/updatePolylineCoordinatesThunk.md create mode 100644 docs/docs/redux/thunks/webservice/fetchGisLinesStatusThunk.md create mode 100644 docs/docs/redux/thunks/webservice/fetchGisStationsMeasurementsThunk.md create mode 100644 docs/docs/redux/thunks/webservice/fetchGisStationsStaticDistrictThunk.md create mode 100644 docs/docs/redux/thunks/webservice/fetchGisStationsStatusDistrictThunk.md create mode 100644 docs/docs/redux/thunks/webservice/fetchGisSystemStaticThunk.md create mode 100644 docs/docs/redux/thunks/webservice/fetchUserRightsThunk.md create mode 100644 docs/docs/services/database/area/updateAreaService.md create mode 100644 docs/docs/services/database/fetchDeviceNameByIdService.md create mode 100644 docs/docs/services/database/fetchLocationDevicesService.md create mode 100644 docs/docs/services/database/fetchPriorityConfigService.md create mode 100644 docs/docs/services/database/getDeviceIdByNameService.md create mode 100644 docs/docs/services/database/locationDevice/fetchLocationDevicesService.md create mode 100644 docs/docs/services/database/pois/addPoiService.md create mode 100644 docs/docs/services/database/pois/deletePoiService.md create mode 100644 docs/docs/services/database/pois/fetchPoiDataByIdService.md create mode 100644 docs/docs/services/database/pois/fetchPoiDataService.md create mode 100644 docs/docs/services/database/pois/fetchPoiIconsDataService.md create mode 100644 docs/docs/services/database/pois/fetchPoiMarkersService.md create mode 100644 docs/docs/services/database/pois/fetchPoiTypService.md create mode 100644 docs/docs/services/database/pois/updatePoiService.md create mode 100644 docs/docs/services/database/polylines/fetchGisLinesService.md create mode 100644 docs/docs/services/database/polylines/updatePolylineCoordinatesService.md create mode 100644 docs/docs/services/database/updateLocationInDatabaseService.md create mode 100644 docs/docs/services/utils/fetchWithTimeout.md create mode 100644 docs/docs/services/webservice/fetchGisLinesStatusService.md create mode 100644 docs/docs/services/webservice/fetchGisStationsMeasurementsService.md create mode 100644 docs/docs/services/webservice/fetchGisStationsStaticDistrictService.md create mode 100644 docs/docs/services/webservice/fetchGisStationsStatusDistrictService.md create mode 100644 docs/docs/services/webservice/fetchGisSystemStaticService.md create mode 100644 docs/docs/services/webservice/fetchUserRightsService.md create mode 100644 docs/docs/utils/addContextMenuToMarker.md create mode 100644 docs/docs/utils/contextMenuUtils.md create mode 100644 docs/docs/utils/devices/createAndSetDevices.md create mode 100644 docs/docs/utils/geometryUtils.md create mode 100644 docs/docs/utils/initializeMap.md create mode 100644 docs/docs/utils/mapUtils.md create mode 100644 docs/docs/utils/markerUtils.md create mode 100644 docs/docs/utils/mysqlPool.md create mode 100644 docs/docs/utils/openInNewTab.md create mode 100644 docs/docs/utils/openInSameWindow.md create mode 100644 docs/docs/utils/poiUtils.md create mode 100644 docs/docs/utils/polylines/contextMenu.md create mode 100644 docs/docs/utils/polylines/eventHandlers.md create mode 100644 docs/docs/utils/polylines/monitorContextMenu.md create mode 100644 docs/docs/utils/polylines/polylineSubscription.md create mode 100644 docs/docs/utils/polylines/redrawPolyline.md create mode 100644 docs/docs/utils/polylines/setupPolylines.md create mode 100644 docs/docs/utils/setupDevices.md create mode 100644 docs/docs/utils/setupPOIs.md create mode 100644 docs/docs/utils/zoomAndCenterUtils.md diff --git a/config/appVersion.js b/config/appVersion.js index ba26cc27b..a4174f091 100644 --- a/config/appVersion.js +++ b/config/appVersion.js @@ -1,2 +1,2 @@ // /config/appVersion -export const APP_VERSION = "1.1.198"; +export const APP_VERSION = "1.1.199"; diff --git a/docs/docs/NodeMap.pdf b/docs/docs/NodeMap.pdf new file mode 100644 index 0000000000000000000000000000000000000000..423f61acf516c2ba636555e074712488b5934142 GIT binary patch literal 112662 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

mP#75V3qE20VeB*mNcdJ9LH8KcK+@fc}z_S1{5 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 F{~xs(vAzHR literal 0 HcmV?d00001 diff --git a/docs/docs/README.md b/docs/docs/README.md new file mode 100644 index 000000000..ae12a69bc --- /dev/null +++ b/docs/docs/README.md @@ -0,0 +1,14 @@ + + +# Entwickler-Dokumentation + +Willkommen in der technischen Dokumentation von NodeMap. + +📂 Wichtige Themen: + +- Webservices: `api/webservices.md` +- Redux / Fetch-Logik: `redux/api/fromWebService.md` +- Konfigurationsschema: `env/env.local.schema.md` +- Deployment: `deployment.md` + +Diese Doku ist für Entwickler, die an diesem Projekt mitarbeiten oder übernehmen. diff --git a/docs/docs/architecture.md b/docs/docs/architecture.md new file mode 100644 index 000000000..5c4bd8dab --- /dev/null +++ b/docs/docs/architecture.md @@ -0,0 +1,90 @@ + + +# 🧠 Architekturübersicht – NodeMap + +Dieses Dokument beschreibt die technische Gesamtarchitektur des Projekts **NodeMap**, einer kartenbasierten Webanwendung zur Anzeige, Bearbeitung und Verwaltung von GIS-Daten, POIs und Gerätestatus. + +--- + +## ⚙️ Technologie-Stack + +| Komponente | Beschreibung | +| --------------------- | ---------------------------------------------------------------------- | +| **Frontend** | React 18 + Next.js (App Router) | +| **State-Management** | Redux Toolkit mit zentralem Store, Thunks & Slices | +| **UI** | Tailwind CSS + Leaflet + React-Icons | +| **Backend-Anbindung** | Webservices via `WebServiceMap.asmx` (IIS) + lokale Next.js API für DB | +| **Datenbank** | MySQL (Produktiv & Entwicklung, z. T. via Docker) | +| **Deployment** | Windows Server (IIS), optional per `nssm` als Dienst | + +--- + +## 🗺️ Architekturüberblick + +``` ++------------------+ +------------------+ +------------------+ +| Leaflet Map | <---> | Redux Store | <---> | Webservices | +| (Interaktivität) | | (Status & Data) | | (IIS, .asmx) | ++------------------+ +------------------+ +------------------+ + ^ + | + v ++------------------+ +------------------+ +-------------------+ +| POI-Komponenten | <---> | Redux Slices | <---> | Next.js API-Routen| +| (Add/Edit) | | (z. B. poiSlice) | | (Datenbank) | ++------------------+ +------------------+ +-------------------+ +``` + +--- + +## 🔁 Datenfluss (Beispiel: POI anzeigen) + +1. Leaflet-Karte lädt bei `MapComponent` Mounting +2. Redux-Thunk `fetchPoiMarkersThunk` wird ausgelöst +3. Thunk ruft `fetchPoiDataService.js` (DB) oder Webservice (IIS) auf +4. Ergebnisse werden im Slice `readPoiMarkersStoreSlice` gespeichert +5. Komponenten lesen POI-Daten über `useSelector(...)` aus dem Store +6. POIs werden als Marker in Leaflet gesetzt + +--- + +## 📁 Schlüsselfunktionen & Module + +| Bereich | Datei/Modul | Aufgabe | +| ------------- | ----------------------------------------------- | ---------------------------------------- | +| Kartenlogik | `MapComponent.js` | Zentrale Initialisierung und Layer-Logik | +| Webservices | `services/webservice/` | Kommunikation mit TALAS V5 Webservice | +| Datenbank | `services/database/` | Zugriff auf lokale Next.js-API & DB | +| POIs | `AddPOIModal.js`, `PoiUpdateModal.js` | UI für POI-Erstellung & -Bearbeitung | +| Redux | `redux/slices/`, `redux/thunks/`, `redux/store` | Globaler State, API-Steuerung | +| Konfiguration | `.env.local`, `config.js`, dynamic URLs | IP, basePath, Ports | + +--- + +## 🧩 Besonderheiten + +- **Konfigurierbarer basePath:** + Pfad wie `/talas5` ist optional und kann per `NEXT_PUBLIC_BASE_PATH` in `.env.local` gesetzt werden. +- **Rechteabhängige UI:** + Funktionen (z. B. POI bearbeiten) basieren auf Benutzerrechten (`IdRight`) vom Server. +- **Zentrale Komponentensteuerung:** + Komponenten wie `MapLayersControlPanel` oder `CoordinatePopup` kontrollieren Layer & Interaktion. +- **Kontextmenü-Logik:** + Marker & Polylinien besitzen eigene Kontextmenüs – dynamisch zusammengesetzt und verwaltet. + +--- + +## 📦 Versionierung & Builds + +- Version ist in `appVersion.js` definiert → wird über `NEXT_PUBLIC_APP_VERSION` eingeblendet +- Build erfolgt via `npm run build`, Auslieferung über `.next/` +- Nicht benötigte Dateien wie `__tests__`, `docs/`, `scripts/` etc. werden nicht in den Build aufgenommen + +--- + +## 📚 Weiterführende Dokumentation + +- [`build-and-deploy.md`](./build-and-deploy.md) +- [`env.local.schema.md`](./env.local.schema.md) +- [`redux/slices/`](./redux/slices/) +- [`services/webservice/`](./services/webservice/) diff --git a/docs/docs/build-and-deploy.md b/docs/docs/build-and-deploy.md new file mode 100644 index 000000000..8ce4da561 --- /dev/null +++ b/docs/docs/build-and-deploy.md @@ -0,0 +1,46 @@ + + +# 🛠 Deployment & Build-Verhalten (Next.js) + +Diese Datei beschreibt, welche Projektdateien in den Build (`.next/`) aufgenommen werden und welche nicht. +Ziel: Klarheit für Onboarding, Deployment-ZIP-Erstellung oder CI/CD. + +--- + +## 📦 Wird beim `npm run build` in `.next/` gespeichert + +| Inhalt | Beschreibung | +| ---------------------- | -------------------------------------------------- | +| Kompilierte Seiten | Alle unter `/pages/` | +| API-Routen | Alles aus `pages/api/` | +| Assets aus `public/` | Werden im Build nicht verändert, aber ausgeliefert | +| CSS-Dateien (Tailwind) | Werden gebundelt und minimiert | +| `.env.local` | Wird eingelesen, aber nicht exportiert | +| JS/TS-Quellcode | Wird zu Client- und Server-Bundles kompiliert | + +--- + +## 🧹 Wird **nicht** in `.next/` aufgenommen + +| Ordner/Datei | Zweck / Grund | +| --------------------------- | -------------------------------------------- | +| `__tests__`, `__mocks__` | Nur lokal für Tests, nicht im Build | +| `cypress/` | End-to-End-Tests, nur für lokale Entwicklung | +| `scripts/` | Hilfsskripte, nicht für Runtime relevant | +| `docs/` | Dokumentation, nur für Entwickler | +| `README.md`, `CHANGELOG.md` | Doku – nicht erforderlich zur Laufzeit | +| `Jenkinsfile`, `.github/` | CI/CD – wird vom Buildsystem verwendet | + +--- + +## 📂 Empfohlene Struktur für Deployment (z. B. ZIP-Upload auf Server) + +Nur folgende Dateien/Ordner übertragen: + +```bash +.next/ +public/ +package.json +package-lock.json +.env.local +``` diff --git a/docs/docs/checklist.md b/docs/docs/checklist.md new file mode 100644 index 000000000..62133ddbe --- /dev/null +++ b/docs/docs/checklist.md @@ -0,0 +1,42 @@ + + +# 🧾 Projektpflege-Checkliste + +Diese Datei dient als persönliche Gedächtnisstütze bei der Entwicklung und Pflege des Projekts. + +Bevor du einen Feature-, Refactor- oder Bugfix-Commit abschließt, geh diese Liste durch: + +--- + +## 📝 Dokumentation + +- [ ] Ist `README.md` noch aktuell (Projektziel, Setup, Nutzung)? +- [ ] Wurde `CHANGELOG.md` ergänzt (mit Datum, Version, Änderung)? +- [ ] Wurde ggf. ein neuer Punkt in `/docs/` ergänzt oder aktualisiert? +- [ ] Sind Beispiel-URLs oder sensible Daten **nicht im Code**, sondern dokumentiert? + +--- + +## 📦 Konfiguration + +- [ ] Ist `.env.local` aktuell und vollständig (für Entwickler/Testserver)? +- [ ] Wird jede Konfiguration ausschließlich über `.env.local` gesteuert? + +--- + +## ✅ Codequalität & Git + +- [ ] Ist die Git-Commit-Message beschreibend und lesbar (z. B. `feat:`, `fix:`, `docs:`)? +- [ ] Wurden unnötige Debug-Logs entfernt oder per `NODE_ENV` abgesichert? +- [ ] Wurden Änderungen getestet (lokal, ggf. auf Testsystem)? + +--- + +## 🧭 Onboarding-freundlich? + +- [ ] Könnte ein neuer Entwickler mit den aktuellen Dokumenten verstehen, was wie funktioniert? +- [ ] Gibt es Hinweise zur Architektur, API-Flows oder Besonderheiten im Code? + +--- + +Du kannst diese Checkliste in jedem Projekt beibehalten und auf deine Arbeitsweise anpassen. diff --git a/docs/docs/components/README.md b/docs/docs/components/README.md new file mode 100644 index 000000000..53c7cd71e --- /dev/null +++ b/docs/docs/components/README.md @@ -0,0 +1,77 @@ + + +# 🧩 `components/` – Übersicht über alle UI-Komponenten + +Dieses Verzeichnis enthält die gesamten React-Komponenten der TALAS-Kartenanwendung. +Sie sind thematisch gegliedert in Teilbereiche für Kontextmenüs, POIs, Polylinien, Modale und die zentrale `MapComponent`. + +--- + +## 📁 Strukturübersicht + +```bash +components/ +├── contextmenu/ # Komponenten für rechte Maustaste & Kontextaktionen +│ ├── CoordinatePopup.js +│ └── useMapContextMenu.js + +├── gisPolylines/ # Polylinien (Kabelstrecken) +│ ├── PolylineContextMenu.js +│ └── icons/ +│ ├── CircleIcon.js +│ ├── EndIcon.js +│ ├── StartIcon.js +│ └── SupportPointIcons.js + +├── icons/devices/overlapping/ # Zusätzliche Overlap-Icons für Geräte +│ └── PlusRoundIcon.js + +├── mainComponent/ # Hauptkomponenten für Karteninitialisierung +│ ├── MapComponent.js +│ └── hooks/ +│ └── useInitializeMap.js + +├── pois/ # POI-spezifische Modale +│ ├── AddPOIModal.js +│ └── PoiUpdateModal.js + +├── uiWidgets/ # UI-Widgets +│ ├── CoordinateInput.js +│ ├── VersionInfoModal.js +│ ├── TestScript.js +│ └── mapLayersControlPanel/ +│ ├── EditModeToggle.js +│ └── MapLayersControlPanel.js +``` + +--- + +## 🔎 Beschreibung der Hauptbereiche + +### `contextmenu/` + +Rechtsklick-Menüs für Marker, POIs, Polylinien. Steuert Anzeige & Verhalten. + +### `gisPolylines/` + +Komponenten für das Zeichnen, Bearbeiten und Interagieren mit Linien/Strecken. + +### `mainComponent/` + +Zentrale Leaflet-Map-Logik & Initialisierung via `MapComponent` und `useInitializeMap`. + +### `pois/` + +Modale für das Hinzufügen und Bearbeiten von POIs (Points of Interest). + +### `uiWidgets/` + +Komponenten wie Eingabefelder für Koordinaten-Suche, Infoboxen und Control Panel für Geräte Layers . + +--- + +## ✅ Besonderheiten + +- Verwendet **Tailwind CSS** für Styling +- Integration mit Redux, Leaflet, OverlappingMarkerSpiderfier +- Vollständig modular & testbar aufgebaut diff --git a/docs/docs/components/TestScript.md b/docs/docs/components/TestScript.md new file mode 100644 index 000000000..c8184a986 --- /dev/null +++ b/docs/docs/components/TestScript.md @@ -0,0 +1,44 @@ + + +# 🧪 TestScript.js + +Ein einfaches React-Testskript zur Laufzeitüberprüfung von Codefragmenten in `setupPolylines.js`. + +## Zweck + +Dieses Skript durchsucht die geladene `setupPolylines.js`-Datei (per `raw-loader`) nach bestimmten Kontextmenüeinträgen: + +- „Stützpunkt entfernen“ +- „Stützpunkt hinzufügen“ + +## Vorgehen + +- Lädt `setupPolylines.js` als Text via `!!raw-loader!` +- Nutzt reguläre Ausdrücke zur Prüfung +- Gibt Ergebnisse farblich formatiert in der Konsole aus + +## Ausgaben + +| Zustand | Beschreibung | +|-------------|-----------------------------------------------------| +| ✅ Test bestanden | Der gesuchte Text wurde gefunden | +| ❌ Test fehlgeschlagen | Der gesuchte Text fehlt in der Datei | +| ℹ️ Info | Neutrale Zusatzinformationen in der Konsole | + +## Besonderheiten + +- Kein visuelles UI – Rückmeldung nur über `console.log` +- Eignet sich als Dev-Hilfe für Refactoring oder PR-Checks + +## Beispielausgabe + +```plaintext +✔ Test bestanden: Der Text für 'Stützpunkt entfernen' wurde gefunden. +ℹ️ Info: Überprüfung abgeschlossen. +``` + +## Hinweise + +- Wird automatisch beim Mount (via `useEffect`) ausgeführt +- `return null` → keine sichtbare Ausgabe + diff --git a/docs/docs/components/contextmenu/CoordinatePopup.md b/docs/docs/components/contextmenu/CoordinatePopup.md new file mode 100644 index 000000000..05048fe80 --- /dev/null +++ b/docs/docs/components/contextmenu/CoordinatePopup.md @@ -0,0 +1,32 @@ + + +# 📌 CoordinatePopup.js + +Zeigt ein modales Fenster mit Koordinateninformationen an, z. B. aus einem Kontextmenü heraus. + +## Features + +- Darstellung eines Koordinatenwerts (`lat,lng`) +- Kopieren in die Zwischenablage (Clipboard API + Fallback) +- Modal zentriert mit Tailwind CSS +- Zwei Buttons: „Kopieren“ und „Schließen“ + +## Props + +| Name | Typ | Beschreibung | +| ------------- | ---------- | -------------------------------------------- | +| `isOpen` | `boolean` | Steuert Sichtbarkeit des Modals | +| `coordinates` | `string` | Zu zeigende Koordinaten (z. B. `"53.2,8.1"`) | +| `onClose` | `function` | Wird bei Klick auf „Schließen“ ausgelöst | + +## Design + +- Tailwind-Klassen für zentriertes Layout (`fixed`, `inset-0`, `z-50`) +- Leicht animierter Button-Hover + +## Interne Logik + +- Nutzt `navigator.clipboard.writeText` oder Fallback mit `document.execCommand("copy")` +- Stoppt Event-Bubbling, um Klick außerhalb zu erkennen + +🔙 [Zurück zur Übersicht](./README.md) diff --git a/docs/docs/components/contextmenu/README.md b/docs/docs/components/contextmenu/README.md new file mode 100644 index 000000000..9a2cff810 --- /dev/null +++ b/docs/docs/components/contextmenu/README.md @@ -0,0 +1,43 @@ + + +# 🖱️ `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. + +--- + +## 📂 Enthaltene Dateien + +| Datei | Beschreibung | +| ------------------------------------------------ | ---------------------------------------------------------------------- | +| [`CoordinatePopup.js`](./CoordinatePopup.md) | Zeigt ein kleines Kontextfenster mit Koordinaten und Copy-Funktion | +| [`useMapContextMenu.js`](./useMapContextMenu.md) | Hook zur Initialisierung und Verwaltung des Kontextmenüs auf der Karte | + +--- + +## 🔄 Verwendung + +Diese Komponenten sind typischerweise eingebunden in: + +- [`MapComponent.js`](../mainComponent/MapComponent.md) +- [`PolylineContextMenu.js`](../gisPolylines/PolylineContextMenu.md) +- Marker- und Linienfunktionen aus `setupDevices`, `setupPolylines` + +--- + +## 🎯 Ziel + +Ermöglicht einfache Benutzerinteraktion mit: + +- Geräten +- Koordinaten +- POIs +- Streckenabschnitten + +--- + +## 📚 Weitere Dokumentation + +Alle Markdown-Dateien für Komponenten befinden sich im `/docs/components/contextmenu/` Verzeichnis. + +🔙 [Zurück zu `components`](../README.md) diff --git a/docs/docs/components/contextmenu/useMapContextMenu.md b/docs/docs/components/contextmenu/useMapContextMenu.md new file mode 100644 index 000000000..4c88560ee --- /dev/null +++ b/docs/docs/components/contextmenu/useMapContextMenu.md @@ -0,0 +1,30 @@ + + +# 🖱️ useMapContextMenu.js + +Initialisiert Kontextmenüeinträge für die Leaflet-Karte. +Wird typischerweise in `initializeMap()` oder `MapComponent` verwendet. + +## Kontextmenüeinträge + +| Eintrag | Funktion | +| -------------------- | ----------------------------------------------- | +| Koordinaten anzeigen | Öffnet `CoordinatePopup` mit aktueller Position | +| Reinzoomen | Zoomt 3 Stufen näher an das Zentrum heran | +| Rauszoomen | Zoomt 3 Stufen heraus | +| Hier zentrieren | Verschiebt Kartenzentrum auf Klickposition | +| POI hinzufügen | (nur bei `editMode=true`) öffnet POI-Dialog | + +## Parameter + +```js +addItemsToMapContextMenu(map, menuItemAdded, setMenuItemAdded, setShowCoordinatesModal, setShowPoiModal, setPopupCoordinates, openPopupWithCoordinates); +``` + +## Besonderheiten + +- Prüft auf `localStorage.editMode` für POI-Eintrag +- FlyTo-Animationen für Zoom-Vorgänge mit dynamischer Dauer +- Modularer Aufbau: `openPopupWithCoordinates` wird extern übergeben + + 🔙 [Zurück zu contextmenu ](./README.md) diff --git a/docs/docs/components/gisPolylines/PolylineContextMenu.md b/docs/docs/components/gisPolylines/PolylineContextMenu.md new file mode 100644 index 000000000..32ab16ae0 --- /dev/null +++ b/docs/docs/components/gisPolylines/PolylineContextMenu.md @@ -0,0 +1,34 @@ + + +# 📐 PolylineContextMenu.js + +Ein einfaches benutzerdefiniertes Kontextmenü zur Interaktion mit Linien (Polylinien) auf der Karte. + +## Zweck + +Das Menü erlaubt folgende Interaktionen: + +- ➕ „Stützpunkt hinzufügen“ +- ➖ „Stützpunkt entfernen“ +- ❌ „Schließen“ + +Wird dynamisch positioniert anhand der Klickkoordinaten (`position.x`, `position.y`). + +## Props + +| Prop | Typ | Beschreibung | +|---------------|-----------|----------------------------------------------------| +| `position` | `{x, y}` | Position in Pixelkoordinaten (z. B. von Mausereignis) | +| `onAddPoint` | `function`| Handler für „Stützpunkt hinzufügen“ | +| `onRemovePoint` | `function` | Handler für „Stützpunkt entfernen“ | +| `onClose` | `function`| Handler zum Schließen des Menüs | + +## Styling + +- Absolut positioniertes `div` +- Weißer Hintergrund, schwarzer Rahmen +- Kein Tailwind – purer Inline-Style + +## Verwendung + +Eingebettet z. B. in `setupPolylines.js` oder `PolylineLayerManager`, um rechte Mausklicks auf Linien zu behandeln. diff --git a/docs/docs/components/gisPolylines/icons/CircleIcon.md b/docs/docs/components/gisPolylines/icons/CircleIcon.md new file mode 100644 index 000000000..6b063073c --- /dev/null +++ b/docs/docs/components/gisPolylines/icons/CircleIcon.md @@ -0,0 +1,16 @@ + + +# 🔘 CircleIcon.js + +Ein einfacher, grauer runder Marker als Stützpunkt in einer Polyline. + +## Eigenschaften + +- Stil: grauer Kreis mit schwarzem Rand +- Größe: 10×10 px, IconSize 25×25 px (wegen Klickfläche) +- Klasse: `custom-circle-icon` + +## Verwendung + +Wird in Polylinien als Zwischenpunkt gesetzt. Inaktiv, aber sichtbar. + diff --git a/docs/docs/components/gisPolylines/icons/EndIcon.md b/docs/docs/components/gisPolylines/icons/EndIcon.md new file mode 100644 index 000000000..28189bd5a --- /dev/null +++ b/docs/docs/components/gisPolylines/icons/EndIcon.md @@ -0,0 +1,15 @@ + + +# 🔲 EndIcon.js + +Ein Viereck zur Markierung des Endpunkts einer Polyline. + +## Eigenschaften + +- Stil: graues Quadrat mit schwarzem Rand +- Größe: 14×14 px +- Klasse: `custom-end-icon` + +## Verwendung + +Wird am letzten Punkt einer Linie gesetzt, z. B. `lineData.coordinates[line.length - 1]` diff --git a/docs/docs/components/gisPolylines/icons/StartIcon.md b/docs/docs/components/gisPolylines/icons/StartIcon.md new file mode 100644 index 000000000..a09d2a3d9 --- /dev/null +++ b/docs/docs/components/gisPolylines/icons/StartIcon.md @@ -0,0 +1,15 @@ + + +# 🔺 StartIcon.js + +Ein SVG-Dreieck zur Markierung des Startpunkts einer Polyline. + +## Eigenschaften + +- Schwarzes Dreieck mit grauem Overlay (Polygon SVG) +- Größe: 18×18 px +- Klasse: `custom-start-icon` + +## Verwendung + +Wird am ersten Punkt einer Polyline platziert. diff --git a/docs/docs/components/gisPolylines/icons/SupportPointIcons.md b/docs/docs/components/gisPolylines/icons/SupportPointIcons.md new file mode 100644 index 000000000..1e0e102a6 --- /dev/null +++ b/docs/docs/components/gisPolylines/icons/SupportPointIcons.md @@ -0,0 +1,20 @@ + + +# ➕➖ SupportPointIcons.js + +Definiert zwei Icons für interaktive Stützpunkte in einer Polyline: + +## AddSupportPointIcon + +- Grüner Kreis mit weißem Rand und Pluszeichen +- `iconSize`: 24×24 px + +## RemoveSupportPointIcon + +- Roter Kreis mit weißem Rand und Minuszeichen +- `iconSize`: 24×24 px + +## Verwendung + +- Hinzufügen/Entfernen von Zwischenpunkten in der Bearbeitungsansicht (editMode) +- Marker erscheinen z. B. bei Maus-Hover oder per Kontextmenü diff --git a/docs/docs/components/icons/devices/overlapping/PlusRoundIcon.md b/docs/docs/components/icons/devices/overlapping/PlusRoundIcon.md new file mode 100644 index 000000000..42d3b2569 --- /dev/null +++ b/docs/docs/components/icons/devices/overlapping/PlusRoundIcon.md @@ -0,0 +1,26 @@ + + +# ➕ PlusRoundIcon.js + +Ein einfaches Leaflet-Icon, das ein rundes Pluszeichen darstellt. +Wird für zusätzliche UI-Markierungen auf Geräten oder überlappenden Icons verwendet. + +## Eigenschaften + +| Attribut | Wert | +|--------------|--------------------| +| `iconUrl` | `/img/plus_round.png` | +| `iconSize` | `[22, 22]` | +| `iconAnchor` | `[25, 55]` | +| `className` | `absolute top-0 left-0 z-10` (Tailwind) + +## Verwendung + +- Dient als Overlay-Symbol, z. B. für „Gerät hinzufügen“ oder zur Darstellung über bestehenden Icons +- Durch die `z-10`-Klasse immer im Vordergrund sichtbar +- Kombinierbar mit OverlappingMarkerSpiderfier oder Marker-Gruppen + +## Hinweis + +- Die Bilddatei `/img/plus_round.png` muss vorhanden sein +- Kann bei Bedarf dynamisch durch ein anderes Icon ersetzt werden diff --git a/docs/docs/components/mainComponent/MapComponent.md b/docs/docs/components/mainComponent/MapComponent.md new file mode 100644 index 000000000..7099b50c9 --- /dev/null +++ b/docs/docs/components/mainComponent/MapComponent.md @@ -0,0 +1,71 @@ + + +# 🗺️ MapComponent.js + +Die zentrale React-Komponente zur Darstellung und Steuerung der Leaflet-Karte. +Bindet alle Marker, Layer, POIs, Linien und das Kontextmenü dynamisch ein. + +--- + +## 🎯 Zweck + +- Initialisiert die Leaflet-Karte (`useInitializeMap`) +- Bindet Marker & Polylinien über Redux und eigene Hooks +- Steuerung über Redux-Slices wie `selectedArea`, `zoomTrigger`, `polylineVisible` +- Kontextmenüs für Karte, POIs, Polylinien +- Unterstützung für Editierfunktionen über `editMode` (localStorage) + +--- + +## 🧱 Hauptbestandteile + +- `useEffect`-Hooks zum Laden und Aktualisieren von: + - Kartenlayern, POIs, Linien, Rechte, Systeme, Positionen +- Marker-Logik für 15+ Layergruppen (TALAS, ECI, GMA, etc.) +- Marker-Overlapping mit `OverlappingMarkerSpiderfier` +- Kontextmenüs (Karte & Polylinie) +- UI-Komponenten: + - `MapLayersControlPanel` + - `CoordinateInput` + - `CoordinatePopup` + - `AddPOIModal`, `PoiUpdateModal`, `VersionInfoModal` + +--- + +## 🧠 Zustand & Redux + +Verwendet umfangreiche Redux-Slices zur Steuerung von: + +- Linienstatus, POI-Typen, POI-Icons +- Gerätesysteme & Rechte +- Sichtbarkeit einzelner Layergruppen +- Aktuelle Selektion (Area, Gerät, POI) + +--- + +## 🔧 Lokale Steuerung + +- EditMode wird aus `localStorage` gelesen +- Karte speichert Zoom & Center dauerhaft im Browser +- Kontextmenü-Einträge ändern sich je nach Rechten & Modus + +--- + +## 🧪 Besonderheiten + +- Fehlerbehandlung für `contextmenu`-Fehler eingebaut → Auto-Neuladen +- Alle Marker-Updates mit Overlapping-Check & Z-Index-Steuerung +- Linien enthalten dynamische Tooltips mit `tooltipContents` +- Initiale Datenabfrage über Redux-Thunk-Kaskade + +--- + +## 🔗 Abhängigkeiten + +- Leaflet, OverlappingMarkerSpiderfier, React-Toastify +- Redux Toolkit (Thunks + Selectors) +- Tailwind CSS für visuelles Layout + +--- + +📄 Pfad: `/components/mainComponent/MapComponent.js` diff --git a/docs/docs/components/mainComponent/hooks/useInitializeMap.md b/docs/docs/components/mainComponent/hooks/useInitializeMap.md new file mode 100644 index 000000000..05b5acfa9 --- /dev/null +++ b/docs/docs/components/mainComponent/hooks/useInitializeMap.md @@ -0,0 +1,53 @@ + + +# 🪄 useInitializeMap.js + +Custom React-Hook zur Initialisierung der Leaflet-Karte. +Ermöglicht die einfache Übergabe aller nötigen Parameter und abstrahiert die `initializeMap(...)`-Logik. + +--- + +## 📦 Zweck + +- Führt `initializeMap(...)` nur **einmal** aus, wenn `mapRef` existiert und `map === null` +- Kapselt die Initialisierung in ein `useEffect` + +--- + +## 🔧 Parameter + +| Name | Typ | Beschreibung | +|--------------------------|------------|---------------------------------------------------| +| `map` | `LeafletMap` (Zustand) | Wird initialisiert, wenn `null` | +| `mapRef` | `ref` | Referenz auf `

` | +| `setMap` | `function` | Callback zum Setzen der Karteninstanz | +| `setOms` | `function` | Callback für OverlappingMarkerSpiderfier | +| `setMenuItemAdded` | `function` | Wird genutzt, um mehrfaches Menü-Setup zu verhindern | +| `addItemsToMapContextMenu` | `function` | Logik zum Hinzufügen von Kontextmenüeinträgen | +| `hasRights` | `boolean` | Steuerung, ob POI-Menüs angezeigt werden dürfen | +| `setPolylineEventsDisabled` | `function` | Aktiviert/Deaktiviert Polyline-Events global | + +--- + +## 🌐 Verwendung + +In `MapComponent.js`: + +```js +useInitializeMap( + map, + mapRef, + setMap, + setOms, + setMenuItemAdded, + addItemsToMapContextMenu, + hasRights, + (value) => dispatch(setDisabled(value)) +); +``` + +--- + +## 📁 Quelle + +Wrappt `initializeMap()` aus `/utils/initializeMap.js` diff --git a/docs/docs/components/pois/AddPOIModal.md b/docs/docs/components/pois/AddPOIModal.md new file mode 100644 index 000000000..0a3017925 --- /dev/null +++ b/docs/docs/components/pois/AddPOIModal.md @@ -0,0 +1,28 @@ + + +# ➕ AddPOIModal.js + +Zeigt ein modales Formular an, um einen neuen POI auf der Karte zu erstellen. +Die Koordinaten (`latlng`) werden automatisch übernommen. + +## Funktionen + +- POI-Name, Typ und zugehöriges Gerät auswählbar +- Koordinatenanzeige (`lat`, `lng`) +- Dynamisches Laden der Gerätedaten und POI-Typen +- Fehleranzeige bei fehlgeschlagenem Speichern +- Löst `addPoiThunk` + Refresh-Trigger (`incrementTrigger`) aus + +## Props + +| Prop | Typ | Beschreibung | +|----------|-----------|--------------------------------------------------| +| `onClose` | `function` | Schließt das Modal | +| `map` | `Leaflet` | (optional) zum Schließen evtl. offener Popups | +| `latlng` | `object` | Koordinaten für den neuen POI | + +## Redux + +- `fetchPoiTypThunk`, `fetchPoiIconsDataThunk` +- `addPoiThunk`, `resetAddPoiStatus` + diff --git a/docs/docs/components/pois/PoiUpdateModal.md b/docs/docs/components/pois/PoiUpdateModal.md new file mode 100644 index 000000000..df17e5ec7 --- /dev/null +++ b/docs/docs/components/pois/PoiUpdateModal.md @@ -0,0 +1,29 @@ + + +# ✏️ PoiUpdateModal.js + +Ein Dialog zur Aktualisierung oder Löschung bestehender POIs. + +## Features + +- Zeigt aktuellen Namen, Beschreibung, Gerät und Typ +- Gerät und Typ auswählbar via `react-select` +- Unterstützt Löschen und Speichern von POIs +- Eingebundene Sicherheitsabfrage bei Löschen + +## Props + +| Prop | Typ | Beschreibung | +|------------|-----------|---------------------------------------| +| `onClose` | `function`| Schließt das Modal | +| `poiData` | `object` | Bestehende POI-Daten zur Bearbeitung | + +## Redux + +- `updatePoiThunk`, `deletePoiThunk` +- `fetchLocationDevicesThunk`, `fetchPoiTypThunk` + +## Technisches + +- Dynamische Gerätegruppenfilterung basierend auf `mapLayersVisibility` +- Formfelder mit `react-select` für bessere UX diff --git a/docs/docs/components/uiWidgets/CoordinateInput.md b/docs/docs/components/uiWidgets/CoordinateInput.md new file mode 100644 index 000000000..ebbb81513 --- /dev/null +++ b/docs/docs/components/uiWidgets/CoordinateInput.md @@ -0,0 +1,101 @@ + + +# 📍 CoordinateInput.js + +Die Komponente `CoordinateInput` stellt ein einfaches Eingabefeld für geografische Koordinaten (Latitude, Longitude) bereit. +Sie dient typischerweise dazu, einen bestimmten Punkt auf der Karte zu fokussieren bzw. zu markieren. + +--- + +## 🔧 Pfad + +```bash +/components/uiWidgets/CoordinateInput.js +``` + +--- + +## 🎯 Zweck + +- Eingabe von Koordinaten (z. B. `53.2,8.1`) +- Übergabe dieser Koordinaten an eine Callback-Funktion zur weiteren Verarbeitung +- Positioniert sich dauerhaft in der linken oberen Ecke der Seite (z. B. zur schnellen Navigation) + +--- + +## ⚙️ Props + +| Prop | Typ | Beschreibung | +| --------------------- | ---------- | ------------------------------------------------------------------------------------- | +| `onCoordinatesSubmit` | `function` | Wird beim Abschicken des Formulars mit dem eingegebenen Koordinaten-String aufgerufen | + +--- + +## 🧩 Interne Logik + +```js +const [coordinates, setCoordinates] = useState(""); +``` + +- Der Eingabewert wird im lokalen State gespeichert +- Beim Submit (`onSubmit`) wird `onCoordinatesSubmit(coordinates)` aufgerufen, wenn gesetzt + +--- + +## 🧰 UI-Aufbau + +- Eingabefeld für Text: Erwartet `lat,lng` +- Button: „Zu Marker zoomen“ +- Position: `fixed top-5 left-5` → dauerhaft sichtbar + +--- + +## 🎨 Gestaltung (Tailwind CSS) + +| Element | Klassen | +| --------- | ---------------------------------------------------------------- | +| Container | `fixed top-5 left-5 z-50 bg-white shadow-lg rounded-lg p-4 w-72` | +| Input | `border p-2 rounded w-full mb-2` | +| Button | `bg-blue-500 text-white p-2 rounded w-full hover:bg-blue-600` | + +--- + +## 🧪 Testfälle + +| Eingabe | Erwartung | +| -------------------------- | --------------------------------------------------------- | +| `53.2,8.1` | Callback `onCoordinatesSubmit("53.2,8.1")` wird ausgelöst | +| Leer | Callback wird ausgelöst mit leerem String | +| Buttonklick | Löst `handleSubmit()` aus | +| Enter-Taste im Eingabefeld | Löst ebenfalls Submit aus | + +--- + +## 💡 Erweiterungsideen + +- Validierung des Formats (`lat,lng`) vor dem Absenden +- Automatisches Zentrieren der Leaflet-Karte in der Callback-Funktion +- Optionale Markierung des Punkts auf der Karte + +--- + +## 📄 Verwendung + +Beispiel in einer Map-Komponente: + +```jsx + { + const [lat, lng] = coords.split(",").map(Number); + map.setView([lat, lng], 16); // Leaflet + }} +/> +``` + +--- + +## 📦 Verwandte Komponenten + +- `MapComponent.js` – kann die übergebenen Koordinaten zur Zentrierung oder Marker-Erstellung nutzen + +--- diff --git a/docs/docs/components/uiWidgets/VersionInfoModal.md b/docs/docs/components/uiWidgets/VersionInfoModal.md new file mode 100644 index 000000000..e211afb8e --- /dev/null +++ b/docs/docs/components/uiWidgets/VersionInfoModal.md @@ -0,0 +1,92 @@ + + +# 🪪 VersionInfoModal.js + +Das `VersionInfoModal` ist ein modales Fenster zur Anzeige von Unternehmensinformationen und der aktuellen App-Version. +Es wird meist im Footer oder als Info-Schaltfläche in der Benutzeroberfläche eingeblendet. + +--- + +## 🔧 Pfad + +```bash +/components/uiWidgets/VersionInfoModal.js +``` + +--- + +## 🎯 Zweck + +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 + +--- + +## ⚙️ Props + +| Prop | Typ | Beschreibung | +| ----------------------- | ---------- | -------------------------------------------------------------- | +| `showVersionInfoModal` | `boolean` | Steuert, ob das Modal angezeigt wird | +| `closeVersionInfoModal` | `function` | Callback zum Schließen des Modals | +| `APP_VERSION` | `string` | Versionstext (z. B. `1.1.188`), meist aus `.env.local` geladen | + +--- + +## 💡 Verhalten + +- Wird `showVersionInfoModal` auf `true` gesetzt, erscheint das Modal zentriert über einem halbtransparenten Overlay +- Klick auf den Hintergrund (schwarzes Overlay) oder auf „Schließen“ führt `closeVersionInfoModal()` aus + +--- + +## 🧩 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`) +- Schaltfläche `Schließen` reagiert auf Hover mit Farbwechsel (`hover:bg-blue-700`) +- Design folgt der UI-Ästhetik von TALAS.web + +--- + +## 🧪 Testfälle + +| Bedingung | Erwartung | +| ------------------------------- | ----------------------------------------- | +| `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 | + +--- + +## 📦 Verknüpfte Dateien + +- `.env.local` enthält z. B. `NEXT_PUBLIC_APP_VERSION=1.1.188` +- Aufruf in `Footer` oder `Layout` zur Anzeige bei Klick auf „Version“ + +--- + +## 🛠 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` + +--- diff --git a/docs/docs/components/uiWidgets/mapLayersControlPanel/EditModeToggle.md b/docs/docs/components/uiWidgets/mapLayersControlPanel/EditModeToggle.md new file mode 100644 index 000000000..237bf2d8c --- /dev/null +++ b/docs/docs/components/uiWidgets/mapLayersControlPanel/EditModeToggle.md @@ -0,0 +1,85 @@ + + +# ✏️ EditModeToggle.js + +Die Komponente `EditModeToggle` stellt einen interaktiven Umschalter für den Bearbeitungsmodus bereit. +Sie ermöglicht das Ein- und Ausschalten des Modus, in dem POIs, Polylines (Strecken) und Bereiche bearbeitet werden können. + +--- + +## 📦 Pfad + +```bash +/components/uiWidgets/mapLayersControlPanel/EditModeToggle.js +``` + +--- + +## 🧩 Zweck + +Der Bearbeitungsmodus wirkt sich auf die Interaktivität der Map aus: + +- Wenn **aktiv**: + - Checkboxen für Layer sind deaktiviert + - POI-Funktionen (Hinzufügen, Verschieben, Löschen) werden ermöglicht +- Wenn **inaktiv**: + - Keine Bearbeitung möglich + - UI ist auf Betrachtung beschränkt + +--- + +## 🖱 Verhalten + +Beim Klick auf das Icon: + +1. Wird der lokale Zustand `editMode` umgeschaltet +2. `localStorage` speichert den neuen Status (`true` oder `false`) +3. Die Seite wird neu geladen (`window.location.reload()`), um globale Effekte zu aktivieren + +--- + +## 🧠 Interner Zustand + +```js +const [editMode, setEditMode] = useState(() => localStorage.getItem("editMode") === "true"); +``` + +- Initialisiert aus `localStorage` +- Persistente Speicherung des Zustands browserseitig +- Aufruf in anderen Komponenten (z. B. `MapLayersControlPanel.js`) basiert ebenfalls auf diesem Wert + +--- + +## 🧰 UI-Darstellung + +- Verwendet **Material-UI-Icons**: + - 🟢 `ModeEditIcon`: Bearbeitungsmodus **aus** → wird angeboten zum **Aktivieren** + - 🔴 `EditOffIcon`: Bearbeitungsmodus **ein** → wird angeboten zum **Deaktivieren** +- Tooltip informiert den Nutzer über die jeweilige Aktion + +--- + +## 🧪 Testfälle + +| Zustand | Erwartetes Verhalten | +| ------------------ | ------------------------------------------------------ | +| `editMode = false` | Icon: ✏️ → Tooltip: „Bearbeitungsmodus aktivieren“ | +| `editMode = true` | Icon: 🚫✏️ → Tooltip: „Bearbeitungsmodus deaktivieren“ | +| Klick auf Icon | Status umschalten, Seite neu laden | + +--- + +## 💡 Erweiterungsideen + +- 🔄 Statt `window.location.reload()` → globalen Zustand über Redux-Dispatch steuern +- 📢 Feedback-Toast nach Umschalten anzeigen (z. B. „Bearbeitungsmodus aktiviert“) +- 🧩 Integration in Redux-Store zur globalen Synchronisierung ohne Reload + +--- + +## 📄 Verwandte Komponenten + +- `MapLayersControlPanel.js`: liest `localStorage.editMode` und deaktiviert Layer-Checkboxen im aktiven Modus +- `PoiUpdateModal`, `AddPOIModal`: nutzen den Bearbeitungsmodus für UI-Freigabe + +--- diff --git a/docs/docs/components/uiWidgets/mapLayersControlPanel/MapLayersControlPanel.md b/docs/docs/components/uiWidgets/mapLayersControlPanel/MapLayersControlPanel.md new file mode 100644 index 000000000..8f47f7b7e --- /dev/null +++ b/docs/docs/components/uiWidgets/mapLayersControlPanel/MapLayersControlPanel.md @@ -0,0 +1,150 @@ + + +# 🧭 MapLayersControlPanel.js + +Dieses UI-Widget zeigt eine interaktive Steuereinheit für Layer, POIs und Stationsbereiche auf der rechten Seite der Karte. +Es ist vollständig an den Redux-Store angebunden und reagiert auf Änderungen der Layer-Sichtbarkeit, Bearbeitungsmodus und Stationsauswahl. + +--- + +## 🔧 Pfad + +```bash +/components/uiWidgets/mapLayersControlPanel/MapLayersControlPanel.js +``` + +--- + +## 📌 Zweck + +Das `MapLayersControlPanel` ermöglicht Nutzern: + +- Die Auswahl eines Stationsbereichs (Dropdown) +- Das Aktivieren/Deaktivieren einzelner GIS-Systeme (Checkboxen) +- Das Anzeigen von POIs oder Kabelstrecken (TALAS-spezifisch) +- Das Ein-/Ausschalten des Bearbeitungsmodus +- Die Steuerung der Karten-Zentrierung über ein Icon + +--- + +## 🧠 Verwendete Redux-Slices + +| Slice | Zweck | +| -------------------------------- | ----------------------------------------------------------- | +| `gisStationsStaticDistrictSlice` | Enthält die Gerätebereiche (mit `.Points`) | +| `gisSystemStaticSlice` | Enthält die konfigurierten GIS-Systeme mit Anzeigeerlaubnis | +| `mapLayersSlice` | Speichert die Sichtbarkeit aller Layer | +| `poiLayerVisibleSlice` | Steuert Sichtbarkeit der POIs | +| `polylineLayerVisibleSlice` | Steuert Sichtbarkeit der Kabelstrecken (TALAS) | +| `zoomTriggerSlice` | Löst Neuzentrierung der Karte aus | +| `selectedAreaSlice` | Speichert den gewählten Bereich/Station | + +--- + +## 🔄 Logikübersicht + +- **Dropdown Stationsauswahl:** + Wird dynamisch aus `GisStationsStaticDistrict.Points` befüllt + Nur eindeutige `Area_Name`, wenn `System` erlaubt ist + +- **Checkboxen für Layer:** + Zeigen alle Systeme aus `GisSystemStatic`, bei denen `Allow === 1` + Sonderfall: `TALAS` erhält ein Untermenü für „Kabelstrecken“ + +- **Lokale Speicherung:** + Sichtbarkeiten, Bearbeitungsmodus und POI-Zustand werden in `localStorage` geschrieben und bei Initialisierung geladen + +- **Bearbeitungsmodus:** + Wenn aktiv (`editMode === true`), sind Layer-Checkboxen deaktiviert + +--- + +## 📥 Wichtige Funktionen + +| Funktion | Zweck | +| -------------------------------- | ---------------------------------------- | +| `handleAreaChange()` | Setzt `selectedArea` im Redux Store | +| `handleCheckboxChange()` | Schaltet Sichtbarkeit einzelner Layer | +| `handlePolylineCheckboxChange()` | Aktiviert Sichtbarkeit von Kabelstrecken | +| `handlePoiCheckboxChange()` | Aktiviert Sichtbarkeit von POIs | +| `handleIconClick()` | Setzt Station zurück und triggert Zoom | + +--- + +## 🧩 UI-Struktur + +```plaintext +[Dropdown: Station wählen] +[🟩 EditModeToggle] [🔍 Expand-Icon] + +[ ] GIS-System 1 +[ ] GIS-System 2 + └─ [ ] Kabelstrecken (falls "TALAS") + +[ ] POIs +``` + +--- + +## 🐞 Debug-Hinweise + +- Debug-Logs: + `console.log("🔍 GisStationsStaticDistrict Inhalt:", ...)` + werden ausgegeben, um sicherzustellen, dass Daten korrekt geladen wurden + +- Warnungen: + Falls `.Points` nicht vorhanden ist, wird dies in der Konsole gewarnt + +--- + +## 🛠 ToDos / Erweiterungsideen + +- Checkboxen für Bereiche („Bereiche“, „Standorte“) sind bereits vorbereitet, aber auskommentiert +- Möglichkeit, Tooltips zu aktivieren/deaktivieren? +- Gruppierung von Layern nach Typ (z. B. Linien, Geräte, POIs) + +--- + +## 📄 Verwendete Komponenten + +- `EditModeToggle` – Schaltfläche für Umschalten des Bearbeitungsmodus + +--- + +## ✅ Zustand: Lokal & Global + +- **Global:** `useSelector(...)` aus Redux +- **Lokal:** `useState(...)` für editMode, stationListing, systemListing + +--- + +## 📦 LokaleStorage-Keys + +| Key | Beschreibung | +| --------------------- | ------------------------------------------ | +| `poiVisible` | Sichtbarkeit der POI-Marker | +| `polylineVisible` | Sichtbarkeit der Kabelstrecken | +| `mapLayersVisibility` | Sichtbarkeiten der einzelnen Systeme | +| `editMode` | Zustand des Bearbeitungsmodus (true/false) | + +--- + +## 🧪 Testempfehlung + +- Dropdown zeigt erwartete `Area_Name`-Werte? +- Layer-Checkboxen werden korrekt gespeichert? +- Bei `TALAS` erscheint zusätzlich: „Kabelstrecken“? +- Bei Wechsel der Station wird `setSelectedArea` ausgelöst? + +--- + +## 🧩 Verknüpfte Dateien + +- `redux/slices/webservice/gisStationsStaticDistrictSlice.js` +- `redux/slices/webservice/gisSystemStaticSlice.js` +- `redux/slices/mapLayersSlice.js` +- `redux/slices/selectedAreaSlice.js` +- `redux/slices/database/polylines/polylineLayerVisibleSlice.js` +- `redux/slices/database/pois/poiLayerVisibleSlice.js` + +--- diff --git a/docs/docs/config/README.md b/docs/docs/config/README.md new file mode 100644 index 000000000..92b772c2f --- /dev/null +++ b/docs/docs/config/README.md @@ -0,0 +1,38 @@ + + +# ⚙️ Konfigurationsübersicht (/config) + +Dieses Verzeichnis enthält zentrale Konfigurationsdateien, die das Verhalten der gesamten App steuern. +Hier sind die wichtigsten Dateien, ihre Aufgaben und Verlinkungen zur Dokumentation: + +--- + +## 📦 [`appVersion.js`](./appVersion.md) + +- Definiert die aktuelle Version der App (`APP_VERSION`) +- Wird z. B. im `VersionInfoModal` angezeigt + +--- + +## 🗺️ [`layers.js`](./layers.md) + +- Enthält alle Leaflet-Layergruppen für die Kartenanzeige +- Zentrale Steuerung der aktiven Layer: TALAS, GMA, Cisco, etc. + +--- + +## 📁 [`paths.js`](./paths.md) + +- Berechnet den Basis-Pfad aus `.env.local` +- Liefert `BASE_URL`, z. B. `/talas5` + +--- + +## 🌐 [`urls.js`](./urls.md) + +- Erzeugt dynamisch API- und Tile-URLs +- Verwendet `window.location.origin` → keine statischen Ports notwendig + +--- + +Diese Konfiguration macht das Projekt flexibel für mehrere Hosting-Umgebungen. diff --git a/docs/docs/config/appVersion.md b/docs/docs/config/appVersion.md new file mode 100644 index 000000000..092947d6f --- /dev/null +++ b/docs/docs/config/appVersion.md @@ -0,0 +1,16 @@ + + +# 📦 appVersion.js + +Diese Datei exportiert die aktuelle App-Version, die an mehreren Stellen in der UI angezeigt werden kann – z. B. im `VersionInfoModal`. + +## Inhalt + +```js +export const APP_VERSION = "1.1.193"; +``` + +## Verwendung + +- Im Footer oder Info-Fenster +- Vergleich von Client- vs. Serverversion diff --git a/docs/docs/config/config.md b/docs/docs/config/config.md new file mode 100644 index 000000000..ca279618f --- /dev/null +++ b/docs/docs/config/config.md @@ -0,0 +1,60 @@ + + +# ⚙️ config.js – zentrale Konfiguration und Umgebungssteuerung + +## Zweck + +Diese Datei enthält zentrale Konfigurationswerte, die abhängig von der Umgebung +(Entwicklung oder Produktion) dynamisch erzeugt werden. + +--- + +## Ersetzungen von Umgebungsvariablen + +Vorher wurden folgende `.env.local` Variablen verwendet: + +- `NEXT_PUBLIC_BASE_URL` +- `NEXT_PUBLIC_SERVER_URL` + +Diese wurden ersetzt durch dynamische Berechnung anhand von: + +```env +NEXT_PUBLIC_API_PORT_MODE=dev +``` + +--- + +## Dynamische Berechnung von `serverURL` + +Die Konfiguration entscheidet anhand des Modus: + +```js +const mode = process.env.NEXT_PUBLIC_API_PORT_MODE; + +const serverURL = mode === "dev" ? `${window.location.protocol}//${window.location.hostname}:80` : `${window.location.origin}`; +``` + +→ Dadurch funktioniert der Code ohne Anpassung bei IP-/Server-Wechseln oder Portunterschieden. + +--- + +## Konfigurationswerte + +- `USE_MOCK_API`: aktiviert lokale Mock-Daten +- `serverURL`: Basis für Webservice-Aufrufe (`/talas5/...`) +- `mapGisStationsStaticDistrictUrl`: komplette zusammengesetzte URL +- `useMockStationData`: true/false aus `.env.local` + +--- + +## Vorteile + +| Punkt | Vorteil | +| ------------------------------- | ---------------------------------------- | +| Keine festen IPs oder Ports | ✅ Weniger Fehler, einfacher Umzug | +| Einheitlich mit anderen Dateien | ✅ Gleiche Struktur wie Webservice-Setup | +| Lesbar & leicht anpassbar | ✅ Auch ohne Doku sofort verständlich | + +--- + +📄 Pfad: `/docs/frontend/config/config.md` diff --git a/docs/docs/config/layers.md b/docs/docs/config/layers.md new file mode 100644 index 000000000..dca2328a3 --- /dev/null +++ b/docs/docs/config/layers.md @@ -0,0 +1,21 @@ + + +# 🗺️ layers.js + +Diese Datei definiert alle verfügbaren Leaflet-Layergruppen im Projekt. +Sie werden global als `MAP_LAYERS` exportiert und enthalten alle Systemtypen (TALAS, GMA, OTDR etc.). + +## Struktur + +```js +export const MAP_LAYERS = { + TALAS: new L.layerGroup(), + ... + lineLayer: new L.LayerGroup(), +}; +``` + +## Verwendung + +- Initialisierung der Leaflet-Karte +- Zuweisung von Markern und Linien diff --git a/docs/docs/config/paths.md b/docs/docs/config/paths.md new file mode 100644 index 000000000..39dfd3ba7 --- /dev/null +++ b/docs/docs/config/paths.md @@ -0,0 +1,19 @@ + + +# 📁 paths.js + +Berechnet den sauberen `BASE_URL`-Pfad basierend auf `.env.local → NEXT_PUBLIC_BASE_PATH`. +Entfernt führende und abschließende Slashes. + +## Beispiel + +Wenn `NEXT_PUBLIC_BASE_PATH = "/talas5/"`, wird `BASE_URL = "/talas5"` gesetzt. + +```js +const BASE_PATH = basePathRaw.replace(/^\/|\/$/g, ""); +export const BASE_URL = BASE_PATH ? `/${BASE_PATH}` : ""; +``` + +## Nutzung + +- Für konsistente Pfadangaben im gesamten Projekt diff --git a/docs/docs/config/urls.md b/docs/docs/config/urls.md new file mode 100644 index 000000000..ec94784eb --- /dev/null +++ b/docs/docs/config/urls.md @@ -0,0 +1,18 @@ + + +# 🌐 urls.js + +Diese Datei berechnet dynamisch URLs basierend auf `window.location.origin`. +Alle Endpunkte (API, Tiles, Server) werden ohne Port oder Hardcoding erzeugt. + +## Exportierte Konstanten + +- `BASE_URL` → `/api` +- `SERVER_URL` → Hostname ohne Port (für Links) +- `PROXY_TARGET` → z. B. `http://hostname:4000` +- `OFFLINE_TILE_LAYER` → Offline-Kachelpfad +- `MAP_TILES_LAYER` → Alias für `OFFLINE_TILE_LAYER` + +## Hinweis + +Alle Berechnungen erfolgen nur **clientseitig** (`typeof window !== "undefined"`). diff --git a/docs/docs/env.local..md b/docs/docs/env.local..md new file mode 100644 index 000000000..8cff0f562 --- /dev/null +++ b/docs/docs/env.local..md @@ -0,0 +1,7 @@ + + +### /docs/env.local.schema.md + +- `NEXT_PUBLIC_API_HOST` → Webservice-DNS oder IP +- `NEXT_PUBLIC_API_BASE_PATH` → z. B. `talas5`, per Deployment steuerbar +- `DB_NAME` → hängt vom Kundenprojekt ab diff --git a/docs/docs/hooks/layers/useAreaMarkersLayer.md b/docs/docs/hooks/layers/useAreaMarkersLayer.md new file mode 100644 index 000000000..6b8d698fc --- /dev/null +++ b/docs/docs/hooks/layers/useAreaMarkersLayer.md @@ -0,0 +1,13 @@ + + +# 🗺️ useAreaMarkersLayer.js + +Lädt Bereichs-/Stationsmarker aus einer API und rendert sie auf der Karte. + +## Features + +- Marker mit Tooltip für Standort & Bereich +- Draggable Marker (verschiebbar) +- Automatischer API-Fetch mit `fetch(...)` +- Dynamisches Layer-Handling via localStorage ("mapLayersVisibility") +- Automatisches Speichern neuer Koordinaten per `updateAreaThunk()` diff --git a/docs/docs/hooks/layers/useCiscoRouterMarkersLayer.md b/docs/docs/hooks/layers/useCiscoRouterMarkersLayer.md new file mode 100644 index 000000000..18947c16c --- /dev/null +++ b/docs/docs/hooks/layers/useCiscoRouterMarkersLayer.md @@ -0,0 +1,11 @@ + + +# 🌐 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(...)` diff --git a/docs/docs/hooks/layers/useDauzMarkersLayer.md b/docs/docs/hooks/layers/useDauzMarkersLayer.md new file mode 100644 index 000000000..6cac8ac43 --- /dev/null +++ b/docs/docs/hooks/layers/useDauzMarkersLayer.md @@ -0,0 +1,11 @@ + + +# 🔧 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 diff --git a/docs/docs/hooks/layers/useDrawLines.md b/docs/docs/hooks/layers/useDrawLines.md new file mode 100644 index 000000000..039d75015 --- /dev/null +++ b/docs/docs/hooks/layers/useDrawLines.md @@ -0,0 +1,11 @@ + + +# 🧬 useDrawLines.js + +Hook zur Konvertierung von GIS-Linien in kartentaugliche Koordinatenpaare. + +## Schritte + +- Lädt Linien mit `fetchGisLinesThunk()` +- Wandelt `points[x, y]` in Leaflet-Koordinaten `[lat, lng]` um +- Gibt `setLinePositions([...])` zurück diff --git a/docs/docs/hooks/layers/useEciMarkersLayer.md b/docs/docs/hooks/layers/useEciMarkersLayer.md new file mode 100644 index 000000000..b228e4b92 --- /dev/null +++ b/docs/docs/hooks/layers/useEciMarkersLayer.md @@ -0,0 +1,11 @@ + + +# 🛰️ 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(...)` diff --git a/docs/docs/hooks/layers/useGmaMarkersLayer.md b/docs/docs/hooks/layers/useGmaMarkersLayer.md new file mode 100644 index 000000000..d4dee7f2b --- /dev/null +++ b/docs/docs/hooks/layers/useGmaMarkersLayer.md @@ -0,0 +1,11 @@ + + +# 🌡️ 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 diff --git a/docs/docs/hooks/layers/useLteModemMarkersLayer.md b/docs/docs/hooks/layers/useLteModemMarkersLayer.md new file mode 100644 index 000000000..e7f411b27 --- /dev/null +++ b/docs/docs/hooks/layers/useLteModemMarkersLayer.md @@ -0,0 +1,10 @@ + + +# 📶 useLteModemMarkersLayer.js + +Steuert Marker vom Typ LTE-Modem (System-ID: 5) + +## Features + +- Standard-Kontextmenü + Popup +- Integration mit OMS und Overlap-Check diff --git a/docs/docs/hooks/layers/useMessstellenMarkersLayer.md b/docs/docs/hooks/layers/useMessstellenMarkersLayer.md new file mode 100644 index 000000000..3e3cddb78 --- /dev/null +++ b/docs/docs/hooks/layers/useMessstellenMarkersLayer.md @@ -0,0 +1,10 @@ + + +# 🧾 useMessstellenMarkersLayer.js + +Für Messstellen-Marker (System-ID: 13) + +## Verhalten + +- Einfache Marker mit Tooltip +- Nutzung von `createAndSetDevices(...)` + Kontextmenü diff --git a/docs/docs/hooks/layers/useOtdrMarkersLayer.md b/docs/docs/hooks/layers/useOtdrMarkersLayer.md new file mode 100644 index 000000000..b7d79ff12 --- /dev/null +++ b/docs/docs/hooks/layers/useOtdrMarkersLayer.md @@ -0,0 +1,10 @@ + + +# 🔍 useOtdrMarkersLayer.js + +Darstellung von OTDR-Messpunkten (System-ID: 9) + +## Funktionen + +- Popup-Interaktion beim Hover +- Marker mit Kontextmenü via `addContextMenuToMarker` diff --git a/docs/docs/hooks/layers/useSiemensMarkersLayer.md b/docs/docs/hooks/layers/useSiemensMarkersLayer.md new file mode 100644 index 000000000..8dc2c6ae4 --- /dev/null +++ b/docs/docs/hooks/layers/useSiemensMarkersLayer.md @@ -0,0 +1,7 @@ +# 🏭 useSiemensMarkersLayer.js + +Für Siemens-Geräte (System-ID: 8). + +- Marker mit Kontextmenü und Overlap-Prüfung +- Integration mit OMS +- Nutzung von `checkOverlappingMarkers(...)` \ No newline at end of file diff --git a/docs/docs/hooks/layers/useSmsfunkmodemMarkersLayer.md b/docs/docs/hooks/layers/useSmsfunkmodemMarkersLayer.md new file mode 100644 index 000000000..be77c5efc --- /dev/null +++ b/docs/docs/hooks/layers/useSmsfunkmodemMarkersLayer.md @@ -0,0 +1,7 @@ +# 📡 useSmsfunkmodemMarkersLayer.js + +Filtert `GisSystemStatic` nach SMS Modem (System 111 oder Name). + +- Icon: `/img/icons/pois/sms-funkmodem.png` +- Kontextmenü & Popup +- Sichtbarkeit über `isVisible` steuerbar \ No newline at end of file diff --git a/docs/docs/hooks/layers/useSonstigeMarkersLayer.md b/docs/docs/hooks/layers/useSonstigeMarkersLayer.md new file mode 100644 index 000000000..cf876ffef --- /dev/null +++ b/docs/docs/hooks/layers/useSonstigeMarkersLayer.md @@ -0,0 +1,7 @@ +# ❔ useSonstigeMarkersLayer.js + +Für alle Geräte mit System-ID 200 (Sonstige). + +- Klassische Leaflet-Marker +- Kontextmenü und Popup +- Nutzung von `createAndSetDevices(...)` \ No newline at end of file diff --git a/docs/docs/hooks/layers/useTalasMarkersLayer.md b/docs/docs/hooks/layers/useTalasMarkersLayer.md new file mode 100644 index 000000000..ce85c4ffc --- /dev/null +++ b/docs/docs/hooks/layers/useTalasMarkersLayer.md @@ -0,0 +1,6 @@ +# 🌐 useTalasMarkersLayer.js + +Für TALAS-Systeme (System-ID: 1). + +- Popup + Kontextmenü auf Marker +- Fügt Marker zuerst zu OMS, dann zu Karte hinzu \ No newline at end of file diff --git a/docs/docs/hooks/layers/useTalasiclMarkersLayer.md b/docs/docs/hooks/layers/useTalasiclMarkersLayer.md new file mode 100644 index 000000000..cfd112e60 --- /dev/null +++ b/docs/docs/hooks/layers/useTalasiclMarkersLayer.md @@ -0,0 +1,6 @@ +# 🔗 useTalasiclMarkersLayer.js + +Spezialhook für Geräte vom Typ TALASICL (System-ID: 100). + +- Erstellt Marker mit Standardverhalten +- Kontextmenü, Popup, Overlap-Prüfung \ No newline at end of file diff --git a/docs/docs/hooks/layers/useTkComponentsMarkersLayer.md b/docs/docs/hooks/layers/useTkComponentsMarkersLayer.md new file mode 100644 index 000000000..cf615a30b --- /dev/null +++ b/docs/docs/hooks/layers/useTkComponentsMarkersLayer.md @@ -0,0 +1,6 @@ +# ⚙️ useTkComponentsMarkersLayer.js + +Für TK-Komponenten (System-ID: 30). + +- Lädt Marker via `createAndSetDevices` +- Marker-Koordinaten können debug-geloggt werden \ No newline at end of file diff --git a/docs/docs/hooks/layers/useUlafMarkersLayer.md b/docs/docs/hooks/layers/useUlafMarkersLayer.md new file mode 100644 index 000000000..ee55b88cc --- /dev/null +++ b/docs/docs/hooks/layers/useUlafMarkersLayer.md @@ -0,0 +1,7 @@ +# 💡 useUlafMarkersLayer.js + +Spezialhook für ULAF-Systeme (System-ID: 0). + +- Marker mit ULAF-Icon +- Kontextmenü und Popup (statisch) +- Dynamisch generierter Popupinhalt \ No newline at end of file diff --git a/docs/docs/hooks/layers/useWagoMarkersLayer.md b/docs/docs/hooks/layers/useWagoMarkersLayer.md new file mode 100644 index 000000000..144ce8c83 --- /dev/null +++ b/docs/docs/hooks/layers/useWagoMarkersLayer.md @@ -0,0 +1,6 @@ +# 🧰 useWagoMarkersLayer.js + +Für WAGO-Systeme (System-ID: 7). + +- Kontextmenü, Popup, Overlapping-Support +- OMS-Integration und Layer-Hinzufügung \ No newline at end of file diff --git a/docs/docs/hooks/layers/useWdmMarkersLayer.md b/docs/docs/hooks/layers/useWdmMarkersLayer.md new file mode 100644 index 000000000..2453e5542 --- /dev/null +++ b/docs/docs/hooks/layers/useWdmMarkersLayer.md @@ -0,0 +1,7 @@ +# 🔷 useWdmMarkersLayer.js + +Verwaltet WDM-Marker (System-ID: 10) in Leaflet. + +- Marker mit Kontextmenü +- Mouseover-Popup +- Nutzung von `createAndSetDevices(...)` \ No newline at end of file diff --git a/docs/docs/hooks/useCreateAndSetDevices.md b/docs/docs/hooks/useCreateAndSetDevices.md new file mode 100644 index 000000000..05f0d430a --- /dev/null +++ b/docs/docs/hooks/useCreateAndSetDevices.md @@ -0,0 +1,17 @@ + + +# 🛠️ useCreateAndSetDevices.js + +Custom Hook zur Initialisierung von Leaflet-Markern für ein bestimmtes System. +Bindet `createAndSetDevices(...)` automatisch in einen `useEffect`. + +## Parameter + +- `systemId`: ID des Gerätesystems (z. B. 1 = TALAS) +- `setMarkersFunction`: Funktion zum Speichern der erzeugten Marker +- `GisSystemStatic`: Systemdaten aus Redux +- `priorityConfig`: Konfigurationsobjekt zur Prioritätsbewertung + +## Redux + +- Bezieht `polylineEventsDisabled` aus Redux zur Steuerung der Interaktivität diff --git a/docs/docs/hooks/useDynamicMarkerLayers.md b/docs/docs/hooks/useDynamicMarkerLayers.md new file mode 100644 index 000000000..8035346f6 --- /dev/null +++ b/docs/docs/hooks/useDynamicMarkerLayers.md @@ -0,0 +1,17 @@ + + +# 🔄 useDynamicMarkerLayers.js + +Verwaltet alle Marker-Layergruppen dynamisch und modular in einem zentralen Hook. + +## Funktionen + +- Initialisiert LayerGroups für 15+ Gerätesysteme +- Ruft `createAndSetDevices()` pro System-ID auf +- Führt automatisch Overlap-Check aus (`checkOverlappingMarkers`) +- Speichert erzeugte Marker in `setMarkerStates` + +## Voraussetzungen + +- Karte (`map`) muss bereit sein +- `GisSystemStatic` + `priorityConfig` + Marker-Setter müssen übergeben werden diff --git a/docs/docs/hooks/useLayerVisibility.md b/docs/docs/hooks/useLayerVisibility.md new file mode 100644 index 000000000..a76503523 --- /dev/null +++ b/docs/docs/hooks/useLayerVisibility.md @@ -0,0 +1,15 @@ + + +# 👁️ useLayerVisibility.js + +Custom Hook zur dynamischen Steuerung von Layer-Sichtbarkeit basierend auf Redux. + +## Features + +- Entfernt oder zeigt Marker je nach `mapLayersVisibility` +- Nutzt `OverlappingMarkerSpiderfier` (`oms`) +- Normalisiert Layer-Keys (z. B. `"GMA"` → `"gma"`) + +## Intern + +Verwendet `addContextMenuToMarker()` zur Kontextmenüintegration pro Marker. diff --git a/docs/docs/hooks/useLineData.md b/docs/docs/hooks/useLineData.md new file mode 100644 index 000000000..82b42230a --- /dev/null +++ b/docs/docs/hooks/useLineData.md @@ -0,0 +1,19 @@ + + +# 📊 useLineData.js + +Lädt Linienstatusdaten (Farben, Tooltips) aus zwei Webservices in Redux und bereitet sie auf. + +## Rückgabe + +- `lineColors`: Farben pro Linie basierend auf Status +- `tooltipContents`: HTML-Tooltip pro Modul/Station + +## Datenquellen + +- `fetchGisLinesThunk()` (Struktur) +- `fetchGisLinesStatusThunk()` (Statusdaten) + +## Intern + +- Nutzt Map `valueMap`, um Messwert, Schleifenwert, Meldungen zu gruppieren diff --git a/docs/docs/hooks/useMapComponentState.md b/docs/docs/hooks/useMapComponentState.md new file mode 100644 index 000000000..e1a3b092b --- /dev/null +++ b/docs/docs/hooks/useMapComponentState.md @@ -0,0 +1,18 @@ + + +# 🧠 useMapComponentState.js + +Sammelt zentrale UI-Zustände und Redux-Daten für die `MapComponent`. + +## Rückgabe + +- POI-Typen + Ladezustand +- `deviceName` (z. B. erstes Gerät) +- `locationDeviceData` +- `priorityConfig` +- `menuItemAdded`, `setMenuItemAdded` +- Sichtbarkeit des POI-Layers + +## Redux + +- `fetchPoiTypThunk`, `fetchGisStationsStaticDistrictThunk`, `fetchPriorityConfigThunk` diff --git a/docs/docs/hooks/useMarkerLayers.md b/docs/docs/hooks/useMarkerLayers.md new file mode 100644 index 000000000..46c160be0 --- /dev/null +++ b/docs/docs/hooks/useMarkerLayers.md @@ -0,0 +1,16 @@ + + +# 📍 useMarkerLayers.js + +Steuert das Hinzufügen oder Entfernen von Markern in ein Leaflet-Map-Layer. + +## Verwendung + +```js +useMarkerLayers(map, gmaMarkers, "GMA"); +``` + +## Redux + +- Liest `mapLayersVisibility` aus dem Store +- Reagiert automatisch auf Änderungen diff --git a/docs/docs/hooks/usePolylineTooltipLayer.md b/docs/docs/hooks/usePolylineTooltipLayer.md new file mode 100644 index 000000000..aa42d0a7d --- /dev/null +++ b/docs/docs/hooks/usePolylineTooltipLayer.md @@ -0,0 +1,15 @@ + + +# 💬 usePolylineTooltipLayer.js + +Initialisiert und steuert Polylinien + Tooltip-Verhalten für Linienmessdaten. + +## Funktion + +- Nutzt `setupPolylines(...)` zur Marker- und Linienerstellung +- Tooltip-Anzeige bei `mouseover`, dynamisch positioniert +- Entfernt alte Marker und Polylinien automatisch + +## Parameter (gekürzt) + +- `map`, `markers`, `setMarkers`, `setPolylines`, `linePositions`, `tooltipContents`, `lineColors`, etc. diff --git a/docs/docs/nssm-exe-installation.md b/docs/docs/nssm-exe-installation.md new file mode 100644 index 000000000..b03858905 --- /dev/null +++ b/docs/docs/nssm-exe-installation.md @@ -0,0 +1,56 @@ + + +````markdown +- Als Administrator Eingabeaufforderung oder PowerShell öffnen + +- Navigiere zu dem NodeMap Projekt Verzeichnis: + ```shell + C:\Users\Administrator>cd C:\inetpub\wwwroot\talas5\nodeMap + ``` +```` + +- Befehl zum Erstellen eines Dienstes: + Führen Sie den folgenden Befehl aus, um einen neuen Dienst zu erstellen: + + ```shell + nssm.exe install NodeMapService + ``` + + Nachdem Sie diesen Befehl ausgeführt haben, öffnet sich ein NSSM-Dialogfenster. + + **Dienstkonfiguration:** + In dem geöffneten NSSM-Dialogfenster müssen Sie einige Parameter angeben: + + - **Path:** Der Pfad zur ausführbaren Datei, die der Dienst ausführen soll. + ```shell + C:\inetpub\wwwroot\talas5\nodeMap\StartNodeApp.bat + ``` + - **Startup directory:** Das Verzeichnis, in dem die Anwendung gestartet werden soll. + ```shell + C:\inetpub\wwwroot\talas5\nodeMap + ``` + - **Arguments:** kann leer gelassen werden. + +- Dienst starten: + Sobald der Dienst erstellt wurde, können Sie ihn starten. + Das können Sie entweder über die Eingabeaufforderung oder über die Diensteverwaltung von Windows tun. + Um den Dienst über die Eingabeaufforderung zu starten, verwenden Sie den folgenden Befehl: + ```shell + nssm.exe start DienstName + ``` + +--- + +- **Dienst bearbeiten:** + ```shell + nssm.exe edit NodeMapService + ``` +- **Dienst entfernen:** + ```shell + nssm.exe remove NodeMapService confirm + ``` + dauert bis 1 Minute + +``` + +``` diff --git a/docs/docs/pages/_app.md b/docs/docs/pages/_app.md new file mode 100644 index 000000000..6e7523f39 --- /dev/null +++ b/docs/docs/pages/_app.md @@ -0,0 +1,26 @@ + + +# 🌐 _app.js + +Diese Datei stellt die Haupt-Wrap-Komponente der Next.js-App dar. +Sie initialisiert globale Provider wie den Redux Store. + +## Features + +- Importiert globales CSS (`styles/global.css`) +- Bindet Redux `Provider` um alle Seiten-Komponenten +- Ermöglicht Zugriff auf Store in allen Seiten + +## Struktur + +```jsx + + + +``` + +## Pfad + +```bash +/pages/_app.js +``` \ No newline at end of file diff --git a/docs/docs/pages/api/[...path].md b/docs/docs/pages/api/[...path].md new file mode 100644 index 000000000..d76f3bed0 --- /dev/null +++ b/docs/docs/pages/api/[...path].md @@ -0,0 +1,45 @@ + + +# 🌐 [...path].js + +Next.js API-Proxy-Handler mit `http-proxy-middleware`. +Dient als Middleware zur Weiterleitung von API-Requests an das Backend (z. B. Raspberry Pi oder Entwicklungsserver). + +--- + +## 🔧 Funktion + +- Leitet alle Requests von `/api/...` an das definierte `target` weiter +- Entfernt `/api` aus dem URL-Pfad +- Erlaubt Cross-Origin Requests mit `changeOrigin: true` + +--- + +## Ziel-Logik + +```js +const mode = process.env.NEXT_PUBLIC_API_PORT_MODE; +const target = mode === "dev" ? "http://localhost:80" : "http://localhost"; +``` + +--- + +## Beispiel + +- Frontend-Request: `GET /api/GisStationsStaticDistrict` +- Weitergeleitet an: `GET http://localhost:80/GisStationsStaticDistrict` + +--- + +## Besonderheiten + +- Ermöglicht portunabhängige Proxy-Nutzung über `.env` +- Setzt `logLevel: "debug"` zur Diagnose + +--- + +## Pfad + +```bash +/pages/api/[...path].js +``` \ No newline at end of file diff --git a/docs/docs/pages/api/talas_v5_DB/area/readArea.md b/docs/docs/pages/api/talas_v5_DB/area/readArea.md new file mode 100644 index 000000000..3d32a3e64 --- /dev/null +++ b/docs/docs/pages/api/talas_v5_DB/area/readArea.md @@ -0,0 +1,28 @@ + + +# 📥 readArea.js + +Liest Bereichskoordinaten (`location_coordinates`) aus der Datenbank basierend auf `idMaps` (und optional `idLocation`). + +## Methode + +- `GET` + +## URL-Parameter + +| Name | Beschreibung | +|-------------|--------------------------------------| +| `m` | Karten-ID (entspricht `idMaps`) | +| `idLocation` | (optional) ID eines bestimmten Bereichs | + +## Verhalten + +- Joint `location`, `location_coordinates` und `area`-Tabelle +- Gibt strukturierte Daten mit `x`, `y`, `location_name`, `area_name` zurück +- Nutzt MySQL-Pool (`getPool()`) + +## Beispiel + +```http +GET /api/talas_v5_DB/area/readArea?m=3 +``` \ No newline at end of file diff --git a/docs/docs/pages/api/talas_v5_DB/area/updateArea.md b/docs/docs/pages/api/talas_v5_DB/area/updateArea.md new file mode 100644 index 000000000..01d7fd46d --- /dev/null +++ b/docs/docs/pages/api/talas_v5_DB/area/updateArea.md @@ -0,0 +1,32 @@ + + +# 📤 updateArea.js + +Aktualisiert die Koordinaten eines Bereichs (`location_coordinates`) basierend auf `idLocation` und `idMap`. + +## Methode + +- `PUT` + +## Request-Body + +```json +{ + "idLocation": 12, + "idMap": 3, + "x": 53.21421, + "y": 8.43212 +} +``` + +## Verhalten + +- Führt `UPDATE location_coordinates SET x=?, y=? WHERE idLocation=? AND idMaps=?` +- Gibt bei Erfolg `success: true` zurück +- Nutzt MySQL-Pool und `connection.release()` + +## Fehlerbehandlung + +- 400: Fehlende Daten +- 404: Kein Eintrag gefunden +- 500: Interner Fehler \ No newline at end of file diff --git a/docs/docs/pages/api/talas_v5_DB/device/getAllStationsNames.md b/docs/docs/pages/api/talas_v5_DB/device/getAllStationsNames.md new file mode 100644 index 000000000..66fcda44b --- /dev/null +++ b/docs/docs/pages/api/talas_v5_DB/device/getAllStationsNames.md @@ -0,0 +1,29 @@ + + +# 🧾 getAllStationsNames.js + +Liefert eine Zuordnungstabelle aller Geräte-IDs (`idLD`) zu ihren Namen (`name`). + +## Methode + +- `GET` + +## Antwortformat + +```json +{ + "123": "Kue 705", + "124": "Basisstation 1" +} +``` + +## Verhalten + +- Nutzt Tabelle `location_device` +- Gibt Fehler bei leerem Ergebnis (404) oder Datenbankfehler (500) + +## Pfad + +```bash +/pages/api/talas_v5_DB/device/getAllStationsNames.js +``` \ No newline at end of file diff --git a/docs/docs/pages/api/talas_v5_DB/device/getDevices.md b/docs/docs/pages/api/talas_v5_DB/device/getDevices.md new file mode 100644 index 000000000..6c1387f54 --- /dev/null +++ b/docs/docs/pages/api/talas_v5_DB/device/getDevices.md @@ -0,0 +1,34 @@ + + +# 🔌 getDevices.js + +API-Route zum Abrufen aller Geräteinformationen aus der `devices`-Tabelle. + +## Methode + +- `POST` (erwartet JSON-Body mit optionalem `activeSystems`-Array) + +## Verhalten + +- Führt ein einfaches `SELECT * FROM devices` aus +- Nutzt Singleton-MySQL-Pool für Verbindung +- Rückgabe: JSON-Array mit allen Geräteobjekten + +## Beispielantwort + +```json +[ + { + "id": 1, + "name": "Kue705", + "idsystem_typ": 1, + ... + } +] +``` + +## Pfad + +```bash +/pages/api/talas_v5_DB/device/getDevices.js +``` \ No newline at end of file diff --git a/docs/docs/pages/api/talas_v5_DB/gisLines/readGisLines.md b/docs/docs/pages/api/talas_v5_DB/gisLines/readGisLines.md new file mode 100644 index 000000000..fb83233c7 --- /dev/null +++ b/docs/docs/pages/api/talas_v5_DB/gisLines/readGisLines.md @@ -0,0 +1,25 @@ + + +# 🧭 readGisLines.js + +Liefert alle Linien aus der Tabelle `gis_lines`. + +## Methode + +- `GET` + +## Rückgabe + +- JSON-Array mit Objekten aus `gis_lines` +- Leeres Array bei keinen Treffern + +## Besonderheiten + +- Nutzt Singleton-Pool (`getPool()`) +- Immer HTTP 200, auch bei leerem Ergebnis + +## Beispiel + +```http +GET /api/talas_v5_DB/gisLines/readGisLines +``` \ No newline at end of file diff --git a/docs/docs/pages/api/talas_v5_DB/gisLines/updateLineCoordinates.md b/docs/docs/pages/api/talas_v5_DB/gisLines/updateLineCoordinates.md new file mode 100644 index 000000000..a123286b1 --- /dev/null +++ b/docs/docs/pages/api/talas_v5_DB/gisLines/updateLineCoordinates.md @@ -0,0 +1,30 @@ + + +# ✏️ updateLineCoordinates.js + +Aktualisiert die `points`-Spalte einer Linie in der Tabelle `gis_lines`. + +## Methode + +- `POST` + +## Request-Body + +```json +{ + "idLD": 7, + "idModul": 2, + "newCoordinates": [[53.2151, 8.4522], [53.2165, 8.4531]] +} +``` + +## Verhalten + +- Erzeugt aus Koordinaten eine `LINESTRING(...)` +- Nutzt `ST_GeomFromText()` in MySQL +- Transaktion mit Commit/Rollback + +## Fehlerfälle + +- 400: Ungültige oder fehlende Felder +- 500: Datenbankfehler \ No newline at end of file diff --git a/docs/docs/pages/api/talas_v5_DB/locationDevice/getDeviceId.md b/docs/docs/pages/api/talas_v5_DB/locationDevice/getDeviceId.md new file mode 100644 index 000000000..aaaff0a8d --- /dev/null +++ b/docs/docs/pages/api/talas_v5_DB/locationDevice/getDeviceId.md @@ -0,0 +1,26 @@ + + +# 🔍 getDeviceId.js + +Gibt die Geräte-ID (`idLD`) zu einem übergebenen Gerätenamen zurück. + +## Methode + +- `GET` + +## Parameter + +| Name | Beschreibung | +|-------------|----------------------| +| `deviceName` | Der Gerätename (z. B. "Kue705") | + +## Antwort + +```json +{ "idLD": 27 } +``` + +## Fehler + +- 400: Wenn `deviceName` fehlt +- 404: Gerät nicht gefunden \ No newline at end of file diff --git a/docs/docs/pages/api/talas_v5_DB/locationDevice/locationDeviceNameById.md b/docs/docs/pages/api/talas_v5_DB/locationDevice/locationDeviceNameById.md new file mode 100644 index 000000000..fbadfec2d --- /dev/null +++ b/docs/docs/pages/api/talas_v5_DB/locationDevice/locationDeviceNameById.md @@ -0,0 +1,26 @@ + + +# 🏷️ locationDeviceNameById.js + +Gibt den Namen eines Geräts anhand seiner ID zurück. + +## Methode + +- `GET` + +## Parameter + +| Name | Beschreibung | +|--------|------------------------| +| `idLD` | Geräte-ID (z. B. 27) | + +## Antwort + +```json +{ "name": "Kue705" } +``` + +## Fehler + +- 400: Fehlender Parameter +- 404: Gerät mit ID nicht gefunden \ No newline at end of file diff --git a/docs/docs/pages/api/talas_v5_DB/locationDevice/locationDevices.md b/docs/docs/pages/api/talas_v5_DB/locationDevice/locationDevices.md new file mode 100644 index 000000000..3521a8f35 --- /dev/null +++ b/docs/docs/pages/api/talas_v5_DB/locationDevice/locationDevices.md @@ -0,0 +1,27 @@ + + +# 🗂️ locationDevices.js + +Gibt eine vollständige Liste aller Geräte in der Tabelle `location_device` zurück. + +## Methode + +- `GET` + +## Verhalten + +- Führt `SELECT * FROM location_device ORDER BY name` aus +- Gibt vollständige Objekte zurück + +## Beispielantwort + +```json +[ + { + "idLD": 27, + "name": "Kue705", + "description": "...", + ... + } +] +``` \ No newline at end of file diff --git a/docs/docs/pages/api/talas_v5_DB/poiTyp/readPoiTyp.md b/docs/docs/pages/api/talas_v5_DB/poiTyp/readPoiTyp.md new file mode 100644 index 000000000..244aa1827 --- /dev/null +++ b/docs/docs/pages/api/talas_v5_DB/poiTyp/readPoiTyp.md @@ -0,0 +1,36 @@ + + +# 🗂️ readPoiTyp.js + +Liefert alle verfügbaren POI-Typen aus der Tabelle `poityp`. + +## Methode + +- `GET` + +## Rückgabe + +- JSON-Array mit allen Einträgen in `poityp` + +## Besonderheiten + +- Gibt bei leerem Ergebnis `200` mit Warnung zurück +- Verwendet Singleton-Verbindungspool (`getPool()`) + +## Beispiel + +```http +GET /api/talas_v5_DB/poiTyp/readPoiTyp +``` + +## Antwort + +```json +[ + { + "idPoiTyp": 1, + "name": "Messgerät", + "icon": 12 + } +] +``` \ No newline at end of file diff --git a/docs/docs/pages/api/talas_v5_DB/pois/addPoi.md b/docs/docs/pages/api/talas_v5_DB/pois/addPoi.md new file mode 100644 index 000000000..22c075cd4 --- /dev/null +++ b/docs/docs/pages/api/talas_v5_DB/pois/addPoi.md @@ -0,0 +1,25 @@ + + +# ➕ addPoi.js + +Fügt einen neuen POI (Point of Interest) zur Datenbank hinzu. + +## Methode + +- `POST` + +## Request-Body + +```json +{ + "name": "POI A", + "poiTypeId": 1, + "latitude": 53.2, + "longitude": 8.1, + "idLD": 27 +} +``` + +## Besonderheiten + +- Position wird als `POINT(longitude latitude)` gespeichert \ No newline at end of file diff --git a/docs/docs/pages/api/talas_v5_DB/pois/deletePoi.md b/docs/docs/pages/api/talas_v5_DB/pois/deletePoi.md new file mode 100644 index 000000000..f09c740d0 --- /dev/null +++ b/docs/docs/pages/api/talas_v5_DB/pois/deletePoi.md @@ -0,0 +1,20 @@ + + +# ❌ deletePoi.js + +Löscht einen POI anhand seiner ID. + +## Methode + +- `DELETE` + +## Query-Parameter + +| Parameter | Beschreibung | +|-----------|---------------------| +| `id` | ID des POI (`idPoi`) | + +## Antwort + +- 200: Erfolgreich gelöscht +- 404: POI nicht gefunden \ No newline at end of file diff --git a/docs/docs/pages/api/talas_v5_DB/pois/getPoiById.md b/docs/docs/pages/api/talas_v5_DB/pois/getPoiById.md new file mode 100644 index 000000000..ececf24d0 --- /dev/null +++ b/docs/docs/pages/api/talas_v5_DB/pois/getPoiById.md @@ -0,0 +1,21 @@ + + +# 🔎 getPoiById.js + +Gibt die Beschreibung eines POIs zurück. + +## Methode + +- `GET` + +## Query-Parameter + +| Parameter | Beschreibung | +|-----------|--------------| +| `idPoi` | POI-ID | + +## Antwort + +```json +{ "description": "POI A" } +``` \ No newline at end of file diff --git a/docs/docs/pages/api/talas_v5_DB/pois/poi-icons.md b/docs/docs/pages/api/talas_v5_DB/pois/poi-icons.md new file mode 100644 index 000000000..26a63d1b7 --- /dev/null +++ b/docs/docs/pages/api/talas_v5_DB/pois/poi-icons.md @@ -0,0 +1,21 @@ + + +# 🖼️ poi-icons.js + +Gibt eine Liste aller POIs und ihrer zugehörigen Icon-Pfade zurück. + +## Methode + +- `GET` + +## Datenquelle + +- `poi` → `poiTyp` → `poiicons` + +## Antwort + +```json +[ + { "idPoi": 12, "path": "/icons/kue.svg" } +] +``` \ No newline at end of file diff --git a/docs/docs/pages/api/talas_v5_DB/pois/readAllPOIs.md b/docs/docs/pages/api/talas_v5_DB/pois/readAllPOIs.md new file mode 100644 index 000000000..dcd1fda01 --- /dev/null +++ b/docs/docs/pages/api/talas_v5_DB/pois/readAllPOIs.md @@ -0,0 +1,17 @@ + + +# 📋 readAllPOIs.js + +Gibt alle POIs mit Positionen zurück. + +## Methode + +- `GET` + +## Rückgabe + +- JSON-Array mit `idPoi`, `description`, `idPoiTyp`, `idLD`, `position` + +## Besonderheiten + +- Position wird per `ST_AsText(...)` als String geliefert \ No newline at end of file diff --git a/docs/docs/pages/api/talas_v5_DB/pois/updateLocation.md b/docs/docs/pages/api/talas_v5_DB/pois/updateLocation.md new file mode 100644 index 000000000..f995a3aad --- /dev/null +++ b/docs/docs/pages/api/talas_v5_DB/pois/updateLocation.md @@ -0,0 +1,23 @@ + + +# 🧭 updateLocation.js + +Aktualisiert die Position (`POINT`) eines POIs. + +## Methode + +- `POST` + +## Request-Body + +```json +{ + "id": 12, + "latitude": 53.2, + "longitude": 8.1 +} +``` + +## Antwort + +- 200: `{ success: true }` \ No newline at end of file diff --git a/docs/docs/pages/api/talas_v5_DB/pois/updatePoi.md b/docs/docs/pages/api/talas_v5_DB/pois/updatePoi.md new file mode 100644 index 000000000..2e5088db2 --- /dev/null +++ b/docs/docs/pages/api/talas_v5_DB/pois/updatePoi.md @@ -0,0 +1,25 @@ + + +# 📝 updatePoi.js + +Aktualisiert Beschreibung, Typ und Gerät eines POIs. + +## Methode + +- `POST` + +## Request-Body + +```json +{ + "idPoi": 12, + "description": "POI A", + "idPoiTyp": 2, + "idLD": 27 +} +``` + +## Antwort + +- 200: Erfolgreich aktualisiert +- 404: POI nicht gefunden \ No newline at end of file diff --git a/docs/docs/pages/api/talas_v5_DB/priorityConfig.md b/docs/docs/pages/api/talas_v5_DB/priorityConfig.md new file mode 100644 index 000000000..9cc9d196d --- /dev/null +++ b/docs/docs/pages/api/talas_v5_DB/priorityConfig.md @@ -0,0 +1,83 @@ + + +# 📊 API: /api/talas_v5_DB/priorityConfig + +Diese API liefert die Konfigurationsdaten für Prioritäten (z. B. „critical“, „minor“) aus der Tabelle `prio`. +Sie wird u. a. für Meldungsanzeigen, Filter und Leaflet-Marker-Priorisierung verwendet. + +--- + +## 📍 Anwendung in Leaflet – Marker-Priorität bei Überlappung + +Die `level`-Werte dieser Konfiguration steuern die **Darstellungsreihenfolge überlappender Marker** in Leaflet: + +- Marker mit **höherer Priorität** (`level = 1`, z. B. `critical`) werden **oben** dargestellt +- Marker mit **niedriger Priorität** (`level = 100`, `101`) werden **weiter hinten** gezeichnet +- Dadurch bleiben wichtige Meldungen stets sichtbar, selbst bei POI-Überlagerung + +Diese Sortierung wird z. B. bei OverlappingMarkerSpiderfier oder Clustern angewendet. + +--- + +## 🔗 Route + +- **Pfad:** `/api/talas_v5_DB/priorityConfig` +- **Methode:** `GET` +- **Beschreibung:** Gibt alle aktiven Prioritätsstufen inkl. Farbcodes zurück + +--- + +## 🧾 Beispielantwort + +**Test-URL:** [`/api/talas_v5_DB/priorityConfig`](http://10.10.0.70:3000/api/talas_v5_DB/priorityConfig) + +```json +[ + { "idprio": 0, "level": 100, "name": "kein", "color": "#ffffff" }, + { "idprio": 1, "level": 101, "name": "gut", "color": "#99CC00" }, + { "idprio": 5, "level": 1, "name": "critical", "color": "#FF0000" }, + { "idprio": 7, "level": 2, "name": "major", "color": "#FF9900" }, + { "idprio": 9, "level": 3, "name": "minor", "color": "#FFFF00" }, + { "idprio": 10, "level": 4, "name": "system", "color": "#FF00FF" }, + { "idprio": 12, "level": 0, "name": "Stationsausfall", "color": "#FF6600" } +] +``` + +📦 Datenstruktur +Feld Typ Beschreibung +idprio number Eindeutige ID der Priorität +level number Prioritätsstufe (1 = hoch, 100 = niedrig) +name string Bezeichnung (z. B. "minor", "system", "Stationsausfall") +color string HEX-Farbcode (z. B. #FF0000) zur visuellen Darstellung + +⚙️ Datenquelle +Tabelle: prio + +SQL-Abfrage: + +sql + +SELECT idprio, level, name, color FROM prio; + +Backend: verwendet getPool() aus utils/mysqlPool.js + +## 🔗 Verwendet in + +| Datei | Zweck | +| ----------------------------- | ------------------------------------------------------------------- | +| `fetchPriorityConfigThunk.js` | Holt Prioritätsdaten über API und reicht sie an Redux weiter | +| `priorityConfigSlice.js` | Speichert die geladenen Prioritätsdaten im Redux-Store | +| `MapComponent.js` | Dispatcht Thunk zum Laden der Daten beim Start | +| `useMapComponentState.js` | Liest `priorityConfig` aus Redux und gibt es an Marker-Setup weiter | +| `createAndSetDevices.js` | Erzeugt Marker mit `zIndexOffset` basierend auf Priorität | +| `useCreateAndSetDevices.js` | Hook zur Initialisierung von Geräten auf der Karte | +| `useDynamicMarkerLayers.js` | Verwaltet Marker-Layer dynamisch (inkl. Z-Priorität) | + +❌ Fehlerbehandlung +Bei DB- oder Verbindungsfehlern: + +json +Copy +Edit +{ "error": "Fehler bei der Abfrage" } +HTTP-Statuscode: 500 diff --git a/docs/docs/pages/api/talas_v5_DB/station/getAllStationsNames.md b/docs/docs/pages/api/talas_v5_DB/station/getAllStationsNames.md new file mode 100644 index 000000000..ff1a7c698 --- /dev/null +++ b/docs/docs/pages/api/talas_v5_DB/station/getAllStationsNames.md @@ -0,0 +1,29 @@ + + +# 🏷️ getAllStationsNames.js + +Liefert eine Mapping-Tabelle aus `idLD` → `name` aller Einträge in `location_device`. + +## Methode + +- `GET` + +## Rückgabe + +```json +{ + "12": "Hauptstation", + "13": "Unterstation" +} +``` + +## Verhalten + +- Antwort ist ein Key-Value-Objekt +- Nutzt `reduce()` zur Map-Erstellung +- Verwendet MySQL-Singleton-Pool + +## Fehler + +- 404: Wenn keine Daten vorhanden +- 500: Datenbankfehler \ No newline at end of file diff --git a/docs/docs/pages/api/talas_v5_DB/station/getDevices.md b/docs/docs/pages/api/talas_v5_DB/station/getDevices.md new file mode 100644 index 000000000..da765d1d8 --- /dev/null +++ b/docs/docs/pages/api/talas_v5_DB/station/getDevices.md @@ -0,0 +1,40 @@ + + +# 📦 getDevices.js + +Gibt alle Geräte aus der `devices`-Tabelle zurück. + +## Methode + +- `POST` + +## Request-Body + +```json +{ + "activeSystems": [1, 2, 3] +} +``` + +⚠️ Hinweis: Im aktuellen Code wird der Parameter `activeSystems` nicht verwendet! + +## Rückgabe + +- JSON-Array mit Geräteobjekten + +## Beispielantwort + +```json +[ + { + "id": 1, + "name": "CPL V4.0", + "idsystem_typ": 1 + } +] +``` + +## Fehler + +- 404: Keine Ergebnisse +- 500: Datenbankfehler \ No newline at end of file diff --git a/docs/docs/pages/index.md b/docs/docs/pages/index.md new file mode 100644 index 000000000..d7da48530 --- /dev/null +++ b/docs/docs/pages/index.md @@ -0,0 +1,32 @@ + + +# 🏠 index.js (Home-Seite) + +Die Hauptseite der Anwendung. +Bindet dynamisch die Leaflet-Karte (`MapComponent`) und ein Testscripting-Tool (`TestScript`). + +## Features + +- `MapComponent` ohne SSR eingebunden +- `TestScript` prüft per Konsole Logik/Strukturen +- Lädt POI-Daten per `fetchPoiMarkersThunk()` +- Liest URL-Parameter `m` und `u` +- Unterstützt POI-Hinzufügen über `addPoiThunk(...)` + +## Redux-Slices + +- `poiMarkersSlice` +- `addPoiSlice` +- `poiReadFromDbTrigger` + +## Struktur + +```jsx + + +``` + +## Besonderheiten + +- Dynamisches Nachladen der POIs bei Triggeränderung +- Fehleranzeige über `addPoiStatus` + `addPoiError` \ No newline at end of file diff --git a/docs/docs/redux/slices/database/area/updateAreaSlice.md b/docs/docs/redux/slices/database/area/updateAreaSlice.md new file mode 100644 index 000000000..4c46aefd1 --- /dev/null +++ b/docs/docs/redux/slices/database/area/updateAreaSlice.md @@ -0,0 +1,40 @@ + + +# 🧩 updateAreaSlice.js + +Redux-Slice zur Verwaltung des Update-Zustands beim Aktualisieren eines Bereichs (Area). + +--- + +## Zustand + +```js +{ + status: "idle" | "loading" | "succeeded" | "failed", + error: string | null +} +``` + +--- + +## Thunk + +- `updateAreaThunk`: Führt den API-Call zum Speichern von `x`, `y` für `idLocation` & `idMaps` durch. + +--- + +## Aktionen + +- `resetUpdateAreaStatus()`: Setzt Status auf `"idle"` und entfernt Fehler + +--- + +## Verwendung + +In der Komponente `useAreaMarkersLayer.js` beim Ziehen und Speichern von Stationsmarkern. + +--- + +## Fehlerbehandlung + +- Fehlernachricht wird in `error` gespeichert, falls `updateAreaThunk` fehlschlägt. \ No newline at end of file diff --git a/docs/docs/redux/slices/database/locationDevice/locationDevicesSlice.md b/docs/docs/redux/slices/database/locationDevice/locationDevicesSlice.md new file mode 100644 index 000000000..cf58c3de0 --- /dev/null +++ b/docs/docs/redux/slices/database/locationDevice/locationDevicesSlice.md @@ -0,0 +1,45 @@ + + +# 🧩 locationDevicesSlice.js + +Redux-Slice zur Verwaltung von Standortgeräten (Devices) aus der Tabelle `location_device`. + +--- + +## Zustand + +```js +{ + data: [], + status: "idle" | "loading" | "succeeded" | "failed", + error: string | null +} +``` + +--- + +## Thunks + +- `fetchLocationDevicesThunk`: Lädt Geräte aus der API + +--- + +## Aktionen + +- `clearLocationDevices()`: Löscht Geräte-Array und setzt Status zurück + +--- + +## Selektoren + +```js +selectLocationDevices = (state) => state.locationDevices.data; +selectLocationDeviceStatus = (state) => state.locationDevices.status; +``` + +--- + +## Besonderheiten + +- Zustand wird bei `pending`, `fulfilled` und `rejected` aktualisiert +- Fehlernachricht wird in `error` gespeichert \ No newline at end of file diff --git a/docs/docs/redux/slices/database/locationDevicesFromDBSlice.md b/docs/docs/redux/slices/database/locationDevicesFromDBSlice.md new file mode 100644 index 000000000..3eedc3fba --- /dev/null +++ b/docs/docs/redux/slices/database/locationDevicesFromDBSlice.md @@ -0,0 +1,25 @@ + + +# 🧩 locationDevicesFromDBSlice.js + +Redux-Slice für das Laden von Geräten aus der Datenbank-Tabelle `location_device`. + +## Zustand + +```js +{ + devices: [], + status: "idle" | "loading" | "succeeded" | "failed", + error: string | null +} +``` + +## Thunk + +- `fetchLocationDevicesThunk` (async) + +## Selector + +```js +selectLocationDevices = (state) => state.locationDevicesFromDB.devices +``` \ No newline at end of file diff --git a/docs/docs/redux/slices/database/locationDevicesSlice.md b/docs/docs/redux/slices/database/locationDevicesSlice.md new file mode 100644 index 000000000..1030038a8 --- /dev/null +++ b/docs/docs/redux/slices/database/locationDevicesSlice.md @@ -0,0 +1,23 @@ + + +# 🧩 locationDevicesSlice.js + +Zweite Variante des Slices für Geräte (veraltet oder parallel verwendet). + +## Zustand + +```js +{ + data: [], + status: "idle" | "loading" | "succeeded" | "failed", + error: string | null +} +``` + +## Selector + +```js +selectLocationDevices = (state) => state.locationDevices.data +``` + +⚠️ Beachte: Duplikat zu `locationDevicesFromDBSlice.js` \ No newline at end of file diff --git a/docs/docs/redux/slices/database/pois/addPoiOnPolylineSlice.md b/docs/docs/redux/slices/database/pois/addPoiOnPolylineSlice.md new file mode 100644 index 000000000..8d16ada73 --- /dev/null +++ b/docs/docs/redux/slices/database/pois/addPoiOnPolylineSlice.md @@ -0,0 +1,3 @@ +# 🧩 addPoiOnPolylineSlice.js + +Redux-Slice zur Verwaltung von addPoiOnPolyline. \ No newline at end of file diff --git a/docs/docs/redux/slices/database/pois/addPoiSlice.md b/docs/docs/redux/slices/database/pois/addPoiSlice.md new file mode 100644 index 000000000..31f99a654 --- /dev/null +++ b/docs/docs/redux/slices/database/pois/addPoiSlice.md @@ -0,0 +1,3 @@ +# 🧩 addPoiSlice.js + +Redux-Slice zur Verwaltung von addPoi. \ No newline at end of file diff --git a/docs/docs/redux/slices/database/pois/currentPoiSlice.md b/docs/docs/redux/slices/database/pois/currentPoiSlice.md new file mode 100644 index 000000000..0c1b4e7fe --- /dev/null +++ b/docs/docs/redux/slices/database/pois/currentPoiSlice.md @@ -0,0 +1,3 @@ +# 🧩 currentPoiSlice.js + +Redux-Slice zur Verwaltung von currentPoi. \ No newline at end of file diff --git a/docs/docs/redux/slices/database/pois/poiIconsDataSlice.md b/docs/docs/redux/slices/database/pois/poiIconsDataSlice.md new file mode 100644 index 000000000..80f57f38d --- /dev/null +++ b/docs/docs/redux/slices/database/pois/poiIconsDataSlice.md @@ -0,0 +1,3 @@ +# 🧩 poiIconsDataSlice.js + +Redux-Slice zur Verwaltung von POIIconsData. \ No newline at end of file diff --git a/docs/docs/redux/slices/database/pois/poiLayerVisibleSlice.md b/docs/docs/redux/slices/database/pois/poiLayerVisibleSlice.md new file mode 100644 index 000000000..ef4aa5c8e --- /dev/null +++ b/docs/docs/redux/slices/database/pois/poiLayerVisibleSlice.md @@ -0,0 +1,3 @@ +# 🧩 poiLayerVisibleSlice.js + +Redux-Slice zur Verwaltung von POILayerVisible. \ No newline at end of file diff --git a/docs/docs/redux/slices/database/pois/poiMarkersSlice.md b/docs/docs/redux/slices/database/pois/poiMarkersSlice.md new file mode 100644 index 000000000..94563967d --- /dev/null +++ b/docs/docs/redux/slices/database/pois/poiMarkersSlice.md @@ -0,0 +1,3 @@ +# 🧩 poiMarkersSlice.js + +Redux-Slice zur Verwaltung von POIMarkers. \ No newline at end of file diff --git a/docs/docs/redux/slices/database/pois/poiReadFromDbTriggerSlice.md b/docs/docs/redux/slices/database/pois/poiReadFromDbTriggerSlice.md new file mode 100644 index 000000000..fd3270b7f --- /dev/null +++ b/docs/docs/redux/slices/database/pois/poiReadFromDbTriggerSlice.md @@ -0,0 +1,3 @@ +# 🧩 poiReadFromDbTriggerSlice.js + +Redux-Slice zur Verwaltung von POIReadFromDbTrigger. \ No newline at end of file diff --git a/docs/docs/redux/slices/database/pois/poiTypSlice.md b/docs/docs/redux/slices/database/pois/poiTypSlice.md new file mode 100644 index 000000000..edf0ff581 --- /dev/null +++ b/docs/docs/redux/slices/database/pois/poiTypSlice.md @@ -0,0 +1,3 @@ +# 🧩 poiTypSlice.js + +Redux-Slice zur Verwaltung von POITyp. \ No newline at end of file diff --git a/docs/docs/redux/slices/database/pois/poiTypesSlice.md b/docs/docs/redux/slices/database/pois/poiTypesSlice.md new file mode 100644 index 000000000..24366914c --- /dev/null +++ b/docs/docs/redux/slices/database/pois/poiTypesSlice.md @@ -0,0 +1,3 @@ +# 🧩 poiTypesSlice.js + +Redux-Slice zur Verwaltung von POITypes. \ No newline at end of file diff --git a/docs/docs/redux/slices/database/pois/readPoiMarkersStoreSlice.md b/docs/docs/redux/slices/database/pois/readPoiMarkersStoreSlice.md new file mode 100644 index 000000000..5cd8b478c --- /dev/null +++ b/docs/docs/redux/slices/database/pois/readPoiMarkersStoreSlice.md @@ -0,0 +1,3 @@ +# 🧩 readPoiMarkersStoreSlice.js + +Redux-Slice zur Verwaltung von readPoiMarkersStore. \ No newline at end of file diff --git a/docs/docs/redux/slices/database/pois/selectedPoiSlice.md b/docs/docs/redux/slices/database/pois/selectedPoiSlice.md new file mode 100644 index 000000000..20bceb7eb --- /dev/null +++ b/docs/docs/redux/slices/database/pois/selectedPoiSlice.md @@ -0,0 +1,3 @@ +# 🧩 selectedPoiSlice.js + +Redux-Slice zur Verwaltung von selectedPoi. \ No newline at end of file diff --git a/docs/docs/redux/slices/database/polylines/gisLinesSlice.md b/docs/docs/redux/slices/database/polylines/gisLinesSlice.md new file mode 100644 index 000000000..580f15c4f --- /dev/null +++ b/docs/docs/redux/slices/database/polylines/gisLinesSlice.md @@ -0,0 +1,25 @@ + + +# 🧩 gisLinesSlice.js + +Verwaltet alle Linienobjekte, die aus der Datenbank (`gis_lines`) gelesen wurden. + +## Zustand + +```js +{ + data: [], + status: "idle" | "loading" | "succeeded" | "failed", + error: string | null +} +``` + +## Thunk + +- `fetchGisLinesThunk()` + +## Selector + +```js +selectGisLines = (state) => state.gisLines.data +``` \ No newline at end of file diff --git a/docs/docs/redux/slices/database/polylines/polylineContextMenuSlice.md b/docs/docs/redux/slices/database/polylines/polylineContextMenuSlice.md new file mode 100644 index 000000000..9258beada --- /dev/null +++ b/docs/docs/redux/slices/database/polylines/polylineContextMenuSlice.md @@ -0,0 +1,25 @@ + + +# 🧩 polylineContextMenuSlice.js + +Verwaltet den Zustand des Kontextmenüs bei Polylinien (z. B. Stützpunkt hinzufügen/entfernen). + +## Zustand + +```js +{ + isOpen: false, + position: { lat, lng } | null, + forceClose: false, + timerStart: number | null, + countdown: number, + countdownActive: boolean +} +``` + +## Aktionen + +- `openPolylineContextMenu(payload)` +- `closePolylineContextMenu()` +- `updateCountdown()` +- `forceCloseContextMenu()` \ No newline at end of file diff --git a/docs/docs/redux/slices/database/polylines/polylineEventsDisabledSlice.md b/docs/docs/redux/slices/database/polylines/polylineEventsDisabledSlice.md new file mode 100644 index 000000000..92fc70c85 --- /dev/null +++ b/docs/docs/redux/slices/database/polylines/polylineEventsDisabledSlice.md @@ -0,0 +1,16 @@ + + +# 🧩 polylineEventsDisabledSlice.js + +Steuert, ob Interaktionen mit Polylinien (z. B. Ziehen, Klicks) temporär deaktiviert sind. + +## Zustand + +```js +{ disabled: boolean } +``` + +## Aktionen + +- `setDisabled(boolean)` +- `toggleDisabled()` \ No newline at end of file diff --git a/docs/docs/redux/slices/database/polylines/polylineLayerVisibleSlice.md b/docs/docs/redux/slices/database/polylines/polylineLayerVisibleSlice.md new file mode 100644 index 000000000..6b440aad9 --- /dev/null +++ b/docs/docs/redux/slices/database/polylines/polylineLayerVisibleSlice.md @@ -0,0 +1,21 @@ + + +# 🧩 polylineLayerVisibleSlice.js + +Steuert die Sichtbarkeit des Polylinienlayers auf der Karte. + +## Zustand + +```js +{ visible: boolean } +``` + +## Aktion + +- `setPolylineVisible(boolean)` + +## Selector + +```js +selectPolylineVisible = (state) => state.polylineLayerVisible.visible +``` \ No newline at end of file diff --git a/docs/docs/redux/slices/database/polylines/updatePolylineCoordinatesSlice.md b/docs/docs/redux/slices/database/polylines/updatePolylineCoordinatesSlice.md new file mode 100644 index 000000000..eae91b396 --- /dev/null +++ b/docs/docs/redux/slices/database/polylines/updatePolylineCoordinatesSlice.md @@ -0,0 +1,22 @@ + + +# 🧩 updatePolylineCoordinatesSlice.js + +Redux-Slice zur Überwachung des Lade-/Fehlerstatus bei der Aktualisierung von Linienkoordinaten. + +## Zustand + +```js +{ + status: "idle" | "loading" | "succeeded" | "failed", + error: string | null +} +``` + +## Thunk + +- `updatePolylineCoordinatesThunk()` + +## Aktion + +- `resetUpdateStatus()` \ No newline at end of file diff --git a/docs/docs/redux/slices/database/priorityConfigSlice.md b/docs/docs/redux/slices/database/priorityConfigSlice.md new file mode 100644 index 000000000..e3a040089 --- /dev/null +++ b/docs/docs/redux/slices/database/priorityConfigSlice.md @@ -0,0 +1,24 @@ + + +# 🧩 priorityConfigSlice.js + +Lädt die Prioritätskonfiguration für Marker (z. B. zur farblichen Darstellung). + +## Zustand + +```js +{ + data: [], + status: "idle" | "succeeded" +} +``` + +## Thunk + +- `fetchPriorityConfigThunk` + +## Selector + +```js +selectPriorityConfig = (state) => state.priorityConfig.data +``` \ No newline at end of file diff --git a/docs/docs/redux/slices/lineVisibilitySlice.md b/docs/docs/redux/slices/lineVisibilitySlice.md new file mode 100644 index 000000000..41d22c21a --- /dev/null +++ b/docs/docs/redux/slices/lineVisibilitySlice.md @@ -0,0 +1,24 @@ + + +# 📶 lineVisibilitySlice.js + +Redux-Slice zur Steuerung der Sichtbarkeit aktiver Linien auf der Karte. + +## Zustand + +```js +{ + activeLines: { + [idLD]: true | false + } +} +``` + +## Aktionen + +- `updateLineStatus({ idLD, active })`: Einzelne Linie sichtbar/unsichtbar +- `setActiveLines({ ... })`: Ganze Objektzuweisung + +## Anwendung + +Wird verwendet z. B. in `useLineData.js`, `MapComponent.js` \ No newline at end of file diff --git a/docs/docs/redux/slices/mapLayersSlice.md b/docs/docs/redux/slices/mapLayersSlice.md new file mode 100644 index 000000000..f2847e83d --- /dev/null +++ b/docs/docs/redux/slices/mapLayersSlice.md @@ -0,0 +1,27 @@ + + +# 🗺️ mapLayersSlice.js + +Verwaltet die Sichtbarkeit einzelner Layergruppen (z. B. GMA, ECI, Siemens). + +## Zustand + +```js +{ + TALAS: true, + ECI: true, + ULAF: true, + ... +} +``` + +## Aktionen + +- `toggleLayer(layer)`: Sichtbarkeit toggeln +- `setLayerVisibility({ layer, visibility })`: Sichtbarkeit explizit setzen + +## Selector + +```js +selectMapLayersState = (state) => state.mapLayers +``` \ No newline at end of file diff --git a/docs/docs/redux/slices/selectedAreaSlice.md b/docs/docs/redux/slices/selectedAreaSlice.md new file mode 100644 index 000000000..d1eb2e999 --- /dev/null +++ b/docs/docs/redux/slices/selectedAreaSlice.md @@ -0,0 +1,16 @@ + + +# 📍 selectedAreaSlice.js + +Steuert die aktuell selektierte Station/Bereich in der Karte. + +## Zustand + +```js +{ area: null | object } +``` + +## Aktionen + +- `setSelectedArea(area)` +- `clearSelectedArea()` \ No newline at end of file diff --git a/docs/docs/redux/slices/selectedDeviceSlice.md b/docs/docs/redux/slices/selectedDeviceSlice.md new file mode 100644 index 000000000..ef20a935a --- /dev/null +++ b/docs/docs/redux/slices/selectedDeviceSlice.md @@ -0,0 +1,16 @@ + + +# 🖥️ selectedDeviceSlice.js + +Speichert das aktuell ausgewählte Gerät aus der Karte. + +## Zustand + +```js +null | { ...Gerät } +``` + +## Aktionen + +- `setSelectedDevice(device)` +- `clearSelectedDevice()` \ No newline at end of file diff --git a/docs/docs/redux/slices/urlParameterSlice.md b/docs/docs/redux/slices/urlParameterSlice.md new file mode 100644 index 000000000..1a2c68801 --- /dev/null +++ b/docs/docs/redux/slices/urlParameterSlice.md @@ -0,0 +1,20 @@ + + +# 🔗 urlParameterSlice.js + +Verwaltet die URL-Parameter `m` (mapId) und `u` (userId). + +## Zustand + +```js +{ + mapId: number | null, + userId: number | null +} +``` + +## Aktionen + +- `setMapId(id)` +- `setUserId(id)` +- `setFromURL({ m, u })` \ No newline at end of file diff --git a/docs/docs/redux/slices/webService/gisLinesStatusSlice.md b/docs/docs/redux/slices/webService/gisLinesStatusSlice.md new file mode 100644 index 000000000..b6a0ba93d --- /dev/null +++ b/docs/docs/redux/slices/webService/gisLinesStatusSlice.md @@ -0,0 +1,25 @@ + + +# 🧩 gisLinesStatusSlice.js + +Lädt und speichert Statusdaten von Linien (z. B. Spannungswerte, Betriebszustand) aus dem Webservice. + +## Zustand + +```js +{ + data: [], + status: "idle" | "loading" | "succeeded" | "failed", + error: string | null +} +``` + +## Thunk + +- `fetchGisLinesStatusThunk()` + +## Selector + +```js +selectGisLinesStatusFromWebservice = (state) => state.gisLinesStatusFromWebservice +``` \ No newline at end of file diff --git a/docs/docs/redux/slices/webService/gisStationsMeasurementsSlice.md b/docs/docs/redux/slices/webService/gisStationsMeasurementsSlice.md new file mode 100644 index 000000000..3f1308bf5 --- /dev/null +++ b/docs/docs/redux/slices/webService/gisStationsMeasurementsSlice.md @@ -0,0 +1,25 @@ + + +# 🧩 gisStationsMeasurementsSlice.js + +Verwaltet Messwerte einzelner Stationen (z. B. Schleifenwiderstand, Isolation) aus Webservice-Antworten. + +## Zustand + +```js +{ + data: [], + status: "idle" | "loading" | "succeeded" | "failed", + error: string | null +} +``` + +## Thunk + +- `fetchGisStationsMeasurementsThunk()` + +## Selector + +```js +selectGisStationsMeasurements = (state) => state.gisStationsMeasurements.data +``` \ No newline at end of file diff --git a/docs/docs/redux/slices/webService/gisStationsStaticDistrictSlice.md b/docs/docs/redux/slices/webService/gisStationsStaticDistrictSlice.md new file mode 100644 index 000000000..faf67e4b2 --- /dev/null +++ b/docs/docs/redux/slices/webService/gisStationsStaticDistrictSlice.md @@ -0,0 +1,25 @@ + + +# 🧩 gisStationsStaticDistrictSlice.js + +Lädt und speichert statische Standortdaten (z. B. Koordinaten) der Stationen im aktuellen Bezirk. + +## Zustand + +```js +{ + data: { Points: [] }, + status: "idle" | "loading" | "succeeded" | "failed", + error: string | null +} +``` + +## Thunk + +- `fetchGisStationsStaticDistrictThunk()` + +## Selector + +```js +selectGisStationsStaticDistrict = (state) => state.gisStationsStaticDistrict.data +``` \ No newline at end of file diff --git a/docs/docs/redux/slices/webService/gisStationsStatusDistrictSlice.md b/docs/docs/redux/slices/webService/gisStationsStatusDistrictSlice.md new file mode 100644 index 000000000..e538fc9c2 --- /dev/null +++ b/docs/docs/redux/slices/webService/gisStationsStatusDistrictSlice.md @@ -0,0 +1,25 @@ + + +# 🧩 gisStationsStatusDistrictSlice.js + +Verwaltet den Status aller Stationen im aktuellen Bezirk aus Webservice-Daten. + +## Zustand + +```js +{ + data: [], + status: "idle" | "loading" | "succeeded" | "failed", + error: string | null +} +``` + +## Thunk + +- `fetchGisStationsStatusDistrictThunk()` + +## Selector + +```js +selectGisStationsStatusDistrict = (state) => state.gisStationsStatusDistrict.data +``` \ No newline at end of file diff --git a/docs/docs/redux/slices/webService/gisSystemStaticSlice.md b/docs/docs/redux/slices/webService/gisSystemStaticSlice.md new file mode 100644 index 000000000..ecf688b02 --- /dev/null +++ b/docs/docs/redux/slices/webService/gisSystemStaticSlice.md @@ -0,0 +1,25 @@ + + +# 🧩 gisSystemStaticSlice.js + +Verwaltet statische Gerätedaten aller Systeme, die vom Webservice zurückgegeben werden. + +## Zustand + +```js +{ + data: [], + status: "idle" | "loading" | "succeeded" | "failed", + error: string | null +} +``` + +## Thunk + +- `fetchGisSystemStaticThunk()` + +## Selector + +```js +selectGisSystemStatic = (state) => state.gisSystemStatic.data +``` \ No newline at end of file diff --git a/docs/docs/redux/slices/webService/userRightsSlice.md b/docs/docs/redux/slices/webService/userRightsSlice.md new file mode 100644 index 000000000..a40453d15 --- /dev/null +++ b/docs/docs/redux/slices/webService/userRightsSlice.md @@ -0,0 +1,26 @@ + + +# 🧩 userRightsSlice.js + +Verwaltet die Benutzerrechte, die vom Webservice für den angemeldeten Nutzer bereitgestellt werden. + +## Zustand + +```js +{ + rights: [], + status: "idle" | "loading" | "succeeded" | "failed", + error: string | null +} +``` + +## Thunk + +- `fetchUserRightsThunk()` + +## Selector + +```js +selectGisUserRightsFromWebservice = (state) => state.gisUserRightsFromWebservice.rights +selectGisUserRightsStatus = (state) => state.gisUserRightsFromWebservice.status +``` \ No newline at end of file diff --git a/docs/docs/redux/slices/zoomTriggerSlice.md b/docs/docs/redux/slices/zoomTriggerSlice.md new file mode 100644 index 000000000..e5aeb3226 --- /dev/null +++ b/docs/docs/redux/slices/zoomTriggerSlice.md @@ -0,0 +1,16 @@ + + +# 🔍 zoomTriggerSlice.js + +Ein Redux-Trigger, der die Karte zur Neuberechnung des Zoom-Zustands veranlasst. + +## Zustand + +```js +{ trigger: number } +``` + +## Aktionen + +- `incrementZoomTrigger()`: Erhöht den Trigger +- `resetZoomTrigger()`: Setzt auf 0 zurück \ No newline at end of file diff --git a/docs/docs/redux/store.md b/docs/docs/redux/store.md new file mode 100644 index 000000000..95664ef63 --- /dev/null +++ b/docs/docs/redux/store.md @@ -0,0 +1,64 @@ + + +# 🧠 Redux Store (store.js) + +Zentrale Konfiguration des globalen Redux-Stores für die Anwendung. +Er verwaltet Zustand für Daten aus Webservices, der Datenbank und UI-Status. + +--- + +## 🔌 Verwendung + +```js +import { Provider } from "react-redux"; +import { store } from "../redux/store"; + + + + +``` + +--- + +## 🔁 Struktur + +Der Store besteht aus drei Bereichen: + +### 1. `database` + +- `poiMarkers`, `addPoi`, `poiLayerVisible` +- `gisLinesFromDatabase`, `polylineLayerVisible` +- `readPoiMarkersStore`, `priorityConfig`, `locationDevicesFromDB` + +### 2. `webservice` + +- `gisStationsStaticDistrict`, `gisStationsStatusDistrict`, `gisSystemStatic` +- `gisStationsMeasurements`, `gisLinesStatusFromWebservice`, `userRights` + +### 3. `ui / interaktiv` + +- `mapLayers`, `selectedDevice`, `selectedPoi`, `selectedArea` +- `lineVisibility`, `zoomTrigger`, `urlParameter`, `polylineContextMenu` +- `polylineEventsDisabled`, `addPoiOnPolyline` + +--- + +## ⚙️ Einrichtung + +```js +export const store = configureStore({ + reducer: { + selectedDevice: selectedDeviceReducer, + poiMarkers: poiMarkersReducer, + ... + } +}); +``` + +--- + +## 📁 Pfad + +```bash +/redux/store.js +``` \ No newline at end of file diff --git a/docs/docs/redux/thunks/database/area/updateAreaThunk.md b/docs/docs/redux/thunks/database/area/updateAreaThunk.md new file mode 100644 index 000000000..24d8dcec5 --- /dev/null +++ b/docs/docs/redux/thunks/database/area/updateAreaThunk.md @@ -0,0 +1,25 @@ + + +# ✏️ updateAreaThunk.js + +Async-Thunk zum Aktualisieren der Koordinaten eines Bereichs (Area). + +## Verwendung + +```js +dispatch(updateAreaThunk({ + idLocation: 5, + idMap: 2, + x: 53.215, + y: 8.45 +})); +``` + +## Quelle + +- Ruft `updateAreaService(payload)` auf + +## Verhalten + +- Gibt `await`-Ergebnis direkt zurück +- Fehlerbehandlung wird vom aufrufenden Slice übernommen \ No newline at end of file diff --git a/docs/docs/redux/thunks/database/fetchLocationDevicesThunk.md b/docs/docs/redux/thunks/database/fetchLocationDevicesThunk.md new file mode 100644 index 000000000..2d62cde1b --- /dev/null +++ b/docs/docs/redux/thunks/database/fetchLocationDevicesThunk.md @@ -0,0 +1,15 @@ + + +# 🚚 fetchLocationDevicesThunk.js + +Async-Thunk zum Laden aller Geräte aus der Tabelle `location_device`. + +## Verwendung + +```js +dispatch(fetchLocationDevicesThunk()); +``` + +## Quelle + +- Ruft `fetchLocationDevicesService()` auf \ No newline at end of file diff --git a/docs/docs/redux/thunks/database/fetchPriorityConfigThunk.md b/docs/docs/redux/thunks/database/fetchPriorityConfigThunk.md new file mode 100644 index 000000000..53895c2f7 --- /dev/null +++ b/docs/docs/redux/thunks/database/fetchPriorityConfigThunk.md @@ -0,0 +1,15 @@ + + +# 🎯 fetchPriorityConfigThunk.js + +Async-Thunk zum Abrufen der Prioritätskonfiguration für Marker. + +## Verwendung + +```js +dispatch(fetchPriorityConfigThunk()); +``` + +## Quelle + +- Ruft `fetchPriorityConfigService()` auf \ No newline at end of file diff --git a/docs/docs/redux/thunks/database/getDeviceIdByNameThunk.md b/docs/docs/redux/thunks/database/getDeviceIdByNameThunk.md new file mode 100644 index 000000000..4e0848f27 --- /dev/null +++ b/docs/docs/redux/thunks/database/getDeviceIdByNameThunk.md @@ -0,0 +1,16 @@ + + +# 🆔 getDeviceIdByNameThunk.js + +Async-Thunk zur Ermittlung der ID eines Geräts anhand des Gerätenamens. + +## Verwendung + +```js +dispatch(getDeviceIdByNameThunk("Kue705")); +``` + +## Verhalten + +- Ruft `getDeviceIdByNameService(deviceName)` auf +- Fehler werden mit `rejectWithValue(error.message)` behandelt \ No newline at end of file diff --git a/docs/docs/redux/thunks/database/locationDevice/fetchLocationDevicesThunk (1).md b/docs/docs/redux/thunks/database/locationDevice/fetchLocationDevicesThunk (1).md new file mode 100644 index 000000000..66ee57295 --- /dev/null +++ b/docs/docs/redux/thunks/database/locationDevice/fetchLocationDevicesThunk (1).md @@ -0,0 +1,37 @@ + + +# 🚚 fetchLocationDevicesThunk.js + +Redux-AsyncThunk zum Abrufen aller Einträge aus der Tabelle `location_device`. + +--- + +## 🔄 Zweck + +Dieser Thunk ruft die Servicefunktion `fetchLocationDevicesService()` auf und liefert deren Ergebnis an den Redux-Slice `locationDevicesSlice`. + +--- + +## 🧠 Intern + +```ts +createAsyncThunk("locationDevices/fetchAll", async () => { + return await fetchLocationDevicesService(); +}); +``` + +--- + +## ✅ Verwendung + +```ts +dispatch(fetchLocationDevicesThunk()); +``` + +--- + +## 📁 Pfad + +``` +/redux/thunks/database/locationDevice/fetchLocationDevicesThunk.js +``` \ No newline at end of file diff --git a/docs/docs/redux/thunks/database/pois/addPoiThunk.md b/docs/docs/redux/thunks/database/pois/addPoiThunk.md new file mode 100644 index 000000000..c53771de6 --- /dev/null +++ b/docs/docs/redux/thunks/database/pois/addPoiThunk.md @@ -0,0 +1,21 @@ + + +# ➕ addPoiThunk.js + +Async-Thunk zur Erstellung eines neuen POIs. + +## Verwendung + +```js +dispatch(addPoiThunk({ + name: "Messstelle 1", + poiTypeId: 2, + latitude: 53.21, + longitude: 8.43, + idLD: 12 +})); +``` + +## Intern + +Ruft `addPoiService(formData)` auf und gibt das Ergebnis zurück. \ No newline at end of file diff --git a/docs/docs/redux/thunks/database/pois/deletePoiThunk.md b/docs/docs/redux/thunks/database/pois/deletePoiThunk.md new file mode 100644 index 000000000..2e6ea1625 --- /dev/null +++ b/docs/docs/redux/thunks/database/pois/deletePoiThunk.md @@ -0,0 +1,16 @@ + + +# ❌ deletePoiThunk.js + +Async-Thunk zum Löschen eines POIs anhand seiner ID. + +## Verwendung + +```js +dispatch(deletePoiThunk(15)); +``` + +## Verhalten + +- Ruft `deletePoiService(id)` auf +- Gibt die ID im Erfolgsfall zurück \ No newline at end of file diff --git a/docs/docs/redux/thunks/database/pois/fetchPoiIconsDataThunk.md b/docs/docs/redux/thunks/database/pois/fetchPoiIconsDataThunk.md new file mode 100644 index 000000000..76931f618 --- /dev/null +++ b/docs/docs/redux/thunks/database/pois/fetchPoiIconsDataThunk.md @@ -0,0 +1,15 @@ + + +# 🖼️ fetchPoiIconsDataThunk.js + +Lädt alle verfügbaren POI-Icons aus dem Backend. + +## Verwendung + +```js +dispatch(fetchPoiIconsDataThunk()); +``` + +## Intern + +- Ruft `fetchPoiIconsDataService()` auf \ No newline at end of file diff --git a/docs/docs/redux/thunks/database/pois/fetchPoiMarkersThunk.md b/docs/docs/redux/thunks/database/pois/fetchPoiMarkersThunk.md new file mode 100644 index 000000000..e1cbc961d --- /dev/null +++ b/docs/docs/redux/thunks/database/pois/fetchPoiMarkersThunk.md @@ -0,0 +1,15 @@ + + +# 📍 fetchPoiMarkersThunk.js + +Lädt alle POI-Marker (Positionsdaten) aus dem Backend. + +## Verwendung + +```js +dispatch(fetchPoiMarkersThunk()); +``` + +## Intern + +- Ruft `fetchPoiMarkersService()` auf \ No newline at end of file diff --git a/docs/docs/redux/thunks/database/pois/fetchPoiTypThunk.md b/docs/docs/redux/thunks/database/pois/fetchPoiTypThunk.md new file mode 100644 index 000000000..2aae375b4 --- /dev/null +++ b/docs/docs/redux/thunks/database/pois/fetchPoiTypThunk.md @@ -0,0 +1,15 @@ + + +# 🗂️ fetchPoiTypThunk.js + +Lädt alle verfügbaren POI-Typen aus der Datenbank. + +## Verwendung + +```js +dispatch(fetchPoiTypThunk()); +``` + +## Intern + +- Ruft `fetchPoiTypService()` auf \ No newline at end of file diff --git a/docs/docs/redux/thunks/database/pois/updatePoiThunk.md b/docs/docs/redux/thunks/database/pois/updatePoiThunk.md new file mode 100644 index 000000000..3a4dec185 --- /dev/null +++ b/docs/docs/redux/thunks/database/pois/updatePoiThunk.md @@ -0,0 +1,20 @@ + + +# 📝 updatePoiThunk.js + +Aktualisiert einen bestehenden POI mit neuen Daten. + +## Verwendung + +```js +dispatch(updatePoiThunk({ + idPoi: 15, + description: "Neue Beschreibung", + idPoiTyp: 3, + idLD: 8 +})); +``` + +## Intern + +- Ruft `updatePoiService(poi)` auf \ No newline at end of file diff --git a/docs/docs/redux/thunks/database/polylines/fetchGisLinesThunk.md b/docs/docs/redux/thunks/database/polylines/fetchGisLinesThunk.md new file mode 100644 index 000000000..9bfbdec01 --- /dev/null +++ b/docs/docs/redux/thunks/database/polylines/fetchGisLinesThunk.md @@ -0,0 +1,16 @@ + + +# 📡 fetchGisLinesThunk.js + +Async-Thunk zum Laden aller Linien aus der Datenbanktabelle `gis_lines`. + +## Verwendung + +```js +dispatch(fetchGisLinesThunk()); +``` + +## Intern + +- Ruft `fetchGisLinesService()` auf +- Liefert alle Linien mit Koordinaten zur Anzeige auf der Karte \ No newline at end of file diff --git a/docs/docs/redux/thunks/database/polylines/updatePolylineCoordinatesThunk.md b/docs/docs/redux/thunks/database/polylines/updatePolylineCoordinatesThunk.md new file mode 100644 index 000000000..19c68ce6f --- /dev/null +++ b/docs/docs/redux/thunks/database/polylines/updatePolylineCoordinatesThunk.md @@ -0,0 +1,20 @@ + + +# 🧭 updatePolylineCoordinatesThunk.js + +Async-Thunk zur Aktualisierung von Linienkoordinaten in der Datenbank. + +## Verwendung + +```js +dispatch(updatePolylineCoordinatesThunk({ + idLD: 7, + idModul: 2, + newCoordinates: [[53.2, 8.4], [53.21, 8.45]] +})); +``` + +## Intern + +- Ruft `updatePolylineCoordinatesService(requestData)` auf +- Wandelt Koordinaten in MySQL LINESTRING um \ No newline at end of file diff --git a/docs/docs/redux/thunks/webservice/fetchGisLinesStatusThunk.md b/docs/docs/redux/thunks/webservice/fetchGisLinesStatusThunk.md new file mode 100644 index 000000000..82e04ab66 --- /dev/null +++ b/docs/docs/redux/thunks/webservice/fetchGisLinesStatusThunk.md @@ -0,0 +1,16 @@ + + +# 📡 fetchGisLinesStatusThunk.js + +Async-Thunk zum Laden des Status aller Linien aus dem Webservice. + +## Verwendung + +```js +dispatch(fetchGisLinesStatusThunk()); +``` + +## Intern + +- Ruft `fetchGisLinesStatusService()` auf +- Fehlerbehandlung per `rejectWithValue(error.message)` \ No newline at end of file diff --git a/docs/docs/redux/thunks/webservice/fetchGisStationsMeasurementsThunk.md b/docs/docs/redux/thunks/webservice/fetchGisStationsMeasurementsThunk.md new file mode 100644 index 000000000..94cfe884f --- /dev/null +++ b/docs/docs/redux/thunks/webservice/fetchGisStationsMeasurementsThunk.md @@ -0,0 +1,15 @@ + + +# 📈 fetchGisStationsMeasurementsThunk.js + +Lädt Messwerte aller Stationen (z. B. Schleifenwiderstand, Isolation). + +## Verwendung + +```js +dispatch(fetchGisStationsMeasurementsThunk()); +``` + +## Intern + +- Ruft `fetchGisStationsMeasurementsService()` auf \ No newline at end of file diff --git a/docs/docs/redux/thunks/webservice/fetchGisStationsStaticDistrictThunk.md b/docs/docs/redux/thunks/webservice/fetchGisStationsStaticDistrictThunk.md new file mode 100644 index 000000000..d69b6dfb4 --- /dev/null +++ b/docs/docs/redux/thunks/webservice/fetchGisStationsStaticDistrictThunk.md @@ -0,0 +1,15 @@ + + +# 🧭 fetchGisStationsStaticDistrictThunk.js + +Lädt statische Standortdaten (z. B. Koordinaten) für den aktuellen Bezirk. + +## Verwendung + +```js +dispatch(fetchGisStationsStaticDistrictThunk()); +``` + +## Intern + +- Ruft `fetchGisStationsStaticDistrictService()` auf \ No newline at end of file diff --git a/docs/docs/redux/thunks/webservice/fetchGisStationsStatusDistrictThunk.md b/docs/docs/redux/thunks/webservice/fetchGisStationsStatusDistrictThunk.md new file mode 100644 index 000000000..587db68b1 --- /dev/null +++ b/docs/docs/redux/thunks/webservice/fetchGisStationsStatusDistrictThunk.md @@ -0,0 +1,15 @@ + + +# 🚦 fetchGisStationsStatusDistrictThunk.js + +Lädt Statusdaten (aktiv/inaktiv) aller Stationen im Bezirk. + +## Verwendung + +```js +dispatch(fetchGisStationsStatusDistrictThunk()); +``` + +## Intern + +- Ruft `fetchGisStationsStatusDistrictService()` auf \ No newline at end of file diff --git a/docs/docs/redux/thunks/webservice/fetchGisSystemStaticThunk.md b/docs/docs/redux/thunks/webservice/fetchGisSystemStaticThunk.md new file mode 100644 index 000000000..9211be3d9 --- /dev/null +++ b/docs/docs/redux/thunks/webservice/fetchGisSystemStaticThunk.md @@ -0,0 +1,15 @@ + + +# 🧱 fetchGisSystemStaticThunk.js + +Lädt Geräte-/Systemdaten für alle Module aus dem Webservice. + +## Verwendung + +```js +dispatch(fetchGisSystemStaticThunk()); +``` + +## Intern + +- Ruft `fetchGisSystemStaticService()` auf \ No newline at end of file diff --git a/docs/docs/redux/thunks/webservice/fetchUserRightsThunk.md b/docs/docs/redux/thunks/webservice/fetchUserRightsThunk.md new file mode 100644 index 000000000..fe1d96508 --- /dev/null +++ b/docs/docs/redux/thunks/webservice/fetchUserRightsThunk.md @@ -0,0 +1,16 @@ + + +# 🔐 fetchUserRightsThunk.js + +Lädt Rechte des angemeldeten Nutzers vom Webservice. + +## Verwendung + +```js +dispatch(fetchUserRightsThunk()); +``` + +## Intern + +- Ruft `fetchUserRightsService()` auf +- Fehlerbehandlung per `rejectWithValue(error.message)` \ No newline at end of file diff --git a/docs/docs/services/database/area/updateAreaService.md b/docs/docs/services/database/area/updateAreaService.md new file mode 100644 index 000000000..fa7520cd2 --- /dev/null +++ b/docs/docs/services/database/area/updateAreaService.md @@ -0,0 +1,37 @@ + + +# 🗺️ updateAreaService.js + +Service zur Aktualisierung der Koordinaten eines Bereichs (Area) in der Datenbank. + +## Verwendung + +```js +await updateAreaService({ + idLocation: 4, + idMap: 1, + newCoords: { x: 53.219, y: 8.435 } +}); +``` + +## API-Route + +``` +PUT /api/talas_v5_DB/area/updateArea +``` + +## Payload + +```json +{ + "idLocation": 4, + "idMap": 1, + "x": 53.219, + "y": 8.435 +} +``` + +## Rückgabe + +- Erfolgreich: JSON mit Erfolgsmeldung +- Fehler: `throw new Error(...)` bei nicht OK \ No newline at end of file diff --git a/docs/docs/services/database/fetchDeviceNameByIdService.md b/docs/docs/services/database/fetchDeviceNameByIdService.md new file mode 100644 index 000000000..95b5880b6 --- /dev/null +++ b/docs/docs/services/database/fetchDeviceNameByIdService.md @@ -0,0 +1,22 @@ + + +# 🆔 fetchDeviceNameByIdService.js + +Lädt den Gerätenamen (`name`) anhand der ID (`idLD`) von der API. + +## Verwendung + +```js +const name = await fetchDeviceNameById(idLD); +``` + +## API-Route + +``` +/api/talas_v5_DB/locationDevice/locationDeviceNameById?idLD=... +``` + +## Rückgabe + +- Erfolgreich: Gerätebezeichnung als `string` +- Fehler: `"Unbekannt"` \ No newline at end of file diff --git a/docs/docs/services/database/fetchLocationDevicesService.md b/docs/docs/services/database/fetchLocationDevicesService.md new file mode 100644 index 000000000..f40447bab --- /dev/null +++ b/docs/docs/services/database/fetchLocationDevicesService.md @@ -0,0 +1,21 @@ + + +# 🧰 fetchLocationDevicesService.js + +Service zum Abrufen aller Einträge aus der `location_device` Tabelle. + +## Verwendung + +```js +const result = await fetchLocationDevicesService(); +``` + +## API-Route + +``` +/api/talas_v5_DB/locationDevice/locationDevices +``` + +## Rückgabe + +- JSON-Array aller Geräte \ No newline at end of file diff --git a/docs/docs/services/database/fetchPriorityConfigService.md b/docs/docs/services/database/fetchPriorityConfigService.md new file mode 100644 index 000000000..7dce4c026 --- /dev/null +++ b/docs/docs/services/database/fetchPriorityConfigService.md @@ -0,0 +1,17 @@ + + +# 🎯 fetchPriorityConfigService.js + +Service zum Abrufen der Prioritätskonfiguration für POIs oder Marker. + +## Verwendung + +```js +const result = await fetchPriorityConfigService(); +``` + +## API-Route + +``` +/api/talas_v5_DB/priorityConfig +``` \ No newline at end of file diff --git a/docs/docs/services/database/getDeviceIdByNameService.md b/docs/docs/services/database/getDeviceIdByNameService.md new file mode 100644 index 000000000..cc0903403 --- /dev/null +++ b/docs/docs/services/database/getDeviceIdByNameService.md @@ -0,0 +1,22 @@ + + +# 🆔 getDeviceIdByNameService.js + +Service zur Ermittlung der Geräte-ID (`idLD`) anhand eines Gerätenamens. + +## Verwendung + +```js +const id = await getDeviceIdByNameService("CPL-V4"); +``` + +## API-Route + +``` +/api/talas_v5_DB/locationDevice/getDeviceId?deviceName=... +``` + +## Rückgabe + +- Erfolgreich: `idLD` (number) +- Fehler: Exception \ No newline at end of file diff --git a/docs/docs/services/database/locationDevice/fetchLocationDevicesService.md b/docs/docs/services/database/locationDevice/fetchLocationDevicesService.md new file mode 100644 index 000000000..f40447bab --- /dev/null +++ b/docs/docs/services/database/locationDevice/fetchLocationDevicesService.md @@ -0,0 +1,21 @@ + + +# 🧰 fetchLocationDevicesService.js + +Service zum Abrufen aller Einträge aus der `location_device` Tabelle. + +## Verwendung + +```js +const result = await fetchLocationDevicesService(); +``` + +## API-Route + +``` +/api/talas_v5_DB/locationDevice/locationDevices +``` + +## Rückgabe + +- JSON-Array aller Geräte \ No newline at end of file diff --git a/docs/docs/services/database/pois/addPoiService.md b/docs/docs/services/database/pois/addPoiService.md new file mode 100644 index 000000000..16d80616d --- /dev/null +++ b/docs/docs/services/database/pois/addPoiService.md @@ -0,0 +1,22 @@ + + +# ➕ addPoiService.js + +Service zum Hinzufügen eines neuen POIs in der Datenbank. + +## Verwendung + +```js +await addPoiService({ + name: "Beispiel", + poiTypeId: 1, + idLD: 12, + latitude: 53.21, + longitude: 8.43 +}); +``` + +## API + +- Endpoint: `POST /api/talas_v5_DB/pois/addPoi` +- Headers: `"Content-Type": "application/json"` \ No newline at end of file diff --git a/docs/docs/services/database/pois/deletePoiService.md b/docs/docs/services/database/pois/deletePoiService.md new file mode 100644 index 000000000..62569ca9a --- /dev/null +++ b/docs/docs/services/database/pois/deletePoiService.md @@ -0,0 +1,15 @@ + + +# ❌ deletePoiService.js + +Service zum Löschen eines POIs aus der Datenbank per ID. + +## Verwendung + +```js +await deletePoiService(id); +``` + +## API + +- Endpoint: `DELETE /api/talas_v5_DB/pois/deletePoi?id=ID` \ No newline at end of file diff --git a/docs/docs/services/database/pois/fetchPoiDataByIdService.md b/docs/docs/services/database/pois/fetchPoiDataByIdService.md new file mode 100644 index 000000000..fbbb86acc --- /dev/null +++ b/docs/docs/services/database/pois/fetchPoiDataByIdService.md @@ -0,0 +1,15 @@ + + +# 🔍 fetchPoiDataByIdService.js + +Service zum Abrufen der POI-Daten anhand einer POI-ID. + +## Verwendung + +```js +const poi = await fetchPoiDataService(idPoi); +``` + +## API + +- Endpoint: `GET /api/talas_v5_DB/pois/getPoiById?idPoi=ID` \ No newline at end of file diff --git a/docs/docs/services/database/pois/fetchPoiDataService.md b/docs/docs/services/database/pois/fetchPoiDataService.md new file mode 100644 index 000000000..797a8a658 --- /dev/null +++ b/docs/docs/services/database/pois/fetchPoiDataService.md @@ -0,0 +1,15 @@ + + +# 📄 fetchPoiDataService.js + +Lädt POI-Icons (alias `poiData`) aus dem Serververzeichnis. + +## Verwendung + +```js +const data = await fetchPoiDataService(); +``` + +## API + +- Endpoint: `GET /api/talas_v5_DB/pois/poi-icons` \ No newline at end of file diff --git a/docs/docs/services/database/pois/fetchPoiIconsDataService.md b/docs/docs/services/database/pois/fetchPoiIconsDataService.md new file mode 100644 index 000000000..8e1ff22a0 --- /dev/null +++ b/docs/docs/services/database/pois/fetchPoiIconsDataService.md @@ -0,0 +1,15 @@ + + +# 🖼️ fetchPoiIconsDataService.js + +Service zum Abrufen der POI-Icon-Metadaten. + +## Verwendung + +```js +const icons = await fetchPoiIconsDataService(); +``` + +## API + +- Endpoint: `GET /api/talas_v5_DB/pois/poi-icons` \ No newline at end of file diff --git a/docs/docs/services/database/pois/fetchPoiMarkersService.md b/docs/docs/services/database/pois/fetchPoiMarkersService.md new file mode 100644 index 000000000..de209ebdf --- /dev/null +++ b/docs/docs/services/database/pois/fetchPoiMarkersService.md @@ -0,0 +1,15 @@ + + +# 📍 fetchPoiMarkersService.js + +Service zum Abrufen aller gespeicherten POI-Marker (Positionen). + +## Verwendung + +```js +const pois = await fetchPoiMarkersService(); +``` + +## API + +- Endpoint: `GET /api/talas_v5_DB/pois/readAllPOIs` \ No newline at end of file diff --git a/docs/docs/services/database/pois/fetchPoiTypService.md b/docs/docs/services/database/pois/fetchPoiTypService.md new file mode 100644 index 000000000..360d035fe --- /dev/null +++ b/docs/docs/services/database/pois/fetchPoiTypService.md @@ -0,0 +1,15 @@ + + +# 🗂️ fetchPoiTypService.js + +Service zum Abrufen aller verfügbaren POI-Typen aus der Datenbank. + +## Verwendung + +```js +const types = await fetchPoiTypService(); +``` + +## API + +- Endpoint: `GET /api/talas_v5_DB/poiTyp/readPoiTyp` \ No newline at end of file diff --git a/docs/docs/services/database/pois/updatePoiService.md b/docs/docs/services/database/pois/updatePoiService.md new file mode 100644 index 000000000..07f78d478 --- /dev/null +++ b/docs/docs/services/database/pois/updatePoiService.md @@ -0,0 +1,21 @@ + + +# 📝 updatePoiService.js + +Service zur Aktualisierung eines POIs mit neuen Informationen. + +## Verwendung + +```js +await updatePoiService({ + idPoi: 5, + description: "Neuer Text", + idLD: 3, + idPoiTyp: 1 +}); +``` + +## API + +- Endpoint: `POST /api/talas_v5_DB/pois/updatePoi` +- Body: JSON mit den zu aktualisierenden Feldern \ No newline at end of file diff --git a/docs/docs/services/database/polylines/fetchGisLinesService.md b/docs/docs/services/database/polylines/fetchGisLinesService.md new file mode 100644 index 000000000..13cecfeca --- /dev/null +++ b/docs/docs/services/database/polylines/fetchGisLinesService.md @@ -0,0 +1,19 @@ + + +# 📡 fetchGisLinesService.js + +Service zum Abrufen aller Linien aus der `gisLines`-Tabelle der Datenbank. + +## Verwendung + +```js +const lines = await fetchGisLinesService(); +``` + +## API + +- Endpoint: `GET /api/talas_v5_DB/gisLines/readGisLines` + +## Rückgabe + +- JSON-Array mit allen Linien und ihren Koordinaten \ No newline at end of file diff --git a/docs/docs/services/database/polylines/updatePolylineCoordinatesService.md b/docs/docs/services/database/polylines/updatePolylineCoordinatesService.md new file mode 100644 index 000000000..2cf961735 --- /dev/null +++ b/docs/docs/services/database/polylines/updatePolylineCoordinatesService.md @@ -0,0 +1,24 @@ + + +# 🧭 updatePolylineCoordinatesService.js + +Service zum Aktualisieren der Koordinaten einer Linie in der Datenbank. + +## Verwendung + +```js +await updatePolylineCoordinatesService({ + idLD: 5, + idModul: 1, + newCoordinates: [[53.2, 8.4], [53.21, 8.45]] +}); +``` + +## API + +- Endpoint: `POST /api/talas_v5_DB/gisLines/updateLineCoordinates` +- Headers: `{ "Content-Type": "application/json" }` + +## Fehlerbehandlung + +- Bei Fehler: `throw new Error(...)` mit Backend-Meldung \ No newline at end of file diff --git a/docs/docs/services/database/updateLocationInDatabaseService.md b/docs/docs/services/database/updateLocationInDatabaseService.md new file mode 100644 index 000000000..e5561d0fe --- /dev/null +++ b/docs/docs/services/database/updateLocationInDatabaseService.md @@ -0,0 +1,21 @@ + + +# 📍 updateLocationInDatabaseService.js + +Service zur Aktualisierung der Geokoordinaten eines POIs in der Datenbank. + +## Verwendung + +```js +await updateLocationInDatabaseService(id, lat, lng); +``` + +## API-Route + +``` +/api/talas_v5_DB/pois/updateLocation +``` + +## Methode + +- `POST` mit JSON-Body: `{ id, latitude, longitude }` \ No newline at end of file diff --git a/docs/docs/services/utils/fetchWithTimeout.md b/docs/docs/services/utils/fetchWithTimeout.md new file mode 100644 index 000000000..c57084bb4 --- /dev/null +++ b/docs/docs/services/utils/fetchWithTimeout.md @@ -0,0 +1,46 @@ + + +# ⏱️ fetchWithTimeout.js + +Hilfsfunktion zur Durchführung eines `fetch`-Requests mit einem Timeout. + +--- + +## 💡 Zweck + +Manche Serveranfragen können hängen bleiben. Diese Funktion sorgt dafür, dass eine Anfrage nach einer bestimmten Zeit abgebrochen wird, um UI-Blockierungen oder lange Wartezeiten zu vermeiden. + +--- + +## 🧩 Funktion + +```js +fetchWithTimeout(url, options, timeout); +``` + +- `url`: Ziel-URL +- `options`: Fetch-Optionen (Headers, Methode etc.) +- `timeout`: Zeit in Millisekunden (Standard: 5000 ms) + +--- + +## Beispiel + +```js +const response = await fetchWithTimeout("/api/data", {}, 3000); +``` + +--- + +## Verhalten + +- Verwendet `AbortController` zur Abbruchsteuerung +- Gibt den `fetch`-Response oder einen Fehler zurück + +--- + +## Pfad + +``` +/services/utils/fetchWithTimeout.js +``` \ No newline at end of file diff --git a/docs/docs/services/webservice/fetchGisLinesStatusService.md b/docs/docs/services/webservice/fetchGisLinesStatusService.md new file mode 100644 index 000000000..da7745114 --- /dev/null +++ b/docs/docs/services/webservice/fetchGisLinesStatusService.md @@ -0,0 +1,60 @@ + + +# fetchGisLinesStatusService + +Lädt Linienstatus-Daten über den TALAS WebService. + +--- + +## 📁 URL-Aufbau + +``` +/ClientData/WebServiceMap.asmx/GisLinesStatus?idMap={idMap} +``` + +- Die `idMap` wird automatisch aus der URL (`?m=...`) gelesen. +- Diese WebService-Antwort enthält ein Objekt mit dem Feld `Statis[]`. + +--- + +## ✅ Rückgabe + +```json +{ + "Name": "...", + "Statis": [ ... ] +} +``` + +- `Statis[]` enthält Statusinformationen zu Linien (Farben, Meldungen, Werte). +- Diese Daten sind **nicht identisch** mit `gisLines.data` aus der Datenbank. + +--- + +## ❗ Unterschied zu `gisLinesSlice` (aus der Datenbank) + +| Eigenschaft | `gisLines` (DB) | `gisLinesStatus` (WebService) | +| ----------- | --------------------------- | ----------------------------------- | +| Quelle | `api/talas_v5_DB/gisLines` | `WebServiceMap.asmx/GisLinesStatus` | +| Daten | Liniengeometrien (`points`) | Status, Meldungen, Farben, Werte | +| Typ | `PolyLine-Daten` | `Statusanzeige` für Linien | +| Nutzung | Layer-Rendering | Farbliche Darstellung / Tooltip | + +--- + +## 🧠 Verwendung im Frontend + +- Die Daten werden über `fetchGisLinesStatusThunk` geladen. +- Sie landen im Redux Slice `gisLinesStatusSlice`. +- Zugriff über: `selectGisLinesStatus(state)` → enthält `.data`, `.status`, `.error` + +--- + +## 📁 Zugehörige Dateien + +| Datei | Zweck | +| ------------------------------- | ---------------------- | +| `fetchGisLinesStatusService.js` | WebService-Aufruf | +| `fetchGisLinesStatusThunk.js` | Redux Thunk | +| `gisLinesStatusSlice.js` | Redux Slice | +| `store.js` | Integration des Slices | diff --git a/docs/docs/services/webservice/fetchGisStationsMeasurementsService.md b/docs/docs/services/webservice/fetchGisStationsMeasurementsService.md new file mode 100644 index 000000000..f10c4b708 --- /dev/null +++ b/docs/docs/services/webservice/fetchGisStationsMeasurementsService.md @@ -0,0 +1,16 @@ + + +# 📈 fetchGisStationsMeasurementsService.js + +Lädt Schleifen- und Isolationswerte für Stationen. + +## Verwendung + +```js +const messwerte = await fetchGisStationsMeasurementsService(); +``` + +## API-Aufruf + +- Endpoint: `/ClientData/WebServiceMap.asmx/GisStationsMeasurements?idMap=...&idUser=...` +- Rückgabe: `Statis[]` \ No newline at end of file diff --git a/docs/docs/services/webservice/fetchGisStationsStaticDistrictService.md b/docs/docs/services/webservice/fetchGisStationsStaticDistrictService.md new file mode 100644 index 000000000..5ab0812b3 --- /dev/null +++ b/docs/docs/services/webservice/fetchGisStationsStaticDistrictService.md @@ -0,0 +1,16 @@ + + +# 🧭 fetchGisStationsStaticDistrictService.js + +Lädt Koordinateninformationen (`Points[]`) aller Stationen im aktuellen Bezirk. + +## Verwendung + +```js +const points = await fetchGisStationsStaticDistrictService(); +``` + +## API-Aufruf + +- Endpoint: `/ClientData/WebServiceMap.asmx/GisStationsStaticDistrict?idMap=...&idUser=...` +- Rückgabe: `Points[]` \ No newline at end of file diff --git a/docs/docs/services/webservice/fetchGisStationsStatusDistrictService.md b/docs/docs/services/webservice/fetchGisStationsStatusDistrictService.md new file mode 100644 index 000000000..c5d217c08 --- /dev/null +++ b/docs/docs/services/webservice/fetchGisStationsStatusDistrictService.md @@ -0,0 +1,16 @@ + + +# 🚦 fetchGisStationsStatusDistrictService.js + +Service zum Abrufen des Status aller Stationen im aktuellen Bezirk. + +## Verwendung + +```js +const result = await fetchGisStationsStatusDistrictService(); +``` + +## API-Aufruf + +- Endpoint: `/ClientData/WebServiceMap.asmx/GisStationsStatusDistrict?idMap=...&idUser=...` +- Rückgabe: `Statis[]` \ No newline at end of file diff --git a/docs/docs/services/webservice/fetchGisSystemStaticService.md b/docs/docs/services/webservice/fetchGisSystemStaticService.md new file mode 100644 index 000000000..1a7da718f --- /dev/null +++ b/docs/docs/services/webservice/fetchGisSystemStaticService.md @@ -0,0 +1,16 @@ + + +# 🧱 fetchGisSystemStaticService.js + +Service zur Abfrage von Systemdaten aller Module aus dem Webservice. + +## Verwendung + +```js +const systems = await fetchGisSystemStaticService(); +``` + +## API-Aufruf + +- Endpoint: `/ClientData/WebServiceMap.asmx/GisSystemStatic?idMap=...&idUser=...` +- Rückgabe: `Systems[]` \ No newline at end of file diff --git a/docs/docs/services/webservice/fetchUserRightsService.md b/docs/docs/services/webservice/fetchUserRightsService.md new file mode 100644 index 000000000..ed0b170cb --- /dev/null +++ b/docs/docs/services/webservice/fetchUserRightsService.md @@ -0,0 +1,16 @@ + + +# 🔐 fetchUserRightsService.js + +Lädt die Benutzerrechte über den TALAS WebService. + +## Verwendung + +```js +const rights = await fetchUserRightsService(); +``` + +## API-Aufruf + +- Endpoint: `/ClientData/WebServiceMap.asmx/GisSystemStatic?idMap=...&idUser=...` +- Rückgabe: `Rights[]` (Fallback: `[]`) \ No newline at end of file diff --git a/docs/docs/utils/addContextMenuToMarker.md b/docs/docs/utils/addContextMenuToMarker.md new file mode 100644 index 000000000..beaf58578 --- /dev/null +++ b/docs/docs/utils/addContextMenuToMarker.md @@ -0,0 +1,28 @@ + + +# 🧭 addContextMenuToMarker.js + +Fügt einem Leaflet-Marker ein individuelles Kontextmenü hinzu. + +## Zweck + +- Erlaubt dem Nutzer über Rechtsklick oder Interaktion den Zugriff auf Funktionen wie: + - „Station öffnen (Tab)“ + - „Details anzeigen“ + - „Bearbeiten starten“ + +## Verwendung + +```js +addContextMenuToMarker(marker, idLD, name); +``` + +## Parameter + +- `marker`: Leaflet-Marker +- `idLD`: Geräte-ID +- `name`: Anzeigename + +## Kontext + +- Wird z. B. in `createAndSetDevices.js` verwendet diff --git a/docs/docs/utils/contextMenuUtils.md b/docs/docs/utils/contextMenuUtils.md new file mode 100644 index 000000000..753c40008 --- /dev/null +++ b/docs/docs/utils/contextMenuUtils.md @@ -0,0 +1,14 @@ + + +# 📋 contextMenuUtils.js + +Hilfsfunktionen zur Verwaltung des Kontextmenüs auf Kartenmarkern und Polylinien. + +## Exportierte Funktionen + +- `getPoiContextMenuOptions(marker)` +- `getPolylineContextMenuOptions(line)` + +## Zweck + +- Menüeinträge je nach Zustand und Marker-Art dynamisch generieren diff --git a/docs/docs/utils/devices/createAndSetDevices.md b/docs/docs/utils/devices/createAndSetDevices.md new file mode 100644 index 000000000..62dece85a --- /dev/null +++ b/docs/docs/utils/devices/createAndSetDevices.md @@ -0,0 +1,107 @@ + + +# 🧭 createAndSetDevices.js – Geräte setzen und verwalten + +## Zweck + +Diese Datei erstellt Leaflet-Marker für aktive Geräte basierend auf Webservice-Daten +und konfiguriert Kontexteinträge für Interaktionen wie: + +- Geräte-Popup anzeigen +- Station/Gerät per Klick in neue Tab öffnen -> Kontextmenü ->Item "Station öffnen (Tab)" +- Statusinformationen einblenden +- Redux-Aktionen für ausgewähltes Gerät auslösen + +Die erzeugten Marker werden über `setMarkersFunction(markersData)` an die aufrufende Komponente übergeben. + +--- + +## Datenquellen + +Die Daten stammen aus: + +- `GisStationsStaticDistrict` (statische Stationsinfos) +- `GisStationsStatusDistrict` (Statusinformationen) + +Sie werden entweder über echte Webservices oder Mock-Daten geladen. + +--- + +## Besonderheiten + +- Marker werden mit Prioritätsicons gerendert +- Redux-Slices: + - `selectedDeviceSlice` wird bei Hover gesetzt + - `lineVisibilitySlice` aktualisiert Linienstatus +- Leaflet-Kontextmenü (nur Marker) mit Menüeintrag: + „Station öffnen (Tab)“ + +--- + +## Dynamische URL mit Port-Logik + +Die Station-Links im Kontextmenü nutzen keine feste URL mehr. +Stattdessen wird dynamisch unterschieden: + +```js +const mode = process.env.NEXT_PUBLIC_API_PORT_MODE; + +const baseUrl = + mode === "dev" + ? \`\${window.location.protocol}//\${window.location.hostname}:80/talas5/\` + : \`\${window.location.origin}/talas5/\`; +``` + +➡ Dadurch wird verhindert, dass bei jeder Server-IP `.env.local` oder ein Build nötig ist. + +--- + +## Kontextmenüaktion + +```js +window.open(`${baseUrl}cpl.aspx?ver=35&kue=24&id=${station.IdLD}`, "_blank"); +``` + +--- + +## Schutz vor doppelten Kontextmenüs + +Ein `contextMenuCreated`-Flag stellt sicher, dass pro Marker nur **ein** Kontextmenü erzeugt wird: + +```js +if (!contextMenuCreated) { + contextMenuCreated = true; + marker.bindContextMenu({ ... }); +} +``` + +➡ Verhindert Duplikate bei wiederholten Rechtsklicks + +--- + +## Weitere Funktionen + +- Popup-HTML enthält Statusanzeigen (Name, Farbe, Meldung) +- Marker werden auf Klick animiert (bounce-Effekt) +- Kontextmenüeinträge werden sauber entfernt bei Klick außerhalb + +--- + +## Tests + +- Marker wird bei gültigen Daten erzeugt +- Kontextmenü enthält nur **einen** Eintrag „Station öffnen“ +- Popup zeigt korrekte Statusinformationen (Farben, Texte) +- Redux `setSelectedDevice(...)` wird korrekt aufgerufen + +--- + +## Siehe auch + +- `setupPolylines.js` +- `redux/slices/selectedDeviceSlice.js` +- Webservices: `fetchGisStationsStaticDistrict.js`, `fetchGisStationsStatusDistrict.js` + +--- + +📄 Pfad: `/docs/frontend/utils/devices/createAndSetDevices.md` diff --git a/docs/docs/utils/geometryUtils.md b/docs/docs/utils/geometryUtils.md new file mode 100644 index 000000000..4dd2920f1 --- /dev/null +++ b/docs/docs/utils/geometryUtils.md @@ -0,0 +1,14 @@ + + +# 📐 geometryUtils.js + +Mathematische Funktionen zur Berechnung geometrischer Werte auf der Karte. + +## Funktionen + +- `calculateDistance(latlng1, latlng2)` +- `getMidpoint(coords)` + +## Zweck + +- Interne Hilfsfunktionen für Strecken, Tooltip-Positionen etc. diff --git a/docs/docs/utils/initializeMap.md b/docs/docs/utils/initializeMap.md new file mode 100644 index 000000000..738b33a9f --- /dev/null +++ b/docs/docs/utils/initializeMap.md @@ -0,0 +1,15 @@ + + +# 🗺️ initializeMap.js + +Initialisiert die Leaflet-Karte mit Basislayern, Gruppen und globalen Events. + +## Funktionen + +- `initializeMap(mapElementId)` + +## Verhalten + +- Erstellt LayerGroups (Devices, POIs, Linien) +- Bindet Kontextmenü +- Stellt Default-Zoom und Position ein diff --git a/docs/docs/utils/mapUtils.md b/docs/docs/utils/mapUtils.md new file mode 100644 index 000000000..4db6069a7 --- /dev/null +++ b/docs/docs/utils/mapUtils.md @@ -0,0 +1,10 @@ + + +# 🧰 mapUtils.js + +Allgemeine Hilfsfunktionen für Leaflet (z. B. Zoom, Marker-Checks, Layer-Findung). + +## Funktionen + +- `zoomToBounds(layerGroup)` +- `findLayerById(map, id)` diff --git a/docs/docs/utils/markerUtils.md b/docs/docs/utils/markerUtils.md new file mode 100644 index 000000000..0df242dce --- /dev/null +++ b/docs/docs/utils/markerUtils.md @@ -0,0 +1,14 @@ + + +# 📍 markerUtils.js + +Hilfsfunktionen zur Erstellung und Konfiguration von Leaflet-Markern. + +## Exportierte Funktionen + +- `createIconByType(type)` +- `createMarker(position, icon)` + +## Kontext + +- Wird in `setupDevices.js` und `setupPOIs.js` verwendet diff --git a/docs/docs/utils/mysqlPool.md b/docs/docs/utils/mysqlPool.md new file mode 100644 index 000000000..847f12ae7 --- /dev/null +++ b/docs/docs/utils/mysqlPool.md @@ -0,0 +1,16 @@ + + +# 💾 mysqlPool.js + +MySQL-Verbindungspool für effiziente Datenbankabfragen (z. B. mit `promise-mysql`). + +## Verwendung + +```js +const connection = await pool.getConnection(); +``` + +## Zweck + +- Reuse von Verbindungen +- Fehlervermeidung bei vielen gleichzeitigen Abfragen diff --git a/docs/docs/utils/openInNewTab.md b/docs/docs/utils/openInNewTab.md new file mode 100644 index 000000000..d883d4f63 --- /dev/null +++ b/docs/docs/utils/openInNewTab.md @@ -0,0 +1,15 @@ + + +# 🪟 openInNewTab.js + +Öffnet eine URL in einem neuen Tab und schützt vor Referrer-Leaks. + +## Verwendung + +```js +openInNewTab(url); +``` + +## Intern + +- nutzt `window.open` mit `noopener,noreferrer` diff --git a/docs/docs/utils/openInSameWindow.md b/docs/docs/utils/openInSameWindow.md new file mode 100644 index 000000000..c778e2c18 --- /dev/null +++ b/docs/docs/utils/openInSameWindow.md @@ -0,0 +1,15 @@ + + +# 🚪 openInSameWindow.js + +Öffnet eine URL im aktuellen Tab. + +## Verwendung + +```js +openInSameWindow("/target"); +``` + +## Verhalten + +- `window.location.href = url` diff --git a/docs/docs/utils/poiUtils.md b/docs/docs/utils/poiUtils.md new file mode 100644 index 000000000..aa67c8922 --- /dev/null +++ b/docs/docs/utils/poiUtils.md @@ -0,0 +1,14 @@ + + +# 🧭 poiUtils.js + +Hilfsfunktionen zur Handhabung von POIs (z. B. Icons, Typzuordnung, Interaktion). + +## Funktionen + +- `getIconForPoiType(type)` +- `groupPOIsByType(list)` + +## Verwendung + +- In `setupPOIs.js` und Thunks für POI-Handling diff --git a/docs/docs/utils/polylines/contextMenu.md b/docs/docs/utils/polylines/contextMenu.md new file mode 100644 index 000000000..df9114129 --- /dev/null +++ b/docs/docs/utils/polylines/contextMenu.md @@ -0,0 +1,10 @@ + + +# 📋 contextMenu.js + +Funktionen zum Steuern des Polyline-Kontextmenüs (Schließen, Zurücksetzen, etc.). + +## exportierte Funktionen + +- `closePolylineSelectionAndContextMenu(map)`: Setzt Polyline-Auswahl zurück und schließt das Kontextmenü +- `monitorContextMenu(map)`: Überwacht via `localStorage`, ob das Menü automatisch geschlossen werden soll \ No newline at end of file diff --git a/docs/docs/utils/polylines/eventHandlers.md b/docs/docs/utils/polylines/eventHandlers.md new file mode 100644 index 000000000..e28deb95a --- /dev/null +++ b/docs/docs/utils/polylines/eventHandlers.md @@ -0,0 +1,10 @@ + + +# 🖱️ eventHandlers.js + +Bindet Maus-Events an Polylinien (z. B. Hover-Effekte). + +## exportierte Funktionen + +- `enablePolylineEvents(polylines, lineColors)` +- `disablePolylineEvents(polylines)` \ No newline at end of file diff --git a/docs/docs/utils/polylines/monitorContextMenu.md b/docs/docs/utils/polylines/monitorContextMenu.md new file mode 100644 index 000000000..5e72ebb20 --- /dev/null +++ b/docs/docs/utils/polylines/monitorContextMenu.md @@ -0,0 +1,10 @@ + + +# 🔄 monitorContextMenu.js + +Erweiterte Überwachung des Kontextmenüs über `localStorage`. + +## Funktion + +- Importiert `closePolylineSelectionAndContextMenu` +- Ruft regelmäßig `setTimeout` auf, um `contextMenuExpired` zu prüfen \ No newline at end of file diff --git a/docs/docs/utils/polylines/polylineSubscription.md b/docs/docs/utils/polylines/polylineSubscription.md new file mode 100644 index 000000000..a7374f798 --- /dev/null +++ b/docs/docs/utils/polylines/polylineSubscription.md @@ -0,0 +1,10 @@ + + +# 🧭 polylineSubscription.js + +Abonnement auf den Redux-Store, um auf `forceClose` im Kontextmenü zu reagieren. + +## Verhalten + +- Erkennt `polylineContextMenu.forceClose` +- Ruft `contextmenu.hide()` auf und resetet Redux-Status \ No newline at end of file diff --git a/docs/docs/utils/polylines/redrawPolyline.md b/docs/docs/utils/polylines/redrawPolyline.md new file mode 100644 index 000000000..e7ae9d31f --- /dev/null +++ b/docs/docs/utils/polylines/redrawPolyline.md @@ -0,0 +1,14 @@ + + +# 🔁 redrawPolyline.js + +Zeichnet eine Polyline mit neuen Koordinaten und Tooltip neu. + +## Funktion + +```js +redrawPolyline(lineData, lineColors, tooltipContents, map) +``` + +- Entfernt vorherige Polyline +- Erstellt neue mit Tooltip und Hover-Effekten \ No newline at end of file diff --git a/docs/docs/utils/polylines/setupPolylines.md b/docs/docs/utils/polylines/setupPolylines.md new file mode 100644 index 000000000..1f3c20246 --- /dev/null +++ b/docs/docs/utils/polylines/setupPolylines.md @@ -0,0 +1,76 @@ + + +# 🧭 setupPolylines.js – Polylinien zeichnen und verwalten + +## Zweck + +Diese Datei enthält die zentrale Funktion `setupPolylines`, die in der Kartenkomponente (Leaflet) Polylinien sowie Marker basierend auf Gerätekonfigurationen zeichnet und verwaltet. + +Sie wird verwendet, um: + +- Polylinien basierend auf Koordinaten zu zeichnen +- Stützpunkte visuell als Marker anzuzeigen +- Marker kontextsensitiv mit Optionen (z. B. „Stützpunkt entfernen“, „Koordinaten anzeigen“) auszustatten +- Linien aktualisieren und neue Koordinaten in die Datenbank schreiben +- Kontextmenü-Interaktionen zu ermöglichen + +--- + +## Besonderheiten + +- Marker mit speziellen Icons (Start, Ende, Zwischenpunkt) +- Interaktivität abhängig vom Bearbeitungsmodus (editMode aus `localStorage`) +- Kontextmenü pro Marker und Linie individuell steuerbar +- API-Aufrufe zur Koordinaten-Aktualisierung: + `POST /api/talas_v5_DB/gisLines/updateLineCoordinates` + +--- + +## Dynamische URL mit Port-Steuerung + +Die Datei verwendet **keine feste API-Basis-URL** mehr aus `.env.local`. +Stattdessen wird `NEXT_PUBLIC_API_PORT_MODE` genutzt, um zwischen Entwicklungs- und Produktionsumgebung zu unterscheiden: + +```env +NEXT_PUBLIC_API_PORT_MODE=dev +``` + +### Beispiel im Code: + +```js +const mode = process.env.NEXT_PUBLIC_API_PORT_MODE; + +const baseUrl = mode === "dev" ? `${window.location.protocol}//${window.location.hostname}:80/talas5/` : `${window.location.origin}/talas5/`; +``` + +--- + +## Kontextmenüaktionen + +- Station öffnen (neuer Tab) +- Koordinaten anzeigen +- Zoom in/out +- Karte zentrieren +- Stützpunkt hinzufügen/entfernen (wenn editMode) + +--- + +## Speicherorte + +- Polylinien und LineColors werden unter `window.polylines` und `window.lineColors` global gespeichert +- Aktive Redux-Slices: + - `polylineContextMenuSlice` + - `addPoiOnPolylineSlice` + - `polylineLayerVisibleSlice` + +--- + +## Siehe auch + +- API-Aufruf: `/api/talas_v5_DB/gisLines/updateLineCoordinates` +- `utils/geometryUtils.js`, `poiUtils.js`, `eventHandlers.js` +- `redux/slices/polylineContextMenuSlice.js` + +--- + +📄 Pfad: `/docs/frontend/utils/polylines/setupPolylines.md` diff --git a/docs/docs/utils/setupDevices.md b/docs/docs/utils/setupDevices.md new file mode 100644 index 000000000..3297a5cf7 --- /dev/null +++ b/docs/docs/utils/setupDevices.md @@ -0,0 +1,15 @@ + + +# 🔌 setupDevices.js + +Fügt alle Geräte (Devices) zur Karte hinzu. + +## Funktionen + +- `setupDevices(map, deviceList)` + +## Verhalten + +- Marker-Erstellung +- Eventbindung +- Layer-Zuweisung diff --git a/docs/docs/utils/setupPOIs.md b/docs/docs/utils/setupPOIs.md new file mode 100644 index 000000000..c84e09202 --- /dev/null +++ b/docs/docs/utils/setupPOIs.md @@ -0,0 +1,32 @@ + + +# 🧭 `setupPOIs.js` + +## Zweck + +Zeichnet alle POI-Marker auf die Leaflet-Karte basierend auf Datenbankeinträgen. Bindet Popup, Kontextmenü, Drag'n'Drop und Redux-Zustand ein. + +## Parameter + +| Name | Beschreibung | +| ----------------- | ---------------------------------------------------- | +| `map` | Leaflet-Instanz | +| `pois` | Array mit POI-Objekten aus der Datenbank | +| `poiData` | Array mit Iconpfaden: `{ idPoi, path }` | +| `poiTypMap` | Map-Objekt: `idPoiTyp → Name` | +| `poiLayerVisible` | Gibt an, ob Layer überhaupt gezeichnet werden sollen | + +## Besonderheiten + +- Icon wird über `iconMap.get(idPoi)` bezogen +- Fallback bei unbekanntem Icon (`default-icon.png`) +- Rechteprüfung für Drag & Kontextmenü (`userRights.some(...)`) +- Marker können bearbeitet, verschoben, gelöscht werden +- Bei `mouseover` → Redux: `setSelectedPoi(poi)` + +## Beispiel für Testdaten + +```js +const poi = { idPoi: 7, idPoiTyp: 2, position: "POINT(8.5 53.1)", description: "Mast", idLD: 123 }; +const poiData = [{ idPoi: 7, path: "poi-marker-icon-2.png" }]; +``` diff --git a/docs/docs/utils/zoomAndCenterUtils.md b/docs/docs/utils/zoomAndCenterUtils.md new file mode 100644 index 000000000..a2bb75dca --- /dev/null +++ b/docs/docs/utils/zoomAndCenterUtils.md @@ -0,0 +1,10 @@ + + +# 🔍 zoomAndCenterUtils.js + +Hilfsfunktionen zum Zoomen auf Marker oder Linien. + +## Funktionen + +- `zoomToMarker(map, marker)` +- `centerOnCoordinates(map, coords)`