feat: WebSocket-Integration mit UI-Reaktivierung für GisStationsStaticDistrict

- WebSocket-Trigger implementiert, der `fetchGisStationsStaticDistrictThunk` ausführt.
- Trigger-Mechanismus über `useState` (`triggerUpdate`) sorgt für gezielten UI-Re-Render.
- Problem gelöst, dass Redux-Store zwar neue Daten enthielt, aber die UI nicht aktualisiert wurde.
- MapComponent.js und useDynamicDeviceLayers.js entsprechend angepasst.
This commit is contained in:
Ismail Ali
2025-06-09 00:24:33 +02:00
parent fbffc82e1b
commit b067a4c97e
12 changed files with 113 additions and 66 deletions

View File

@@ -82,11 +82,14 @@ import { cleanupMarkers } from "@/utils/common/cleanupMarkers";
import { monitorHeapAndReload } from "@/utils/common/monitorMemory"; import { monitorHeapAndReload } from "@/utils/common/monitorMemory";
import { monitorHeapWithRedux } from "@/utils/common/monitorMemory"; import { monitorHeapWithRedux } from "@/utils/common/monitorMemory";
import { io } from "socket.io-client"; import { io } from "socket.io-client";
import { setGisStationsStaticDistrict } from "@/redux/slices/webservice/gisStationsStaticDistrictSlice.js";
//----------------------------------------------------------------------------------------------------- //-----------------------------------------------------------------------------------------------------
const MapComponent = ({ locations, onLocationUpdate, lineCoordinates }) => { const MapComponent = ({ locations, onLocationUpdate, lineCoordinates }) => {
//------------------------------- //-------------------------------
const dispatch = useDispatch(); const dispatch = useDispatch();
// useDataUpdater(); // useDataUpdater();
const [triggerUpdate, setTriggerUpdate] = useState(false);
const countdown = useSelector(state => state.polylineContextMenu.countdown); const countdown = useSelector(state => state.polylineContextMenu.countdown);
const countdownActive = useSelector(state => state.polylineContextMenu.countdownActive); const countdownActive = useSelector(state => state.polylineContextMenu.countdownActive);
@@ -818,9 +821,11 @@ const MapComponent = ({ locations, onLocationUpdate, lineCoordinates }) => {
socket.on("GisStationsMeasurementsUpdated", data => { socket.on("GisStationsMeasurementsUpdated", data => {
dispatch(fetchGisStationsMeasurementsThunk(data)); dispatch(fetchGisStationsMeasurementsThunk(data));
}); });
socket.on("GisStationsStaticDistrictUpdated", () => {
socket.on("GisStationsStaticDistrictUpdated", data => { console.log("🛰 WebSocket-Trigger empfangen → Thunk wird erneut ausgeführt");
dispatch(fetchGisStationsStaticDistrictThunk(data)); dispatch(fetchGisStationsStaticDistrictThunk());
console.log("🛰 WebSocket-Trigger empfangen → Trigger wird gesetzt");
setTriggerUpdate(prev => !prev); // Trigger toggeln
}); });
socket.on("GisStationsStatusDistrictUpdated", data => { socket.on("GisStationsStatusDistrictUpdated", data => {
@@ -835,7 +840,12 @@ const MapComponent = ({ locations, onLocationUpdate, lineCoordinates }) => {
socket.disconnect(); socket.disconnect();
}; };
}, [dispatch]); }, [dispatch]);
//--------------------------------------------
useEffect(() => {
console.log("📦 Neue Daten empfangen:", GisStationsStaticDistrict);
}, [GisStationsStaticDistrict]);
const { Points = [] } = useSelector(selectGisStationsStaticDistrict);
useEffect(() => {}, [triggerUpdate]);
//--------------------------------------------- //---------------------------------------------
//-------------------------------------------- //--------------------------------------------
return ( return (

View File

@@ -178,6 +178,19 @@ function MapLayersControlPanel() {
console.log("📌 stationListing aktualisiert:", filteredAreas); console.log("📌 stationListing aktualisiert:", filteredAreas);
} }
}, [GisStationsStaticDistrict, GisSystemStatic]); }, [GisStationsStaticDistrict, GisSystemStatic]);
//---------------------------
useEffect(() => {
const next = (GisStationsStaticDistrict.Points || []).map(p => p.Area_Name).join("|");
const current = stationListing.map(s => s.name).join("|");
if (next !== current) {
setStationListing(
GisStationsStaticDistrict.Points.map((area, index) => ({
id: index + 1,
name: area.Area_Name,
}))
);
}
}, [GisStationsStaticDistrict]);
//--------------------------- //---------------------------
return ( return (
@@ -194,9 +207,9 @@ function MapLayersControlPanel() {
style={{ minWidth: "150px", maxWidth: "200px" }} style={{ minWidth: "150px", maxWidth: "200px" }}
> >
<option value="Station wählen">Station wählen</option> <option value="Station wählen">Station wählen</option>
{stationListing.map(station => ( {(GisStationsStaticDistrict.Points || []).map((item, index) => (
<option key={station.id} value={station.id}> <option key={index} value={item.IdLD}>
{station.name} {item.Area_Name}
</option> </option>
))} ))}
</select> </select>

View File

@@ -1,2 +1,2 @@
// /config/appVersion // /config/appVersion
export const APP_VERSION = "1.1.247"; export const APP_VERSION = "1.1.248";

View File

@@ -12,8 +12,9 @@ import { fetchGisSystemStaticThunk } from "../redux/thunks/webservice/fetchGisSy
const REFRESH_INTERVAL = parseInt(process.env.NEXT_PUBLIC_REFRESH_INTERVAL || "10000"); const REFRESH_INTERVAL = parseInt(process.env.NEXT_PUBLIC_REFRESH_INTERVAL || "10000");
export default function useDataUpdater() { export const useDataUpdater = () => {
const dispatch = useDispatch(); const dispatch = useDispatch();
useEffect(() => { useEffect(() => {
const updateAll = () => { const updateAll = () => {
dispatch(fetchGisLinesStatusThunk()); dispatch(fetchGisLinesStatusThunk());
@@ -23,9 +24,17 @@ export default function useDataUpdater() {
dispatch(fetchGisSystemStaticThunk()); dispatch(fetchGisSystemStaticThunk());
}; };
updateAll(); // direkt initial einmal laden updateAll();
const interval = setInterval(updateAll, REFRESH_INTERVAL); const interval = setInterval(updateAll, 10000);
return () => clearInterval(interval);
return () => clearInterval(interval); // Cleanup
}, [dispatch]); }, [dispatch]);
} };
// Das ist eine normale Funktion
export const triggerDataUpdate = dispatch => {
dispatch(fetchGisLinesStatusThunk());
dispatch(fetchGisStationsMeasurementsThunk());
dispatch(fetchGisStationsStaticDistrictThunk());
dispatch(fetchGisStationsStatusDistrictThunk());
dispatch(fetchGisSystemStaticThunk());
};

View File

@@ -4,6 +4,8 @@ import L from "leaflet";
import { createAndSetDevices } from "../utils/devices/createAndSetDevices"; import { createAndSetDevices } from "../utils/devices/createAndSetDevices";
import { checkOverlappingMarkers } from "../utils/mapUtils"; import { checkOverlappingMarkers } from "../utils/mapUtils";
import plusRoundIcon from "@/components/icons/devices/overlapping/PlusRoundIcon"; import plusRoundIcon from "@/components/icons/devices/overlapping/PlusRoundIcon";
import { useSelector } from "react-redux";
import { selectGisStationsStaticDistrict } from "@/redux/slices/webservice/gisStationsStaticDistrictSlice.js";
/** /**
* Dynamisch GIS-System-Marker erstellen & Sichtbarkeit steuern. * Dynamisch GIS-System-Marker erstellen & Sichtbarkeit steuern.
@@ -14,6 +16,7 @@ import plusRoundIcon from "@/components/icons/devices/overlapping/PlusRoundIcon"
* @returns {{ markerStates, layerRefs }} Alle Marker und Referenzen * @returns {{ markerStates, layerRefs }} Alle Marker und Referenzen
*/ */
const useDynamicDeviceLayers = (map, GisSystemStatic, mapLayersVisibility, priorityConfig, oms) => { const useDynamicDeviceLayers = (map, GisSystemStatic, mapLayersVisibility, priorityConfig, oms) => {
const staticDistrictData = useSelector(selectGisStationsStaticDistrict);
const [markerStates, setMarkerStates] = useState({}); const [markerStates, setMarkerStates] = useState({});
const layerRefs = useRef({}); const layerRefs = useRef({});
@@ -54,7 +57,7 @@ const useDynamicDeviceLayers = (map, GisSystemStatic, mapLayersVisibility, prior
oms oms
); );
}); });
}, [map, GisSystemStatic, priorityConfig]); }, [map, GisSystemStatic, priorityConfig, staticDistrictData]);
// Sichtbarkeit nach Redux-Status steuern // Sichtbarkeit nach Redux-Status steuern
useEffect(() => { useEffect(() => {

View File

@@ -11,10 +11,23 @@ const slice = createSlice({
status: "idle", status: "idle",
error: null, error: null,
}, },
reducers: {}, reducers: {
extraReducers: (builder) => { setGisStationsStaticDistrict: (state, action) => {
// Stelle sicher, dass die Struktur immer gleich bleibt
if (Array.isArray(action.payload)) {
state.data = { Points: action.payload };
} else if (action.payload?.Points) {
state.data = { Points: action.payload.Points };
} else {
state.data = { Points: [] };
}
state.status = "succeeded";
state.lastUpdated = Date.now();
},
}, // Ende der reducers
extraReducers: builder => {
builder builder
.addCase(fetchGisStationsStaticDistrictThunk.pending, (state) => { .addCase(fetchGisStationsStaticDistrictThunk.pending, state => {
state.status = "loading"; state.status = "loading";
}) })
.addCase(fetchGisStationsStaticDistrictThunk.fulfilled, (state, action) => { .addCase(fetchGisStationsStaticDistrictThunk.fulfilled, (state, action) => {
@@ -28,5 +41,6 @@ const slice = createSlice({
}, },
}); });
export const selectGisStationsStaticDistrict = (state) => state.gisStationsStaticDistrict.data; export const selectGisStationsStaticDistrict = state => state.gisStationsStaticDistrict.data;
export default slice.reducer; export default slice.reducer;
export const { setGisStationsStaticDistrict } = slice.actions;

View File

@@ -35,7 +35,6 @@ app.prepare().then(() => {
io.on("connection", socket => { io.on("connection", socket => {
const { m: idMap, u: idUser, mode } = socket.handshake.query; const { m: idMap, u: idUser, mode } = socket.handshake.query;
const isLiveMode = mode === "live";
console.log(`🔌 WebSocket verbunden (idMap=${idMap}, idUser=${idUser}, mode=${mode})`); console.log(`🔌 WebSocket verbunden (idMap=${idMap}, idUser=${idUser}, mode=${mode})`);
const endpoints = [ const endpoints = [
@@ -80,7 +79,7 @@ app.prepare().then(() => {
const jsonStr = fs.readFileSync(mockPath, "utf-8"); const jsonStr = fs.readFileSync(mockPath, "utf-8");
const json = JSON.parse(jsonStr); const json = JSON.parse(jsonStr);
statis = extractData(json, name); statis = extractData(json, name);
console.log(`🧪 [Mock] ${name}`); //console.log(`🧪 [Mock] ${name}`);
} else { } else {
const fetchUrl = `http://localhost/talas5/ClientData/${getUrl()}`; const fetchUrl = `http://localhost/talas5/ClientData/${getUrl()}`;
const res = await fetch(fetchUrl); const res = await fetch(fetchUrl);
@@ -106,7 +105,7 @@ app.prepare().then(() => {
console.log(`✅ Änderung bei ${name} erkannt → gesendet`); console.log(`✅ Änderung bei ${name} erkannt → gesendet`);
writeJsonFile(`${name}.json`, statis); writeJsonFile(`${name}.json`, statis);
} else { } else {
console.log(`🔁 ${name}: Keine Änderung`); // console.log(`🔁 ${name}: Keine Änderung`);
} }
} catch (error) { } catch (error) {
console.error(`❌ Fehler bei ${name}:`, error.message); console.error(`❌ Fehler bei ${name}:`, error.message);
@@ -114,14 +113,13 @@ app.prepare().then(() => {
} }
}; };
if (isLiveMode) { // fetchData immer ausführen unabhängig vom Modus
fetchData(); fetchData();
const interval = setInterval(fetchData, 5000); const interval = setInterval(fetchData, 5000);
socket.on("disconnect", () => { socket.on("disconnect", () => {
clearInterval(interval); clearInterval(interval);
console.log("❌ WebSocket getrennt"); console.log("❌ WebSocket getrennt");
}); });
}
}); });
server.listen(PORT, () => { server.listen(PORT, () => {

View File

@@ -3,9 +3,9 @@
"IdLD": 50922, "IdLD": 50922,
"Modul": 8, "Modul": 8,
"DpName": "KUE08_Messwertalarm", "DpName": "KUE08_Messwertalarm",
"ModulName": "Test12", "ModulName": "Test9",
"ModulTyp": "Kü705-FO", "ModulTyp": "Kü705-FO",
"Message": "KÜG 08: Überspannung gehend", "Message": "KÜG 08: Überspannung gehend",
"Level": 3, "Level": 3,
"PrioColor": "#FFFF00", "PrioColor": "#FFFF00",
"PrioName": "minor", "PrioName": "minor",
@@ -17,7 +17,7 @@
"DpName": "KUE02_Aderbruch", "DpName": "KUE02_Aderbruch",
"ModulName": "Kue 2", "ModulName": "Kue 2",
"ModulTyp": "Kü705-FO", "ModulTyp": "Kü705-FO",
"Message": "KÜG 02: Aderbruch kommend", "Message": "KÜG 02: Aderbruch kommend",
"Level": 1, "Level": 1,
"PrioColor": "#FF0000", "PrioColor": "#FF0000",
"PrioName": "critical", "PrioName": "critical",
@@ -29,7 +29,7 @@
"DpName": "KUE03_Aderbruch", "DpName": "KUE03_Aderbruch",
"ModulName": "Kue 3", "ModulName": "Kue 3",
"ModulTyp": "Kü705-FO", "ModulTyp": "Kü705-FO",
"Message": "KÜG 03: Aderbruch kommend", "Message": "KÜG 03: Aderbruch kommend",
"Level": 1, "Level": 1,
"PrioColor": "#FF0000", "PrioColor": "#FF0000",
"PrioName": "critical", "PrioName": "critical",
@@ -1227,7 +1227,7 @@
"IdLD": 50922, "IdLD": 50922,
"Modul": 8, "Modul": 8,
"DpName": "KUE08_Messwert", "DpName": "KUE08_Messwert",
"ModulName": "Test12", "ModulName": "Test8",
"ModulTyp": "Kü705-FO", "ModulTyp": "Kü705-FO",
"Message": "?", "Message": "?",
"Level": -1, "Level": -1,
@@ -1335,7 +1335,7 @@
"IdLD": 50922, "IdLD": 50922,
"Modul": 8, "Modul": 8,
"DpName": "KUE08_Schleifenwert", "DpName": "KUE08_Schleifenwert",
"ModulName": "Test12", "ModulName": "Test8",
"ModulTyp": "Kü705-FO", "ModulTyp": "Kü705-FO",
"Message": "?", "Message": "?",
"Level": -1, "Level": -1,

View File

@@ -4,7 +4,7 @@
"IdL": 24101, "IdL": 24101,
"IdDP": 3, "IdDP": 3,
"Na": "FBT", "Na": "FBT",
"Val": "6", "Val": "7",
"Unit": "°C", "Unit": "°C",
"Gr": "GMA", "Gr": "GMA",
"Area_Name": "Rastede" "Area_Name": "Rastede"
@@ -105,7 +105,7 @@
"IdDP": 7, "IdDP": 7,
"Na": "WR", "Na": "WR",
"Val": "180", "Val": "180",
"Unit": "°", "Unit": "°",
"Gr": "GMA", "Gr": "GMA",
"Area_Name": "Rastede" "Area_Name": "Rastede"
} }

View File

@@ -1,6 +1,6 @@
[ [
{ {
"LD_Name": "CPL Ismael", "LD_Name": "CPL Ismail31",
"IdLD": 50922, "IdLD": 50922,
"Device": "CPL V3.5 mit 24 Kü", "Device": "CPL V3.5 mit 24 Kü",
"Link": "cpl.aspx?ver=35&kue=24&id=50922", "Link": "cpl.aspx?ver=35&kue=24&id=50922",
@@ -10,8 +10,8 @@
"Area_Name": "Rastede", "Area_Name": "Rastede",
"Area_Short": "", "Area_Short": "",
"IdArea": 20998, "IdArea": 20998,
"X": 53.243954, "X": 53.242157,
"Y": 8.160439, "Y": 8.160353,
"Icon": 20, "Icon": 20,
"System": 1, "System": 1,
"Active": 1 "Active": 1
@@ -27,8 +27,8 @@
"Area_Name": "Rastede", "Area_Name": "Rastede",
"Area_Short": "", "Area_Short": "",
"IdArea": 20998, "IdArea": 20998,
"X": 53.243954, "X": 53.242157,
"Y": 8.160439, "Y": 8.160353,
"Icon": 12, "Icon": 12,
"System": 5, "System": 5,
"Active": 1 "Active": 1
@@ -44,8 +44,8 @@
"Area_Name": "Rastede", "Area_Name": "Rastede",
"Area_Short": "", "Area_Short": "",
"IdArea": 20998, "IdArea": 20998,
"X": 53.243954, "X": 53.242157,
"Y": 8.160439, "Y": 8.160353,
"Icon": 21, "Icon": 21,
"System": 6, "System": 6,
"Active": 1 "Active": 1
@@ -61,8 +61,8 @@
"Area_Name": "Rastede", "Area_Name": "Rastede",
"Area_Short": "", "Area_Short": "",
"IdArea": 20998, "IdArea": 20998,
"X": 53.243954, "X": 53.242157,
"Y": 8.160439, "Y": 8.160353,
"Icon": 1, "Icon": 1,
"System": 11, "System": 11,
"Active": 1 "Active": 1
@@ -78,8 +78,8 @@
"Area_Name": "Rastede", "Area_Name": "Rastede",
"Area_Short": "", "Area_Short": "",
"IdArea": 20998, "IdArea": 20998,
"X": 53.243954, "X": 53.242157,
"Y": 8.160439, "Y": 8.160353,
"Icon": 12, "Icon": 12,
"System": 111, "System": 111,
"Active": 1 "Active": 1
@@ -95,8 +95,8 @@
"Area_Name": "Rastede", "Area_Name": "Rastede",
"Area_Short": "", "Area_Short": "",
"IdArea": 20998, "IdArea": 20998,
"X": 53.243954, "X": 53.242157,
"Y": 8.160439, "Y": 8.160353,
"Icon": 20, "Icon": 20,
"System": 1, "System": 1,
"Active": 1 "Active": 1
@@ -112,8 +112,8 @@
"Area_Name": "Rastede", "Area_Name": "Rastede",
"Area_Short": "", "Area_Short": "",
"IdArea": 20998, "IdArea": 20998,
"X": 53.243954, "X": 53.242157,
"Y": 8.160439, "Y": 8.160353,
"Icon": 9, "Icon": 9,
"System": 7, "System": 7,
"Active": 1 "Active": 1
@@ -129,8 +129,8 @@
"Area_Name": "Rastede", "Area_Name": "Rastede",
"Area_Short": "", "Area_Short": "",
"IdArea": 20998, "IdArea": 20998,
"X": 53.243954, "X": 53.242157,
"Y": 8.160439, "Y": 8.160353,
"Icon": 21, "Icon": 21,
"System": 6, "System": 6,
"Active": 1 "Active": 1
@@ -146,8 +146,8 @@
"Area_Name": "Rastede", "Area_Name": "Rastede",
"Area_Short": "", "Area_Short": "",
"IdArea": 20998, "IdArea": 20998,
"X": 53.243954, "X": 53.242157,
"Y": 8.160439, "Y": 8.160353,
"Icon": 21, "Icon": 21,
"System": 6, "System": 6,
"Active": 1 "Active": 1

View File

@@ -67,7 +67,7 @@
"Na": "minor", "Na": "minor",
"Le": 3, "Le": 3,
"Co": "#FFFF00", "Co": "#FFFF00",
"Me": "KÜG 08: Überspannung gehend", "Me": "KÜG 08: Überspannung gehend",
"Feld": 4, "Feld": 4,
"Icon": 0 "Icon": 0
}, },
@@ -85,7 +85,7 @@
"Na": "critical", "Na": "critical",
"Le": 1, "Le": 1,
"Co": "#FF0000", "Co": "#FF0000",
"Me": "KÜG 02: Aderbruch kommend", "Me": "KÜG 02: Aderbruch kommend",
"Feld": 4, "Feld": 4,
"Icon": 0 "Icon": 0
}, },
@@ -94,7 +94,7 @@
"Na": "critical", "Na": "critical",
"Le": 1, "Le": 1,
"Co": "#FF0000", "Co": "#FF0000",
"Me": "KÜG 03: Aderbruch kommend", "Me": "KÜG 03: Aderbruch kommend",
"Feld": 4, "Feld": 4,
"Icon": 0 "Icon": 0
} }

View File

@@ -9,14 +9,14 @@
{ {
"IdSystem": 2, "IdSystem": 2,
"Name": "ECI", "Name": "ECI",
"Longname": "ECI Geräte", "Longname": "ECI Geräte",
"Allow": 1, "Allow": 1,
"Icon": 2 "Icon": 2
}, },
{ {
"IdSystem": 3, "IdSystem": 3,
"Name": "ULAF", "Name": "ULAF",
"Longname": "ULAF Geräte", "Longname": "ULAF Geräte",
"Allow": 0, "Allow": 0,
"Icon": 3 "Icon": 3
}, },
@@ -51,7 +51,7 @@
{ {
"IdSystem": 9, "IdSystem": 9,
"Name": "OTDR", "Name": "OTDR",
"Longname": "Glasfaserüberwachung OTU", "Longname": "Glasfaserüberwachung OTU",
"Allow": 1, "Allow": 1,
"Icon": 9 "Icon": 9
}, },
@@ -65,7 +65,7 @@
{ {
"IdSystem": 11, "IdSystem": 11,
"Name": "GMA", "Name": "GMA",
"Longname": "Glättemeldeanlagen", "Longname": "Glättemeldeanlagen",
"Allow": 1, "Allow": 1,
"Icon": 11 "Icon": 11
}, },
@@ -73,7 +73,7 @@
"IdSystem": 13, "IdSystem": 13,
"Name": "Messstellen", "Name": "Messstellen",
"Longname": "Messstellen", "Longname": "Messstellen",
"Allow": 0, "Allow": 1,
"Icon": 13 "Icon": 13
}, },
{ {
@@ -93,7 +93,7 @@
{ {
"IdSystem": 110, "IdSystem": 110,
"Name": "DAUZ", "Name": "DAUZ",
"Longname": "Dauerzählstellen", "Longname": "Dauerzählstellen",
"Allow": 1, "Allow": 1,
"Icon": 110 "Icon": 110
}, },