feat: Verbesserung der Slot-Auswahl und Chart-Steuerung

- `selectedSlot` in `kueChartModeSlice.ts` hinzugefügt, um den aktiven Slot zu speichern.
- `handleButtonClick.ts` aktualisiert, damit `selectedSlot` in Redux gesetzt wird.
- `handleOpenChartModal.ts` angepasst, sodass `selectedSlot` beim Öffnen des Modals gesetzt wird.
- `Kue705FO.tsx` nutzt jetzt Redux für die Slot- und Modus-Steuerung (`selectedSlot`, `activeMode`).
- Redux-Logik optimiert: `selectedSlot` wird nun konsistent zwischen `TDRChart.tsx` und `ChartSwitcher.tsx` genutzt.
- Verbesserung der Benutzererfahrung: Nur `onClick` verwendet, `onMouseOver` wegen Performance-Problemen vermieden.

 Jetzt wird der richtige Slot in Redux gespeichert, wenn ein Button oder das Chart-Modal geöffnet wird.
 `TDRChart.tsx` zeigt nur das gewählte Modul an, kein unnötiges Neuladen von Daten.
 Stabile und optimierte Chart-Steuerung ohne ungewollte Änderungen durch Mausbewegungen.
This commit is contained in:
ISA
2025-03-20 11:50:14 +01:00
parent 735fc92b96
commit 0bdf5b29ea
13 changed files with 110 additions and 47 deletions

View File

@@ -1,4 +1,5 @@
"use client"; // /components/modules/kue705FO/charts/ChartSwitcher.tsx "use client"; // /components/modules/kue705FO/charts/ChartSwitcher.tsx
import React, { useState } from "react"; import React, { useState } from "react";
import ReactModal from "react-modal"; import ReactModal from "react-modal";
import LoopChartActionBar from "./LoopMeasurementChart/LoopChartActionBar"; import LoopChartActionBar from "./LoopMeasurementChart/LoopChartActionBar";
@@ -11,15 +12,26 @@ import {
setChartOpen, setChartOpen,
setFullScreen, setFullScreen,
} from "../../../../../redux/slices/kabelueberwachungChartSlice"; } from "../../../../../redux/slices/kabelueberwachungChartSlice";
import {
setSelectedSlot,
setSelectedChartType,
} from "../../../../../redux/slices/tdrChartSlice";
import { resetBrushRange } from "../../../../../redux/slices/brushSlice"; import { resetBrushRange } from "../../../../../redux/slices/brushSlice";
interface ChartSwitcherProps { interface ChartSwitcherProps {
isOpen: boolean; isOpen: boolean;
onClose: () => void; onClose: () => void;
slotIndex: number;
} }
const ChartSwitcher: React.FC<ChartSwitcherProps> = ({ isOpen, onClose }) => { const ChartSwitcher: React.FC<ChartSwitcherProps> = ({
isOpen,
onClose,
slotIndex,
}) => {
const dispatch = useDispatch(); const dispatch = useDispatch();
// **Redux-States für aktive Messkurve (TDR oder Schleife)**
const activeMode = useSelector( const activeMode = useSelector(
(state: RootState) => state.kueChartMode.activeMode (state: RootState) => state.kueChartMode.activeMode
); );
@@ -40,6 +52,12 @@ const ChartSwitcher: React.FC<ChartSwitcherProps> = ({ isOpen, onClose }) => {
dispatch(setFullScreen(!isFullScreen)); dispatch(setFullScreen(!isFullScreen));
}; };
// **Slot und Messkurve setzen**
const setChartType = (chartType: "TDR" | "Schleife") => {
dispatch(setSelectedSlot(slotIndex));
dispatch(setSelectedChartType(chartType));
};
return ( return (
<ReactModal <ReactModal
isOpen={isOpen} isOpen={isOpen}
@@ -112,6 +130,26 @@ const ChartSwitcher: React.FC<ChartSwitcherProps> = ({ isOpen, onClose }) => {
height: "100%", height: "100%",
}} }}
> >
{/* Auswahl zwischen TDR und Schleife */}
<div className="flex space-x-4 mb-4">
<button
className={`p-2 text-white ${
activeMode === "TDR" ? "bg-blue-500" : "bg-gray-400"
}`}
onClick={() => setChartType("TDR")}
>
TDR
</button>
<button
className={`p-2 text-white ${
activeMode === "Schleife" ? "bg-blue-500" : "bg-gray-400"
}`}
onClick={() => setChartType("Schleife")}
>
Schleife
</button>
</div>
{activeMode === "Schleife" ? ( {activeMode === "Schleife" ? (
<> <>
<h3 className="text-lg font-semibold">Schleifenmessung</h3> <h3 className="text-lg font-semibold">Schleifenmessung</h3>
@@ -123,13 +161,7 @@ const ChartSwitcher: React.FC<ChartSwitcherProps> = ({ isOpen, onClose }) => {
) : ( ) : (
<> <>
<h3 className="text-lg font-semibold">TDR-Messung</h3> <h3 className="text-lg font-semibold">TDR-Messung</h3>
{/* TDR-Chart-Action-Bar */} <TDRChart isFullScreen={isFullScreen} />
{/* <TDRChartActionBar /> */}
{/* TDR-Chart */}
<div style={{ flex: 1, height: "90%" }}>
<TDRChart />
</div>
</> </>
)} )}
</div> </div>

View File

@@ -73,7 +73,7 @@ const LoopChartActionBar: React.FC = () => {
if (!apiUrl) return; if (!apiUrl) return;
try { try {
console.log("📡 API-Request an:", apiUrl); // console.log("📡 API-Request an:", apiUrl);
const response = await fetch(apiUrl, { const response = await fetch(apiUrl, {
method: "GET", method: "GET",
headers: { "Content-Type": "application/json" }, headers: { "Content-Type": "application/json" },

View File

@@ -1,4 +1,6 @@
// components/main/kabelueberwachung/kue705FO/Charts/TDRChart/TDRChart.tsx // components/main/kabelueberwachung/kue705FO/Charts/TDRChart/TDRChart.tsx
"use client";
import React, { useEffect, useRef } from "react"; import React, { useEffect, useRef } from "react";
import { RootState } from "../../../../../../redux/store"; import { RootState } from "../../../../../../redux/store";
import { useSelector } from "react-redux"; import { useSelector } from "react-redux";
@@ -6,26 +8,22 @@ import { Chart, registerables } from "chart.js";
import "chartjs-adapter-date-fns"; import "chartjs-adapter-date-fns";
import { getColor } from "../../../../../../utils/colors"; import { getColor } from "../../../../../../utils/colors";
// 🟢 **Prop-Typ für isFullScreen hinzufügen** const TDRChart: React.FC<{ isFullScreen: boolean }> = ({ isFullScreen }) => {
interface TDRChartProps {
isFullScreen: boolean;
}
const TDRChart: React.FC = () => {
const isFullScreen = useSelector(
(state: RootState) => state.kabelueberwachungChart.isFullScreen
);
const chartRef = useRef<HTMLCanvasElement>(null); const chartRef = useRef<HTMLCanvasElement>(null);
const chartInstance = useRef<Chart | null>(null); const chartInstance = useRef<Chart | null>(null);
// TDR-Daten aus dem Redux Store selektieren // 🟢 **Hole den ausgewählten Slot und Messkurve aus Redux**
const tdrChartData = useSelector( const selectedSlot = useSelector(
(state: any) => state.kabelueberwachungChart.tdrChartData (state: RootState) => state.tdrChart.selectedSlot
);
const selectedChartType = useSelector(
(state: RootState) => state.kueChartMode.activeMode
);
const tdrChartData = useSelector((state: RootState) =>
selectedSlot !== null ? state.tdrChart.data[selectedSlot] || [] : []
); );
useEffect(() => { useEffect(() => {
// Importiere und registriere das Zoom-Plugin innerhalb des useEffect-Hooks
import("chartjs-plugin-zoom").then((zoomPlugin) => { import("chartjs-plugin-zoom").then((zoomPlugin) => {
Chart.register(...registerables, zoomPlugin.default); Chart.register(...registerables, zoomPlugin.default);
@@ -41,15 +39,16 @@ const TDRChart: React.FC = () => {
data: { data: {
datasets: [ datasets: [
{ {
label: "TDR Entfernung (km)", label: `Modul ${
selectedSlot !== null ? selectedSlot + 1 : "?"
} (${selectedChartType})`,
data: tdrChartData, data: tdrChartData,
borderColor: getColor("littwin-blue"), // Nutzt automatisch die Tailwind-Farbe borderColor: getColor("littwin-blue"),
borderWidth: 0.5, borderWidth: 1,
//backgroundColor: getColor("littwin-blue"),
tension: 0.1, tension: 0.1,
parsing: { parsing: {
xAxisKey: "t", // Schlüssel für den Zeitstempel xAxisKey: "t",
yAxisKey: "m", // Schlüssel für den Messwert yAxisKey: "m",
}, },
}, },
], ],
@@ -59,7 +58,7 @@ const TDRChart: React.FC = () => {
maintainAspectRatio: false, maintainAspectRatio: false,
scales: { scales: {
x: { x: {
type: "linear", // Lineare Skalierung für numerische Daten type: "linear",
title: { title: {
display: true, display: true,
text: "Entfernung (m)", text: "Entfernung (m)",
@@ -94,7 +93,7 @@ const TDRChart: React.FC = () => {
} }
} }
}); });
}, [tdrChartData]); }, [tdrChartData, selectedSlot, selectedChartType]);
return ( return (
<div style={{ width: "100%", height: isFullScreen ? "90%" : "28rem" }}> <div style={{ width: "100%", height: isFullScreen ? "90%" : "28rem" }}>

View File

@@ -4,7 +4,10 @@ import {
setChartOpen, setChartOpen,
setSlotNumber, setSlotNumber,
} from "../../../../../redux/slices/kabelueberwachungChartSlice"; } from "../../../../../redux/slices/kabelueberwachungChartSlice";
import { setActiveMode } from "../../../../../redux/slices/kueChartModeSlice"; import {
setActiveMode,
setSelectedSlot, // ✅ Importiere setSelectedSlot
} from "../../../../../redux/slices/kueChartModeSlice";
import { useDispatch } from "react-redux"; import { useDispatch } from "react-redux";
const handleOpenChartModal = ( const handleOpenChartModal = (
@@ -17,6 +20,9 @@ const handleOpenChartModal = (
dispatch(setChartOpen(true)); dispatch(setChartOpen(true));
dispatch(setSlotNumber(slotIndex + 1)); dispatch(setSlotNumber(slotIndex + 1));
// ✅ Speichert den gewählten Slot in Redux
dispatch(setSelectedSlot(slotIndex));
if (activeButton === "TDR") { if (activeButton === "TDR") {
dispatch(setActiveMode("TDR")); dispatch(setActiveMode("TDR"));
} else { } else {

View File

@@ -1,6 +1,9 @@
// components/main/kabelueberwachung/kue705FO/kue705FO-Funktionen/handleButtonClick.ts // components/main/kabelueberwachung/kue705FO/kue705FO-Funktionen/handleButtonClick.ts
import { Dispatch } from "react"; import { Dispatch } from "react";
import { setActiveMode } from "../../../../../redux/slices/kueChartModeSlice"; import {
setActiveMode,
setSelectedSlot,
} from "../../../../../redux/slices/kueChartModeSlice";
const handleButtonClick = ( const handleButtonClick = (
button: "Schleife" | "TDR", button: "Schleife" | "TDR",
@@ -12,6 +15,9 @@ const handleButtonClick = (
slotIndex: number, slotIndex: number,
dispatch: Dispatch<any> dispatch: Dispatch<any>
) => { ) => {
// 🔥 Speichert den gewählten Slot im Redux-Store
dispatch(setSelectedSlot(slotIndex));
if (button === "Schleife") { if (button === "Schleife") {
setActiveButton("Schleife"); setActiveButton("Schleife");
setLoopTitleText("Schleifenwiderstand [kOhm]"); setLoopTitleText("Schleifenwiderstand [kOhm]");

View File

@@ -6,5 +6,5 @@
2: Patch oder Hotfix (Bugfixes oder kleine Änderungen). 2: Patch oder Hotfix (Bugfixes oder kleine Änderungen).
*/ */
const webVersion = "1.6.137"; const webVersion = "1.6.138";
export default webVersion; export default webVersion;

View File

@@ -39,7 +39,7 @@ function AppContent({ Component, pageProps }: AppProps) {
const variables = await loadWindowVariables(); const variables = await loadWindowVariables();
if (!variables) throw new Error("Sitzungsfehler"); if (!variables) throw new Error("Sitzungsfehler");
console.log("✅ Window-Variablen geladen:", variables); //console.log("✅ Window-Variablen geladen:", variables);
const { const {
last20Messages, last20Messages,

View File

@@ -36,7 +36,7 @@ function Kabelueberwachung() {
// Beim Laden der Seite TDR-Daten abrufen // Beim Laden der Seite TDR-Daten abrufen
useEffect(() => { useEffect(() => {
if (!tdrData || tdrData.length === 0) { if (!tdrData || tdrData.length === 0) {
console.log("TDR-Daten abrufen..."); // console.log("TDR-Daten abrufen...");
dispatch(fetchAllTDRChartData()); dispatch(fetchAllTDRChartData());
} }
}, [dispatch, tdrData]); }, [dispatch, tdrData]);

View File

@@ -2,11 +2,13 @@
import { createSlice, PayloadAction } from "@reduxjs/toolkit"; import { createSlice, PayloadAction } from "@reduxjs/toolkit";
interface KueChartModeState { interface KueChartModeState {
activeMode: "Schleife" | "TDR"; // 🔥 Neuer Zustand für den aktiven Modus activeMode: "Schleife" | "TDR"; // 🔥 Zustand für den aktiven Modus
selectedSlot: number | null; // 🔥 Neu: Aktuell gewählter Slot
} }
const initialState: KueChartModeState = { const initialState: KueChartModeState = {
activeMode: "Schleife", // Standard ist Schleife activeMode: "Schleife", // Standard ist Schleife
selectedSlot: null, // Standard: Kein Slot ausgewählt
}; };
export const kueChartModeSlice = createSlice({ export const kueChartModeSlice = createSlice({
@@ -16,8 +18,11 @@ export const kueChartModeSlice = createSlice({
setActiveMode: (state, action: PayloadAction<"Schleife" | "TDR">) => { setActiveMode: (state, action: PayloadAction<"Schleife" | "TDR">) => {
state.activeMode = action.payload; // 🔥 Speichert den Modus (Schleife oder TDR) state.activeMode = action.payload; // 🔥 Speichert den Modus (Schleife oder TDR)
}, },
setSelectedSlot: (state, action: PayloadAction<number>) => {
state.selectedSlot = action.payload; // 🔥 Speichert den aktiven Slot
},
}, },
}); });
export const { setActiveMode } = kueChartModeSlice.actions; export const { setActiveMode, setSelectedSlot } = kueChartModeSlice.actions;
export default kueChartModeSlice.reducer; export default kueChartModeSlice.reducer;

View File

@@ -4,12 +4,16 @@ import { fetchAllTDRChartData } from "../thunks/fetchAllTDRChartThunk";
interface TDRChartState { interface TDRChartState {
data: any[]; data: any[];
selectedSlot: number | null;
selectedChartType: "TDR" | "Schleife";
loading: boolean; loading: boolean;
error: string | null; error: string | null;
} }
const initialState: TDRChartState = { const initialState: TDRChartState = {
data: [], data: [],
selectedSlot: null,
selectedChartType: "TDR",
loading: false, loading: false,
error: null, error: null,
}; };
@@ -17,7 +21,17 @@ const initialState: TDRChartState = {
const tdrChartSlice = createSlice({ const tdrChartSlice = createSlice({
name: "tdrChart", name: "tdrChart",
initialState, initialState,
reducers: {}, reducers: {
setSelectedSlot: (state, action: PayloadAction<number>) => {
state.selectedSlot = action.payload;
},
setSelectedChartType: (
state,
action: PayloadAction<"TDR" | "Schleife">
) => {
state.selectedChartType = action.payload;
},
},
extraReducers: (builder) => { extraReducers: (builder) => {
builder builder
.addCase(fetchAllTDRChartData.pending, (state) => { .addCase(fetchAllTDRChartData.pending, (state) => {
@@ -28,7 +42,7 @@ const tdrChartSlice = createSlice({
fetchAllTDRChartData.fulfilled, fetchAllTDRChartData.fulfilled,
(state, action: PayloadAction<any[]>) => { (state, action: PayloadAction<any[]>) => {
state.loading = false; state.loading = false;
state.data = action.payload; // Speichere die neuen Daten in Redux state.data = action.payload;
} }
) )
.addCase(fetchAllTDRChartData.rejected, (state, action) => { .addCase(fetchAllTDRChartData.rejected, (state, action) => {
@@ -38,4 +52,5 @@ const tdrChartSlice = createSlice({
}, },
}); });
export const { setSelectedSlot, setSelectedChartType } = tdrChartSlice.actions;
export default tdrChartSlice.reducer; export default tdrChartSlice.reducer;

View File

@@ -25,7 +25,7 @@ export const fetchAllTDRChartData = createAsyncThunk(
const newData = await Promise.all(fetchPromises); const newData = await Promise.all(fetchPromises);
console.log("Geladene Daten:", newData); //console.log("Geladene Daten:", newData);
// Falls die Daten leer oder identisch sind, aber initial leer waren, trotzdem speichern // Falls die Daten leer oder identisch sind, aber initial leer waren, trotzdem speichern
if (newData.every((d) => d === null || d === undefined)) { if (newData.every((d) => d === null || d === undefined)) {

View File

@@ -21,7 +21,7 @@ export const fetchAnalogeEingaenge = async () => {
const apiUrl = getApiUrl(); const apiUrl = getApiUrl();
if (!apiUrl) return null; // ❌ Falls SSR aktiv ist, nicht ausführen if (!apiUrl) return null; // ❌ Falls SSR aktiv ist, nicht ausführen
console.log(`📡 API-Request an: ${apiUrl}`); // console.log(`📡 API-Request an: ${apiUrl}`);
const response = await fetch(apiUrl); const response = await fetch(apiUrl);
if (!response.ok) { if (!response.ok) {
@@ -29,7 +29,7 @@ export const fetchAnalogeEingaenge = async () => {
} }
const rawData = await response.text(); const rawData = await response.text();
console.log("✅ Rohdaten erfolgreich geladen:", rawData); //console.log("✅ Rohdaten erfolgreich geladen:", rawData);
// **JavaScript-Variablen als Skript einfügen** // **JavaScript-Variablen als Skript einfügen**
const script = document.createElement("script"); const script = document.createElement("script");
@@ -53,7 +53,7 @@ export const fetchAnalogeEingaenge = async () => {
} }
} }
console.log("✅ Formatierte Daten:", formattedData); // console.log("✅ Formatierte Daten:", formattedData);
return formattedData; return formattedData;
} catch (error) { } catch (error) {
console.error("❌ Fehler beim Laden der analogen Eingänge:", error); console.error("❌ Fehler beim Laden der analogen Eingänge:", error);

View File

@@ -20,14 +20,14 @@ export const fetchDigitaleEingaenge = async () => {
const apiUrl = getApiUrl(); const apiUrl = getApiUrl();
if (!apiUrl) return null; if (!apiUrl) return null;
console.log(`📡 API-Request an: ${apiUrl}`); // console.log(`📡 API-Request an: ${apiUrl}`);
const response = await fetch(apiUrl); const response = await fetch(apiUrl);
if (!response.ok) { if (!response.ok) {
throw new Error(`❌ Fehler: ${response.status} ${response.statusText}`); throw new Error(`❌ Fehler: ${response.status} ${response.statusText}`);
} }
const rawData = await response.text(); const rawData = await response.text();
console.log("✅ Rohdaten erfolgreich geladen:", rawData); // console.log("✅ Rohdaten erfolgreich geladen:", rawData);
// **JavaScript-Variablen als Skript einfügen** // **JavaScript-Variablen als Skript einfügen**
const script = document.createElement("script"); const script = document.createElement("script");
@@ -43,7 +43,7 @@ export const fetchDigitaleEingaenge = async () => {
flutter: win_flutter[index] || 0, flutter: win_flutter[index] || 0,
})); }));
console.log("✅ Formatierte Daten:", formattedData); // console.log("✅ Formatierte Daten:", formattedData);
return formattedData; return formattedData;
} catch (error) { } catch (error) {
console.error("❌ Fehler beim Laden der digitalen Eingänge:", error); console.error("❌ Fehler beim Laden der digitalen Eingänge:", error);