refactor(area): Bereichsaktualisierung von util auf Redux umgestellt

- updateAreaUtil.js entfernt
- updateAreaService, updateAreaThunk, updateAreaSlice eingeführt
- useAreaMarkersLayer nutzt jetzt updateAreaThunk
- MapComponent umgestellt auf Redux-Dispatch
- Version erhöht auf 1.1.182
This commit is contained in:
ISA
2025-05-27 07:55:12 +02:00
parent b6acf719ff
commit 44cb27ce0f
12 changed files with 159 additions and 94 deletions

View File

@@ -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 [1.1.181] 2025-05-26
♻️ Refactor ♻️ Refactor
setupPolylines.js von direktem fetch() auf sauberes Redux-Muster umgestellt: setupPolylines.js von direktem fetch() auf sauberes Redux-Muster umgestellt:

View File

@@ -18,13 +18,13 @@ import * as layers from "../../config/layers.js";
import addItemsToMapContextMenu from "../contextmenu/useMapContextMenu.js"; import addItemsToMapContextMenu from "../contextmenu/useMapContextMenu.js";
import useGmaMarkersLayer from "../../hooks/layers/useGmaMarkersLayer.js"; import useGmaMarkersLayer from "../../hooks/layers/useGmaMarkersLayer.js";
import useSmsfunkmodemMarkersLayer from "../../hooks/layers/useSmsfunkmodemMarkersLayer.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 { setupPolylines } from "../../utils/polylines/setupPolylines.js";
import { setupPOIs } from "../../utils/setupPOIs.js"; import { setupPOIs } from "../../utils/setupPOIs.js";
import useLayerVisibility from "../../hooks/useLayerVisibility.js"; import useLayerVisibility from "../../hooks/useLayerVisibility.js";
import useLineData from "../../hooks/useLineData.js"; import useLineData from "../../hooks/useLineData.js";
import { useMapComponentState } from "../../hooks/useMapComponentState.js"; import { useMapComponentState } from "../../hooks/useMapComponentState.js";
import { updateLocation } from "../../utils/updateBereichUtil.js";
import CoordinatePopup from "../contextmenu/CoordinatePopup.js"; import CoordinatePopup from "../contextmenu/CoordinatePopup.js";
//----------Ui Widgets---------------- //----------Ui Widgets----------------
import MapLayersControlPanel from "../uiWidgets/mapLayersControlPanel/MapLayersControlPanel.js"; 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 { fetchUserRightsThunk } from "../../redux/thunks/webservice/fetchUserRightsThunk";
import { fetchPoiIconsDataThunk } from "../../redux/thunks/database/pois/fetchPoiIconsDataThunk.js"; import { fetchPoiIconsDataThunk } from "../../redux/thunks/database/pois/fetchPoiIconsDataThunk.js";
import { fetchPoiTypThunk } from "../../redux/thunks/database/pois/fetchPoiTypThunk.js"; import { fetchPoiTypThunk } from "../../redux/thunks/database/pois/fetchPoiTypThunk.js";
import { updateAreaThunk } from "../../redux/thunks/database/area/updateAreaThunk";
//----------------------------------------------------------------------------------------------------- //-----------------------------------------------------------------------------------------------------
const MapComponent = ({ locations, onLocationUpdate, lineCoordinates }) => { const MapComponent = ({ locations, onLocationUpdate, lineCoordinates }) => {
@@ -463,7 +464,7 @@ const MapComponent = ({ locations, onLocationUpdate, lineCoordinates }) => {
} }
}, [map]); }, [map]);
//-------------------------------------------- //--------------------------------------------
// Bereich in DataSheet ->dropdownmenu // Area in DataSheet ->dropdownmenu
useEffect(() => { useEffect(() => {
//console.log("🔍 GisStationsStaticDistrict Inhalt:", GisStationsStaticDistrict); //console.log("🔍 GisStationsStaticDistrict Inhalt:", GisStationsStaticDistrict);
@@ -653,28 +654,28 @@ const MapComponent = ({ locations, onLocationUpdate, lineCoordinates }) => {
//--------------------------------------- //---------------------------------------
// Initialisiere Leaflet-Karte // Initialisiere Leaflet-Karte
// Rufe useBereicheMarkersLayer direkt auf // Rufe useAreaMarkersLayer direkt auf
//const [bereichUrl, setBereichUrl] = useState(null); //const [areaUrl, setAreaUrl] = useState(null);
const urlParams = new URLSearchParams(window.location.search); // URL-Parameter parsen const urlParams = new URLSearchParams(window.location.search); // URL-Parameter parsen
const mValue = urlParams.get("m"); // Wert von "m" holen const mValue = urlParams.get("m"); // Wert von "m" holen
const hostname = window.location.hostname; // Dynamischer Hostname const hostname = window.location.hostname; // Dynamischer Hostname
const port = 3000; // Definiere den gewünschten Port 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) => { const handleLocationUpdate = async (idLocation, idMap, newCoords) => {
try { try {
const result = await updateLocation(idLocation, idMap, newCoords); // Update-API await dispatch(updateAreaThunk({ idLocation, idMap, newCoords })).unwrap();
console.log("Koordinaten erfolgreich aktualisiert:", result); console.log("Koordinaten erfolgreich aktualisiert:", result);
} catch (error) { } catch (error) {
console.error("Fehler beim Aktualisieren der Location:", error); console.error("Fehler beim Aktualisieren der Location:", error);
} }
}; };
// Bereichs-Marker basierend auf dynamischer URL laden // Areas-Marker basierend auf dynamischer URL laden
const bereicheMarkers = useBereicheMarkersLayer(map, oms, bereichUrl, handleLocationUpdate); 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. Marker werden bei deaktiviertem editMode aus der Karte entfernt.
Dynamische Überwachung von Änderungen im editMode über localStorage und Event Listener implementiert. Dynamische Überwachung von Änderungen im editMode über localStorage und Event Listener implementiert.
Dragging für Marker im editMode aktiviert und Z-Index angepasst. 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 // Entferne alle Marker aus der Karte
if (!map) return; // Sicherstellen, dass map existiert if (!map) return; // Sicherstellen, dass map existiert
bereicheMarkers.forEach((marker) => { areaMarkers.forEach((marker) => {
if (map.hasLayer(marker)) { if (map.hasLayer(marker)) {
map.removeLayer(marker); map.removeLayer(marker);
} }
}); });
} else { } else {
// Wenn editMode aktiviert ist, füge die Marker hinzu und aktiviere Dragging // Wenn editMode aktiviert ist, füge die Marker hinzu und aktiviere Dragging
bereicheMarkers.forEach((marker) => { areaMarkers.forEach((marker) => {
if (!map.hasLayer(marker)) { if (!map.hasLayer(marker)) {
marker.addTo(map); // Layer hinzufügen marker.addTo(map); // Layer hinzufügen
} }
@@ -702,7 +703,7 @@ const MapComponent = ({ locations, onLocationUpdate, lineCoordinates }) => {
marker.setZIndexOffset(1000); // Marker nach oben setzen marker.setZIndexOffset(1000); // Marker nach oben setzen
}); });
} }
}, [bereicheMarkers, map]); }, [areaMarkers, map]);
//---------------------------------- //----------------------------------
useEffect(() => { useEffect(() => {

View File

@@ -224,10 +224,10 @@ function MapLayersControlPanel() {
</label> </label>
</div> </div>
{/* Bereiche {/* Areas
<div className="flex items-center"> <div className="flex items-center">
<input type="checkbox" checked={bereicheVisible} onChange={handleBereicheCheckboxChange} id="bereiche-checkbox" /> <input type="checkbox" checked={areaVisible} onChange={handleAreaCheckboxChange} id="area-checkbox" />
<label htmlFor="bereiche-checkbox" className="text-sm ml-2"> <label htmlFor="area-checkbox" className="text-sm ml-2">
Bereiche Bereiche
</label> </label>
</div> </div>
@@ -235,8 +235,8 @@ function MapLayersControlPanel() {
{/* Standorte {/* Standorte
<div className="flex items-center"> <div className="flex items-center">
<input type="checkbox" checked={standordVisible} onChange={handleStandorteCheckboxChange} id="bereiche-checkbox" /> <input type="checkbox" checked={standordVisible} onChange={handleStandorteCheckboxChange} id="area-checkbox" />
<label htmlFor="bereiche-checkbox" className="text-sm ml-2"> <label htmlFor="area-checkbox" className="text-sm ml-2">
Standorte Standorte
</label> </label>
</div> </div>

View File

@@ -1,2 +1,2 @@
// /config/appVersion // /config/appVersion
export const APP_VERSION = "1.1.182"; export const APP_VERSION = "1.1.183";

View File

@@ -22,7 +22,7 @@ describe("contextmenuTest", () => {
cy.get('img[src*="img/icons/marker-icon-20.svg"]') // Marker suchen cy.get('img[src*="img/icons/marker-icon-20.svg"]') // Marker suchen
.filter(":visible") // Nur sichtbare Marker .filter(":visible") // Nur sichtbare Marker
.first() // Ersten Marker auswählen .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 .should("be.visible") // Sicherstellen, dass Marker sichtbar ist
.trigger("mouseover") // Mouseover simulieren .trigger("mouseover") // Mouseover simulieren
.wait(500) // Wartezeit nach Mouseover .wait(500) // Wartezeit nach Mouseover

View File

@@ -1,10 +1,10 @@
// /hooks/layers/useBereicheMarkersLayer.js // /hooks/layers/useAreaMarkersLayer.js
import { useEffect, useState, useRef } from "react"; import { useEffect, useState, useRef } from "react";
import L from "leaflet"; import L from "leaflet";
import "leaflet/dist/leaflet.css"; 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({ const customIcon = new L.Icon({
iconUrl: "/img/bereich.png", iconUrl: "/img/bereich.png",
iconSize: [25, 41], iconSize: [25, 41],
@@ -14,22 +14,21 @@ const customIcon = new L.Icon({
shadowSize: [41, 41], shadowSize: [41, 41],
}); });
const useBereicheMarkersLayer = (map, oms, apiUrl) => { const useAreaMarkersLayer = (map, oms, apiUrl, onUpdateSuccess) => {
const [bereicheMarkers, setBereicheMarkers] = useState([]); const dispatch = useDispatch();
const prevVisibility = useRef(null); // Referenz, um vorherige Sichtbarkeitsdaten zu speichern const [areaMarkers, setAreaMarkers] = useState([]);
const prevVisibility = useRef(null);
const updateMarkersVisibility = () => { const updateMarkersVisibility = () => {
if (!map || bereicheMarkers.length === 0) return; if (!map || areaMarkers.length === 0) return;
const mapLayersVisibility = JSON.parse(localStorage.getItem("mapLayersVisibility")) || {}; const mapLayersVisibility = JSON.parse(localStorage.getItem("mapLayersVisibility")) || {};
const areAllLayersInvisible = Object.values(mapLayersVisibility).every((v) => !v);
const areAllLayersInvisible = Object.values(mapLayersVisibility).every((visibility) => !visibility);
if (areAllLayersInvisible === prevVisibility.current) return; if (areAllLayersInvisible === prevVisibility.current) return;
prevVisibility.current = areAllLayersInvisible; prevVisibility.current = areAllLayersInvisible;
bereicheMarkers.forEach((marker) => { areaMarkers.forEach((marker) => {
if (areAllLayersInvisible) { if (areAllLayersInvisible) {
marker.addTo(map); marker.addTo(map);
if (oms) oms.addMarker(marker); if (oms) oms.addMarker(marker);
@@ -49,25 +48,19 @@ const useBereicheMarkersLayer = (map, oms, apiUrl) => {
}; };
window.addEventListener("storage", handleStorageChange); window.addEventListener("storage", handleStorageChange);
const intervalId = setInterval(updateMarkersVisibility, 500);
const intervalId = setInterval(() => {
updateMarkersVisibility();
}, 500);
return () => { return () => {
window.removeEventListener("storage", handleStorageChange); window.removeEventListener("storage", handleStorageChange);
clearInterval(intervalId); clearInterval(intervalId);
}; };
}, [map, bereicheMarkers, oms]); }, [map, areaMarkers, oms]);
useEffect(() => { useEffect(() => {
const fetchBereiche = async () => { const fetchArea = async () => {
try { try {
const response = await fetch(apiUrl); 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(); const data = await response.json();
@@ -78,10 +71,8 @@ const useBereicheMarkersLayer = (map, oms, apiUrl) => {
}); });
marker.bindTooltip( marker.bindTooltip(
` `<strong>Bereich:</strong> ${item.location_name} <br />
<strong>Bereich:</strong> ${item.location_name} <br /> <strong>Standort:</strong> ${item.area_name}`,
<strong>Standort:</strong> ${item.area_name} <br />
`,
{ {
permanent: false, permanent: false,
direction: "top", direction: "top",
@@ -92,39 +83,46 @@ const useBereicheMarkersLayer = (map, oms, apiUrl) => {
marker.on("dragend", async (e) => { marker.on("dragend", async (e) => {
const { lat, lng } = e.target.getLatLng(); const { lat, lng } = e.target.getLatLng();
try { try {
await updateLocation(item.idLocation, item.idMaps, { x: lat, y: lng }); await dispatch(
console.log("Koordinaten erfolgreich aktualisiert:", { lat, lng }); 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) { } catch (error) {
console.error("Fehler beim Aktualisieren der Koordinaten:", error); console.error("Fehler beim Aktualisieren der Koordinaten:", error);
} }
}); });
return marker; return marker;
}); });
setBereicheMarkers(markers); setAreaMarkers(markers);
} catch (error) { } catch (error) {
console.error("Fehler beim Laden der Bereiche:", error.message); console.error("Fehler beim Laden der Bereiche:", error.message);
setBereicheMarkers([]); // Wichtig: Leere Liste setzen, kein reload oder Ausnahme erzeugen setAreaMarkers([]);
} }
}; };
fetchBereiche(); fetchArea();
}, [apiUrl]); }, [apiUrl, dispatch]);
useEffect(() => { useEffect(() => {
if (map) { if (map) {
bereicheMarkers.forEach((marker) => marker.addTo(map)); areaMarkers.forEach((marker) => marker.addTo(map));
} }
return () => { return () => {
if (map) { 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;

View File

@@ -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 import getPool from "../../../../utils/mysqlPool"; // Singleton-Pool importieren
export default async function handler(req, res) { export default async function handler(req, res) {

View File

@@ -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"; import getPool from "../../../../utils/mysqlPool";
export default async function handler(req, res) { export default async function handler(req, res) {
@@ -45,8 +45,4 @@ export default async function handler(req, res) {
res.status(500).json({ error: "Keine Antwort vom Server" }); res.status(500).json({ error: "Keine Antwort vom Server" });
} }
} }
} }

View File

@@ -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;

View File

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

View File

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

View File

@@ -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;
}
};