feat: Struktur für Charts verbessert und Komponenten getrennt

- `LoopMeasurementChart.tsx` und `TDRChart.tsx` erstellt für separate Diagramm-Darstellungen.
- Neue Struktur unter `/components/modules/kue705FO/charts/` eingeführt.
- `ChartModal.tsx` bleibt für generelle Nutzung erhalten.
- Erhöhte Wartbarkeit und Modularität durch Trennung der Chart-Komponenten.
This commit is contained in:
ISA
2025-02-13 11:55:52 +01:00
parent d67ad97f83
commit 5c7b5555c4
17 changed files with 411 additions and 91 deletions

View File

@@ -3,13 +3,13 @@ import { render, fireEvent, screen } from "@testing-library/react";
import configureStore from "redux-mock-store";
import { Provider } from "react-redux";
import "@testing-library/jest-dom";
import Kue705FO from "../../../components/modules/Kue705FO";
import Kue705FO from "../../../components/modules/kue705FO/Kue705FO";
// Mocks für externe Abhängigkeiten
jest.mock("chart.js/auto", () => ({
default: {
register: jest.fn(),
},
default: {
register: jest.fn(),
},
Chart: jest.fn().mockImplementation(() => ({
destroy: jest.fn(),
update: jest.fn(),

View File

@@ -1,66 +0,0 @@
import React from "react";
import ReactModal from "react-modal";
import TDRPopup from "./LoopTDRChartActionBar";
interface ChartModalProps {
isOpen: boolean;
onClose: () => void;
chartRef: React.RefObject<HTMLCanvasElement>;
}
const ChartModal: React.FC<ChartModalProps> = ({
isOpen,
onClose,
chartRef,
}) => {
return (
<ReactModal
isOpen={isOpen}
onRequestClose={onClose}
ariaHideApp={false}
style={{
overlay: { backgroundColor: "rgba(0, 0, 0, 0.5)" },
content: {
top: "50%",
left: "50%",
right: "auto",
bottom: "auto",
marginRight: "-50%",
transform: "translate(-50%, -50%)",
width: "80%",
maxWidth: "50rem",
height: "30rem",
padding: "1rem",
overflow: "hidden",
},
}}
>
<button
onClick={onClose}
style={{
position: "absolute",
top: "0.625rem",
right: "0.625rem",
background: "transparent",
border: "none",
fontSize: "1.5rem",
cursor: "pointer",
}}
>
<i className="bi bi-x-circle-fill"></i>
</button>
<div style={{ maxHeight: "100%", overflow: "auto" }}>
<TDRPopup />
</div>
<canvas
id="myChart"
ref={chartRef}
style={{ width: "100%", height: "20rem" }}
></canvas>
</ReactModal>
);
};
export default ChartModal;

View File

@@ -4,10 +4,12 @@ import { useState, useEffect } from "react";
import { useSelector, useDispatch } from "react-redux";
import { setVariables } from "../../../redux/slices/variablesSlice";
import "bootstrap-icons/font/bootstrap-icons.css"; // Import Bootstrap Icons
import handleSave, { OriginalValues } from "./handlers/handleSave";
import handleDisplayEinschalten from "./handlers/handleDisplayEinschalten";
import handleChange from "./handlers/handleChange";
import firmwareUpdate from "./handlers/firmwareUpdate";
import handleSave, {
OriginalValues,
} from "../../modules/kue705FO/handlers/handleSave";
import handleDisplayEinschalten from "../../modules/kue705FO/handlers/handleDisplayEinschalten";
import handleChange from "../../modules/kue705FO/handlers/handleChange";
import firmwareUpdate from "../../modules/kue705FO/handlers/firmwareUpdate";
import decodeToken from "../../../utils/decodeToken";
// Props-Typen definieren
interface KueModalProps {

View File

@@ -1,6 +1,6 @@
import React, { useState } from "react";
import DateRangePicker from "./DateRangePicker";
import ChartModal from "./ChartModal"; // Importiere das Chart-Modal
import ChartModal from "../../modules/kue705FO/charts/ChartModal"; // Importiere das Chart-Modal
import { useDispatch, useSelector } from "react-redux";
import { setChartData } from "../../../redux/slices/chartDataSlice";
import { RootState } from "../../../redux/store";
@@ -93,6 +93,7 @@ const LoopTDRChartActionBar: React.FC = () => {
<ChartModal
isOpen={showChartModal}
onClose={() => setShowChartModal(false)}
chartRef={React.createRef<HTMLCanvasElement>()}
/>
)}
</div>

View File

@@ -3,24 +3,25 @@ import React, { useState, useEffect, useRef } from "react";
import ReactModal from "react-modal";
import Chart from "chart.js/auto";
import { useSelector } from "react-redux";
import KueModal from "../modales/kueModal/KueModal";
import KueModal from "../../modales/kueModal/KueModal";
import "bootstrap-icons/font/bootstrap-icons.css"; // Import Bootstrap Icons
import { RootState } from "../../redux/store";
import { DataTDR } from "../../redux/types/chartDataTypesTDR";
import { RootState } from "../../../redux/store";
import { DataTDR } from "../../../redux/types/chartDataTypesTDR";
import { useDispatch } from "react-redux";
import {
setSelectedChartData,
setSelectedFileName,
} from "../../redux/slices/variablesSlice";
import TDRPopup from "../modales/kueModal/LoopTDRChartActionBar";
import { createLoopChart, createTDRChart } from "../../utils/chartUtils";
import { getAlarmDisplayText } from "../../utils/alarmUtils";
import { goLoop } from "../../utils/goLoop";
import { goTDR } from "../../utils/goTDR";
import { loadTDRChartData } from "../../utils/loadTDRChartData";
import { loadLoopChartData } from "../../utils/loadLoopChartData";
import { Kue705FOProps } from "../../types/components/Kue705FOProps";
import ChartModal from "../modales/kueModal/ChartModal";
} from "../../../redux/slices/variablesSlice";
import TDRPopup from "../../modales/kueModal/LoopTDRChartActionBar";
import { createLoopChart, createTDRChart } from "../../../utils/chartUtils";
import { getAlarmDisplayText } from "../../../utils/alarmUtils";
import { goLoop } from "../../../utils/goLoop";
import { goTDR } from "../../../utils/goTDR";
import { loadTDRChartData } from "../../../utils/loadTDRChartData";
import { loadLoopChartData } from "../../../utils/loadLoopChartData";
import { Kue705FOProps } from "../../../types/components/Kue705FOProps";
import ChartModal from "./charts/ChartModal";
import { setActiveMode } from "../../../redux/slices/chartDataSlice";
const Kue705FO: React.FC<Kue705FOProps> = ({
isolationswert,
@@ -102,6 +103,7 @@ const Kue705FO: React.FC<Kue705FOProps> = ({
setActiveButton("Schleife");
setloopTitleText("Schleifenwiderstand [kOhm]");
setLoopDisplayValue(schleifenwiderstand); // Setze den Wert auf schleifenwiderstand
dispatch(setActiveMode("Schleife")); // 🔥 Speichert den Modus in Redux
} else if (button === "TDR") {
setActiveButton("TDR");
setloopTitleText("Entfernung [Km]");
@@ -110,6 +112,7 @@ const Kue705FO: React.FC<Kue705FOProps> = ({
? tdrLocation[slotIndex]
: "0"
); // Setze den Wert auf tdrLocation oder "0" als Fallback
dispatch(setActiveMode("TDR")); // 🔥 Speichert den Modus in Redux
}
};

View File

@@ -0,0 +1,171 @@
import React, { useEffect, useRef } from "react";
import ReactModal from "react-modal";
import { useSelector } from "react-redux";
import { RootState } from "../../../../redux/store";
import Chart from "chart.js/auto";
import "chartjs-adapter-date-fns";
import { parseISO } from "date-fns";
import LoopTDRPopup from "../../../modales/kueModal/LoopTDRChartActionBar";
interface ChartModalProps {
isOpen: boolean;
onClose: () => void;
}
const ChartModal: React.FC<ChartModalProps> = ({ isOpen, onClose }) => {
const chartRef = useRef<HTMLCanvasElement>(null);
const chartInstance = useRef<Chart | null>(null);
// Redux State abrufen
const chartData = useSelector((state: RootState) => state.chartData.data);
const activeMode = useSelector(
(state: RootState) => state.chartData.activeMode
);
useEffect(() => {
if (chartRef.current && chartData.length > 0) {
if (chartInstance.current) {
chartInstance.current.destroy();
}
const ctx = chartRef.current.getContext("2d");
if (ctx) {
const labels = chartData.map((entry) =>
entry.timestamp ? parseISO(entry.timestamp) : new Date()
);
chartInstance.current = new Chart(ctx, {
type: "line",
data: {
labels,
datasets: [
{
label: "Isolationswiderstand (MOhm)",
data: chartData.map((entry) => entry.isolation ?? 0),
borderColor: "rgba(0, 123, 255, 1)",
backgroundColor: "rgba(0, 123, 255, 0.2)",
tension: 0.1,
yAxisID: "y-left",
},
{
label: "Schleifenwiderstand (kOhm)",
data: chartData.map((entry) => entry.loop ?? 0),
borderColor: "rgba(108, 117, 125, 1)",
backgroundColor: "rgba(108, 117, 125, 0.2)",
tension: 0.1,
yAxisID: "y-right",
},
],
},
options: {
responsive: true,
maintainAspectRatio: false,
scales: {
x: {
type: "time",
time: {
unit: "hour",
tooltipFormat: "dd.MM.yyyy HH:mm",
},
title: {
display: true,
text: "Zeit",
},
grid: {
drawBorder: true, // 🔥 Stellt sicher, dass die X-Achse sichtbar ist
},
},
y: {
id: "y-left",
position: "left",
title: {
display: true,
text: "MOhm",
},
min: 0,
max:
Math.max(...chartData.map((entry) => entry.isolation ?? 1)) +
10,
grid: {
drawOnChartArea: true, // 🔥 Korrektur, damit es sich nicht überlagert
},
},
"y-right": {
id: "y-right",
position: "right",
title: {
display: true,
text: "kOhm",
},
min: 0,
max: Math.max(...chartData.map((entry) => entry.loop ?? 1)) + 1,
grid: {
drawOnChartArea: false, // 🔥 Verhindert doppelte Linien
},
},
},
},
});
}
}
return () => {
if (chartInstance.current) {
chartInstance.current.destroy();
chartInstance.current = null;
}
};
}, [chartData, activeMode]);
return (
<ReactModal
isOpen={isOpen}
onRequestClose={onClose}
ariaHideApp={false}
style={{
overlay: { backgroundColor: "rgba(0, 0, 0, 0.5)" },
content: {
top: "50%",
left: "50%",
right: "auto",
bottom: "auto",
marginRight: "-50%",
transform: "translate(-50%, -50%)",
width: "80%",
maxWidth: "50rem",
height: "30rem",
padding: "1rem",
overflow: "hidden",
},
}}
>
<button
onClick={onClose}
style={{
position: "absolute",
top: "0.625rem",
right: "0.625rem",
background: "transparent",
border: "none",
fontSize: "1.5rem",
cursor: "pointer",
}}
>
<i className="bi bi-x-circle-fill"></i>
</button>
{/* Beibehaltung der UI mit Kalender und Buttons */}
<div style={{ maxHeight: "100%", overflow: "auto" }}>
<LoopTDRPopup />
</div>
{/* Canvas für das Chart */}
<canvas
ref={chartRef}
style={{ width: "100%", height: "20rem", marginTop: "1rem" }}
></canvas>
</ReactModal>
);
};
export default ChartModal;

View File

@@ -0,0 +1,110 @@
import React, { useEffect, useRef } from "react";
import { useSelector } from "react-redux";
import { RootState } from "../../../redux/store";
import Chart from "chart.js/auto";
import "chartjs-adapter-date-fns";
import { parseISO } from "date-fns";
const LoopMeasurementChart: React.FC = () => {
const chartRef = useRef<HTMLCanvasElement>(null);
const chartInstance = useRef<Chart | null>(null);
// Redux-Daten abrufen
const chartData = useSelector((state: RootState) => state.chartData.data);
useEffect(() => {
if (chartRef.current && chartData.length > 0) {
if (chartInstance.current) {
chartInstance.current.destroy(); // Bestehendes Chart zerstören
}
const ctx = chartRef.current.getContext("2d");
if (ctx) {
const labels = chartData.map((entry) =>
entry.timestamp ? parseISO(entry.timestamp) : new Date()
);
chartInstance.current = new Chart(ctx, {
type: "line",
data: {
labels,
datasets: [
{
label: "Schleifenwiderstand (kOhm)",
data: chartData.map((entry) => entry.loop ?? 0),
borderColor: "rgba(75, 192, 192, 1)", // Türkis
backgroundColor: "rgba(75, 192, 192, 0.2)",
tension: 0.1,
yAxisID: "y-left",
},
{
label: "Zusätzliche Schleifenmesswerte",
data: chartData.map((entry) => entry.additional ?? 0),
borderColor: "rgba(255, 159, 64, 1)", // Orange
backgroundColor: "rgba(255, 159, 64, 0.2)",
tension: 0.1,
yAxisID: "y-right",
},
],
},
options: {
responsive: true,
maintainAspectRatio: false,
scales: {
x: {
type: "time",
time: {
unit: "hour",
tooltipFormat: "dd.MM.yyyy HH:mm",
},
title: {
display: true,
text: "Zeit",
},
grid: {
drawBorder: true,
},
},
y: {
id: "y-left",
position: "left",
title: {
display: true,
text: "kOhm",
},
min: 0,
max: Math.max(...chartData.map((entry) => entry.loop ?? 1)) + 1,
},
"y-right": {
id: "y-right",
position: "right",
title: {
display: true,
text: "Zusätzliche Werte",
},
min: 0,
max:
Math.max(...chartData.map((entry) => entry.additional ?? 1)) +
1,
grid: {
drawOnChartArea: false,
},
},
},
},
});
}
}
return () => {
if (chartInstance.current) {
chartInstance.current.destroy();
chartInstance.current = null;
}
};
}, [chartData]);
return <canvas ref={chartRef} style={{ width: "100%", height: "20rem" }} />;
};
export default LoopMeasurementChart;

View File

@@ -0,0 +1,71 @@
import React, { useEffect, useRef } from "react";
import { useSelector } from "react-redux";
import { RootState } from "../../../redux/store";
import Chart from "chart.js/auto";
import "chartjs-adapter-date-fns";
import { parseISO } from "date-fns";
const TDRChart: React.FC = () => {
const chartRef = useRef<HTMLCanvasElement>(null);
const chartInstance = useRef<Chart | null>(null);
const chartData = useSelector((state: RootState) => state.chartData.data);
useEffect(() => {
if (chartRef.current && chartData.length > 0) {
if (chartInstance.current) {
chartInstance.current.destroy();
}
const ctx = chartRef.current.getContext("2d");
if (ctx) {
chartInstance.current = new Chart(ctx, {
type: "line",
data: {
labels: chartData.map((entry) => parseISO(entry.timestamp)),
datasets: [
{
label: "Isolationswiderstand (MOhm)",
data: chartData.map((entry) => entry.isolation ?? 0),
borderColor: "rgba(0, 123, 255, 1)",
backgroundColor: "rgba(0, 123, 255, 0.2)",
tension: 0.1,
},
],
},
options: {
responsive: true,
maintainAspectRatio: false,
scales: {
x: {
type: "time",
time: {
unit: "hour",
tooltipFormat: "dd.MM.yyyy HH:mm",
},
title: { display: true, text: "Zeit" },
},
y: {
title: { display: true, text: "MOhm" },
min: 0,
max:
Math.max(...chartData.map((entry) => entry.isolation ?? 1)) +
10,
},
},
},
});
}
}
return () => {
if (chartInstance.current) {
chartInstance.current.destroy();
chartInstance.current = null;
}
};
}, [chartData]);
return <canvas ref={chartRef} style={{ width: "100%", height: "20rem" }} />;
};
export default TDRChart;

View File

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

20
package-lock.json generated
View File

@@ -17,6 +17,7 @@
"bcryptjs": "^2.4.3",
"bootstrap-icons": "^1.11.3",
"chart.js": "^4.4.5",
"chartjs-adapter-date-fns": "^3.0.0",
"chartjs-plugin-zoom": "^2.0.1",
"crypto-js": "^4.2.0",
"date-fns": "^4.1.0",
@@ -25,6 +26,7 @@
"jwt-decode": "^4.0.0",
"next": "^14.2.23",
"react": "^18.3.1",
"react-chartjs-2": "^5.3.0",
"react-date-picker": "^11.0.0",
"react-datepicker": "^8.0.0",
"react-dom": "^18.3.1",
@@ -2606,6 +2608,15 @@
"pnpm": ">=8"
}
},
"node_modules/chartjs-adapter-date-fns": {
"version": "3.0.0",
"resolved": "https://registry.npmjs.org/chartjs-adapter-date-fns/-/chartjs-adapter-date-fns-3.0.0.tgz",
"integrity": "sha512-Rs3iEB3Q5pJ973J93OBTpnP7qoGwvq3nUnoMdtxO+9aoJof7UFcRbWcIDteXuYd1fgAvct/32T9qaLyLuZVwCg==",
"peerDependencies": {
"chart.js": ">=2.8.0",
"date-fns": ">=2.0.0"
}
},
"node_modules/chartjs-plugin-zoom": {
"version": "2.2.0",
"resolved": "https://registry.npmjs.org/chartjs-plugin-zoom/-/chartjs-plugin-zoom-2.2.0.tgz",
@@ -6892,6 +6903,15 @@
}
}
},
"node_modules/react-chartjs-2": {
"version": "5.3.0",
"resolved": "https://registry.npmjs.org/react-chartjs-2/-/react-chartjs-2-5.3.0.tgz",
"integrity": "sha512-UfZZFnDsERI3c3CZGxzvNJd02SHjaSJ8kgW1djn65H1KK8rehwTjyrRKOG3VTMG8wtHZ5rgAO5oTHtHi9GCCmw==",
"peerDependencies": {
"chart.js": "^4.1.1",
"react": "^16.8.0 || ^17.0.0 || ^18.0.0 || ^19.0.0"
}
},
"node_modules/react-date-picker": {
"version": "11.0.0",
"resolved": "https://registry.npmjs.org/react-date-picker/-/react-date-picker-11.0.0.tgz",

View File

@@ -22,6 +22,7 @@
"bcryptjs": "^2.4.3",
"bootstrap-icons": "^1.11.3",
"chart.js": "^4.4.5",
"chartjs-adapter-date-fns": "^3.0.0",
"chartjs-plugin-zoom": "^2.0.1",
"crypto-js": "^4.2.0",
"date-fns": "^4.1.0",
@@ -30,6 +31,7 @@
"jwt-decode": "^4.0.0",
"next": "^14.2.23",
"react": "^18.3.1",
"react-chartjs-2": "^5.3.0",
"react-date-picker": "^11.0.0",
"react-datepicker": "^8.0.0",
"react-dom": "^18.3.1",

View File

@@ -1,7 +1,7 @@
"use client";
import React, { useState, useEffect } from "react";
import { useRouter, useSearchParams } from "next/navigation";
import Kue705FO from "../components/modules/Kue705FO";
import Kue705FO from "../components/modules/kue705FO/Kue705FO";
import { useDispatch, useSelector } from "react-redux";
function Kabelueberwachung() {

View File

@@ -2,10 +2,12 @@ import { createSlice, PayloadAction } from "@reduxjs/toolkit";
interface ChartDataState {
data: any[];
activeMode: "Schleife" | "TDR"; // 🔥 Neuer Zustand für den aktiven Modus
}
const initialState: ChartDataState = {
data: [],
activeMode: "Schleife", // Standard ist Schleife
};
export const chartDataSlice = createSlice({
@@ -18,8 +20,12 @@ export const chartDataSlice = createSlice({
clearChartData: (state) => {
state.data = [];
},
setActiveMode: (state, action: PayloadAction<"Schleife" | "TDR">) => {
state.activeMode = action.payload; // 🔥 Speichert den Modus (Schleife oder TDR)
},
},
});
export const { setChartData, clearChartData } = chartDataSlice.actions;
export const { setChartData, clearChartData, setActiveMode } =
chartDataSlice.actions;
export default chartDataSlice.reducer;