diff --git a/CHANGELOG.md b/CHANGELOG.md index fe4cc6e39..59de6a2cc 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -4,6 +4,38 @@ Alle bedeutenden Änderungen an diesem Projekt werden in dieser Datei dokumentie --- +## [1.1.182] – 2025-05-27 + +### ♻️ Refactor + +- Bereichsaktualisierung (`updateAreaUtil.js`) vollständig ersetzt durch Redux-Architektur: + - 🔁 Neue Dateien: `updateAreaService.js`, `updateAreaThunk.js`, `updateAreaSlice.js` + - ❌ Alte fetch-Methode entfernt, durch Redux-Thunk ersetzt + - Bereichskoordinaten werden jetzt via `dispatch(updateAreaThunk(...))` aktualisiert + +### 🧠 Architektur + +- Einhaltung des Musters: `Service → Thunk → Slice` +- Status-Handling und Fehleranzeige über `updateAreaSlice` möglich +- `useAreaMarkersLayer.js` nutzt jetzt Redux-Thunk und optionalen `onUpdateSuccess`-Callback +- Redux-Version kompatibel mit DevTools und zentraler Fehlerbehandlung + +### ✅ Clean + +- `updateAreaUtil.js` ist überflüssig und wurde entfernt +- MapComponent verwendet jetzt `updateAreaThunk` direkt für Bereichsmarker + +### 📄 README.md (optional) + +- Wenn gewünscht, kann in `README.md` ein neuer Punkt unter `🚀 Funktionen` ergänzt werden: + > - Bereichsaktualisierung via Redux (statt direkter fetch-Aufrufe) + +### 🔧 Version + +- 📦 Version erhöht auf **1.1.182** + +--- + [1.1.181] – 2025-05-26 ♻️ Refactor setupPolylines.js von direktem fetch() auf sauberes Redux-Muster umgestellt: diff --git a/components/mainComponent/MapComponent.js b/components/mainComponent/MapComponent.js index 4746ce414..a35ab4f8c 100644 --- a/components/mainComponent/MapComponent.js +++ b/components/mainComponent/MapComponent.js @@ -18,13 +18,13 @@ import * as layers from "../../config/layers.js"; import addItemsToMapContextMenu from "../contextmenu/useMapContextMenu.js"; import useGmaMarkersLayer from "../../hooks/layers/useGmaMarkersLayer.js"; import useSmsfunkmodemMarkersLayer from "../../hooks/layers/useSmsfunkmodemMarkersLayer.js"; -import useBereicheMarkersLayer from "../../hooks/layers/useBereicheMarkersLayer.js"; +import useAreaMarkersLayer from "../../hooks/layers/useAreaMarkersLayer.js"; import { setupPolylines } from "../../utils/polylines/setupPolylines.js"; import { setupPOIs } from "../../utils/setupPOIs.js"; import useLayerVisibility from "../../hooks/useLayerVisibility.js"; import useLineData from "../../hooks/useLineData.js"; import { useMapComponentState } from "../../hooks/useMapComponentState.js"; -import { updateLocation } from "../../utils/updateBereichUtil.js"; + import CoordinatePopup from "../contextmenu/CoordinatePopup.js"; //----------Ui Widgets---------------- import MapLayersControlPanel from "../uiWidgets/mapLayersControlPanel/MapLayersControlPanel.js"; @@ -67,6 +67,7 @@ import { fetchGisLinesStatusThunk } from "../../redux/thunks/webservice/fetchGis import { fetchUserRightsThunk } from "../../redux/thunks/webservice/fetchUserRightsThunk"; import { fetchPoiIconsDataThunk } from "../../redux/thunks/database/pois/fetchPoiIconsDataThunk.js"; import { fetchPoiTypThunk } from "../../redux/thunks/database/pois/fetchPoiTypThunk.js"; +import { updateAreaThunk } from "../../redux/thunks/database/area/updateAreaThunk"; //----------------------------------------------------------------------------------------------------- const MapComponent = ({ locations, onLocationUpdate, lineCoordinates }) => { @@ -463,7 +464,7 @@ const MapComponent = ({ locations, onLocationUpdate, lineCoordinates }) => { } }, [map]); //-------------------------------------------- - // Bereich in DataSheet ->dropdownmenu + // Area in DataSheet ->dropdownmenu useEffect(() => { //console.log("🔍 GisStationsStaticDistrict Inhalt:", GisStationsStaticDistrict); @@ -653,28 +654,28 @@ const MapComponent = ({ locations, onLocationUpdate, lineCoordinates }) => { //--------------------------------------- // Initialisiere Leaflet-Karte - // Rufe useBereicheMarkersLayer direkt auf - //const [bereichUrl, setBereichUrl] = useState(null); + // Rufe useAreaMarkersLayer direkt auf + //const [areaUrl, setAreaUrl] = useState(null); const urlParams = new URLSearchParams(window.location.search); // URL-Parameter parsen const mValue = urlParams.get("m"); // Wert von "m" holen const hostname = window.location.hostname; // Dynamischer Hostname const port = 3000; // Definiere den gewünschten Port - const bereichUrl = `http://${hostname}:${port}/api/talas_v5_DB/bereich/readBereich?m=${mValue}`; // Dynamischer Hostname und Port + const areaUrl = `http://${hostname}:${port}/api/talas_v5_DB/area/readArea?m=${mValue}`; // Dynamischer Hostname und Port - // Bereichs-Marker basierend auf dynamischer URL laden + // Areas-Marker basierend auf dynamischer URL laden const handleLocationUpdate = async (idLocation, idMap, newCoords) => { try { - const result = await updateLocation(idLocation, idMap, newCoords); // Update-API + await dispatch(updateAreaThunk({ idLocation, idMap, newCoords })).unwrap(); console.log("Koordinaten erfolgreich aktualisiert:", result); } catch (error) { console.error("Fehler beim Aktualisieren der Location:", error); } }; - // Bereichs-Marker basierend auf dynamischer URL laden - const bereicheMarkers = useBereicheMarkersLayer(map, oms, bereichUrl, handleLocationUpdate); + // Areas-Marker basierend auf dynamischer URL laden + const areaMarkers = useAreaMarkersLayer(map, oms, areaUrl, handleLocationUpdate); /* - Bereichsmarker werden jetzt nur angezeigt, wenn der editMode aktiviert ist. + Areamarker werden jetzt nur angezeigt, wenn der editMode aktiviert ist. Marker werden bei deaktiviertem editMode aus der Karte entfernt. Dynamische Überwachung von Änderungen im editMode über localStorage und Event Listener implementiert. Dragging für Marker im editMode aktiviert und Z-Index angepasst. @@ -687,14 +688,14 @@ const MapComponent = ({ locations, onLocationUpdate, lineCoordinates }) => { // Entferne alle Marker aus der Karte if (!map) return; // Sicherstellen, dass map existiert - bereicheMarkers.forEach((marker) => { + areaMarkers.forEach((marker) => { if (map.hasLayer(marker)) { map.removeLayer(marker); } }); } else { // Wenn editMode aktiviert ist, füge die Marker hinzu und aktiviere Dragging - bereicheMarkers.forEach((marker) => { + areaMarkers.forEach((marker) => { if (!map.hasLayer(marker)) { marker.addTo(map); // Layer hinzufügen } @@ -702,7 +703,7 @@ const MapComponent = ({ locations, onLocationUpdate, lineCoordinates }) => { marker.setZIndexOffset(1000); // Marker nach oben setzen }); } - }, [bereicheMarkers, map]); + }, [areaMarkers, map]); //---------------------------------- useEffect(() => { diff --git a/components/uiWidgets/mapLayersControlPanel/MapLayersControlPanel.js b/components/uiWidgets/mapLayersControlPanel/MapLayersControlPanel.js index d5dfb7cca..77edb1361 100644 --- a/components/uiWidgets/mapLayersControlPanel/MapLayersControlPanel.js +++ b/components/uiWidgets/mapLayersControlPanel/MapLayersControlPanel.js @@ -224,10 +224,10 @@ function MapLayersControlPanel() { - {/* Bereiche + {/* Areas
- -
@@ -235,8 +235,8 @@ function MapLayersControlPanel() { {/* Standorte
- -
diff --git a/config/appVersion.js b/config/appVersion.js index 57d3674a7..a668d587c 100644 --- a/config/appVersion.js +++ b/config/appVersion.js @@ -1,2 +1,2 @@ // /config/appVersion -export const APP_VERSION = "1.1.182"; +export const APP_VERSION = "1.1.183"; diff --git a/cypress/e2e/contextmenuDeviceInNewTab.cy.js b/cypress/e2e/contextmenuDeviceInNewTab.cy.js index f72c16c32..e81712f1b 100644 --- a/cypress/e2e/contextmenuDeviceInNewTab.cy.js +++ b/cypress/e2e/contextmenuDeviceInNewTab.cy.js @@ -22,7 +22,7 @@ describe("contextmenuTest", () => { cy.get('img[src*="img/icons/marker-icon-20.svg"]') // Marker suchen .filter(":visible") // Nur sichtbare Marker .first() // Ersten Marker auswählen - .scrollIntoView() // Marker in den sichtbaren Bereich scrollen + .scrollIntoView() // Marker in den sichtbaren Area scrollen .should("be.visible") // Sicherstellen, dass Marker sichtbar ist .trigger("mouseover") // Mouseover simulieren .wait(500) // Wartezeit nach Mouseover diff --git a/hooks/layers/useBereicheMarkersLayer.js b/hooks/layers/useAreaMarkersLayer.js similarity index 56% rename from hooks/layers/useBereicheMarkersLayer.js rename to hooks/layers/useAreaMarkersLayer.js index 444fcd29e..853e74e4d 100644 --- a/hooks/layers/useBereicheMarkersLayer.js +++ b/hooks/layers/useAreaMarkersLayer.js @@ -1,10 +1,10 @@ -// /hooks/layers/useBereicheMarkersLayer.js +// /hooks/layers/useAreaMarkersLayer.js import { useEffect, useState, useRef } from "react"; import L from "leaflet"; import "leaflet/dist/leaflet.css"; -import { updateLocation } from "../../utils/updateBereichUtil"; +import { useDispatch } from "react-redux"; +import { updateAreaThunk } from "../../redux/thunks/database/area/updateAreaThunk"; -// Definiere ein Standard-Icon const customIcon = new L.Icon({ iconUrl: "/img/bereich.png", iconSize: [25, 41], @@ -14,22 +14,21 @@ const customIcon = new L.Icon({ shadowSize: [41, 41], }); -const useBereicheMarkersLayer = (map, oms, apiUrl) => { - const [bereicheMarkers, setBereicheMarkers] = useState([]); - const prevVisibility = useRef(null); // Referenz, um vorherige Sichtbarkeitsdaten zu speichern +const useAreaMarkersLayer = (map, oms, apiUrl, onUpdateSuccess) => { + const dispatch = useDispatch(); + const [areaMarkers, setAreaMarkers] = useState([]); + const prevVisibility = useRef(null); const updateMarkersVisibility = () => { - if (!map || bereicheMarkers.length === 0) return; + if (!map || areaMarkers.length === 0) return; const mapLayersVisibility = JSON.parse(localStorage.getItem("mapLayersVisibility")) || {}; - - const areAllLayersInvisible = Object.values(mapLayersVisibility).every((visibility) => !visibility); + const areAllLayersInvisible = Object.values(mapLayersVisibility).every((v) => !v); if (areAllLayersInvisible === prevVisibility.current) return; - prevVisibility.current = areAllLayersInvisible; - bereicheMarkers.forEach((marker) => { + areaMarkers.forEach((marker) => { if (areAllLayersInvisible) { marker.addTo(map); if (oms) oms.addMarker(marker); @@ -49,25 +48,19 @@ const useBereicheMarkersLayer = (map, oms, apiUrl) => { }; window.addEventListener("storage", handleStorageChange); - - const intervalId = setInterval(() => { - updateMarkersVisibility(); - }, 500); + const intervalId = setInterval(updateMarkersVisibility, 500); return () => { window.removeEventListener("storage", handleStorageChange); clearInterval(intervalId); }; - }, [map, bereicheMarkers, oms]); + }, [map, areaMarkers, oms]); useEffect(() => { - const fetchBereiche = async () => { + const fetchArea = async () => { try { const response = await fetch(apiUrl); - - if (!response.ok) { - throw new Error(`API-Fehler: ${response.status} ${response.statusText}`); - } + if (!response.ok) throw new Error(`API-Fehler: ${response.status} ${response.statusText}`); const data = await response.json(); @@ -78,10 +71,8 @@ const useBereicheMarkersLayer = (map, oms, apiUrl) => { }); marker.bindTooltip( - ` - Bereich: ${item.location_name}
- Standort: ${item.area_name}
- `, + `Bereich: ${item.location_name}
+ Standort: ${item.area_name}`, { permanent: false, direction: "top", @@ -92,39 +83,46 @@ const useBereicheMarkersLayer = (map, oms, apiUrl) => { marker.on("dragend", async (e) => { const { lat, lng } = e.target.getLatLng(); try { - await updateLocation(item.idLocation, item.idMaps, { x: lat, y: lng }); - console.log("Koordinaten erfolgreich aktualisiert:", { lat, lng }); + await dispatch( + updateAreaThunk({ + idLocation: item.idLocation, + idMap: item.idMaps, + newCoords: { x: lat, y: lng }, + }) + ).unwrap(); + console.log("✔️ Koordinaten erfolgreich aktualisiert:", { lat, lng }); + onUpdateSuccess?.(); // optionaler Callback } catch (error) { - console.error("Fehler beim Aktualisieren der Koordinaten:", error); + console.error("❌ Fehler beim Aktualisieren der Koordinaten:", error); } }); return marker; }); - setBereicheMarkers(markers); + setAreaMarkers(markers); } catch (error) { console.error("Fehler beim Laden der Bereiche:", error.message); - setBereicheMarkers([]); // Wichtig: Leere Liste setzen, kein reload oder Ausnahme erzeugen + setAreaMarkers([]); } }; - fetchBereiche(); - }, [apiUrl]); + fetchArea(); + }, [apiUrl, dispatch]); useEffect(() => { if (map) { - bereicheMarkers.forEach((marker) => marker.addTo(map)); + areaMarkers.forEach((marker) => marker.addTo(map)); } return () => { if (map) { - bereicheMarkers.forEach((marker) => map.removeLayer(marker)); + areaMarkers.forEach((marker) => map.removeLayer(marker)); } }; - }, [map, bereicheMarkers]); + }, [map, areaMarkers]); - return bereicheMarkers; + return areaMarkers; }; -export default useBereicheMarkersLayer; +export default useAreaMarkersLayer; diff --git a/pages/api/talas_v5_DB/bereich/readBereich.js b/pages/api/talas_v5_DB/area/readArea.js similarity index 98% rename from pages/api/talas_v5_DB/bereich/readBereich.js rename to pages/api/talas_v5_DB/area/readArea.js index b2ee9199d..a48aee764 100644 --- a/pages/api/talas_v5_DB/bereich/readBereich.js +++ b/pages/api/talas_v5_DB/area/readArea.js @@ -1,4 +1,4 @@ -// /pages/api/talas_v5_DB/bereich/readBereich.js +// /pages/api/talas_v5_DB/area/readArea.js import getPool from "../../../../utils/mysqlPool"; // Singleton-Pool importieren export default async function handler(req, res) { diff --git a/pages/api/talas_v5_DB/bereich/updateBereich.js b/pages/api/talas_v5_DB/area/updateArea.js similarity index 91% rename from pages/api/talas_v5_DB/bereich/updateBereich.js rename to pages/api/talas_v5_DB/area/updateArea.js index 2c45ee7b0..8098c4a87 100644 --- a/pages/api/talas_v5_DB/bereich/updateBereich.js +++ b/pages/api/talas_v5_DB/area/updateArea.js @@ -1,4 +1,4 @@ -// /pages/api/talas_v5_DB/bereich/updateBereich.js +// /pages/api/talas_v5_DB/area/updateArea.js import getPool from "../../../../utils/mysqlPool"; export default async function handler(req, res) { @@ -37,16 +37,12 @@ export default async function handler(req, res) { return res.status(500).json({ error: "Interner Serverfehler beim Aktualisieren der Koordinaten" }); } finally { if (connection) { - connection.release(); + connection.release(); } // Sicherheitshalber eine Standardantwort senden, falls keine vorherige Antwort existiert if (!res.headersSent) { - res.status(500).json({ error: "Keine Antwort vom Server" }); + res.status(500).json({ error: "Keine Antwort vom Server" }); } + } } - -} - - - diff --git a/redux/slices/database/area/updateAreaSlice.js b/redux/slices/database/area/updateAreaSlice.js new file mode 100644 index 000000000..c56797702 --- /dev/null +++ b/redux/slices/database/area/updateAreaSlice.js @@ -0,0 +1,34 @@ +// /redux/slices/database/area/updateAreaSlice.js + +import { createSlice } from "@reduxjs/toolkit"; +import { updateAreaThunk } from "../../../thunks/database/area/updateAreaThunk"; + +const updateAreaSlice = createSlice({ + name: "updateArea", + initialState: { + status: "idle", + error: null, + }, + reducers: { + resetUpdateAreaStatus: (state) => { + state.status = "idle"; + state.error = null; + }, + }, + extraReducers: (builder) => { + builder + .addCase(updateAreaThunk.pending, (state) => { + state.status = "loading"; + }) + .addCase(updateAreaThunk.fulfilled, (state) => { + state.status = "succeeded"; + }) + .addCase(updateAreaThunk.rejected, (state, action) => { + state.status = "failed"; + state.error = action.error.message; + }); + }, +}); + +export const { resetUpdateAreaStatus } = updateAreaSlice.actions; +export default updateAreaSlice.reducer; diff --git a/redux/thunks/database/area/updateAreaThunk.js b/redux/thunks/database/area/updateAreaThunk.js new file mode 100644 index 000000000..0fb027097 --- /dev/null +++ b/redux/thunks/database/area/updateAreaThunk.js @@ -0,0 +1,8 @@ +// /redux/thunks/database/area/updateAreaThunk.js + +import { createAsyncThunk } from "@reduxjs/toolkit"; +import { updateAreaService } from "../../../../services/database/area/updateAreaService"; + +export const updateAreaThunk = createAsyncThunk("area/update", async (payload) => { + return await updateAreaService(payload); +}); diff --git a/services/database/area/updateAreaService.js b/services/database/area/updateAreaService.js new file mode 100644 index 000000000..d53ff1774 --- /dev/null +++ b/services/database/area/updateAreaService.js @@ -0,0 +1,23 @@ +// /services/database/area/updateAreaService.js + +export const updateAreaService = async ({ idLocation, idMap, newCoords }) => { + const response = await fetch("/api/talas_v5_DB/area/updateArea", { + method: "PUT", + headers: { + "Content-Type": "application/json", + }, + body: JSON.stringify({ + idLocation, + idMap, + x: newCoords.x, + y: newCoords.y, + }), + }); + + if (!response.ok) { + const error = await response.json(); + throw new Error(error.message || "Fehler beim Aktualisieren der Fläche"); + } + + return await response.json(); +}; diff --git a/utils/updateBereichUtil.js b/utils/updateBereichUtil.js deleted file mode 100644 index 43ce30a0a..000000000 --- a/utils/updateBereichUtil.js +++ /dev/null @@ -1,27 +0,0 @@ -export const updateLocation = async (idLocation, idMap, newCoords) => { - try { - const response = await fetch("/api/talas_v5_DB/bereich/updateBereich", { - method: "PUT", - headers: { - "Content-Type": "application/json", - }, - body: JSON.stringify({ - idLocation, - idMap, - x: newCoords.x, - y: newCoords.y, - }), - }); - - if (!response.ok) { - throw new Error("Fehler beim Aktualisieren der Koordinaten"); - } - - const data = await response.json(); - console.log("Koordinaten erfolgreich aktualisiert:", data); - return data; - } catch (error) { - console.error("Fehler beim Aufruf von updateLocation:", error); - throw error; - } -};