diff --git a/__tests__/components/modules/Kue705FO.test.tsx b/__tests__/components/modules/Kue705FO.test.tsx index 844f076..00f9f89 100644 --- a/__tests__/components/modules/Kue705FO.test.tsx +++ b/__tests__/components/modules/Kue705FO.test.tsx @@ -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(), diff --git a/components/modales/kueModal/ChartModal.tsx b/components/modales/kueModal/ChartModal.tsx deleted file mode 100644 index 4b472a1..0000000 --- a/components/modales/kueModal/ChartModal.tsx +++ /dev/null @@ -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; -} - -const ChartModal: React.FC = ({ - isOpen, - onClose, - chartRef, -}) => { - return ( - - - -
- -
- - -
- ); -}; - -export default ChartModal; diff --git a/components/modales/kueModal/KueModal.tsx b/components/modales/kueModal/KueModal.tsx index 1802fb2..f468e00 100644 --- a/components/modales/kueModal/KueModal.tsx +++ b/components/modales/kueModal/KueModal.tsx @@ -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 { diff --git a/components/modales/kueModal/LoopTDRChartActionBar.tsx b/components/modales/kueModal/LoopTDRChartActionBar.tsx index 4d90c49..9395fb8 100644 --- a/components/modales/kueModal/LoopTDRChartActionBar.tsx +++ b/components/modales/kueModal/LoopTDRChartActionBar.tsx @@ -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 = () => { setShowChartModal(false)} + chartRef={React.createRef()} /> )} diff --git a/components/modules/Kue705FO.tsx b/components/modules/kue705FO/Kue705FO.tsx similarity index 94% rename from components/modules/Kue705FO.tsx rename to components/modules/kue705FO/Kue705FO.tsx index c84dd6e..2b73302 100644 --- a/components/modules/Kue705FO.tsx +++ b/components/modules/kue705FO/Kue705FO.tsx @@ -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 = ({ isolationswert, @@ -102,6 +103,7 @@ const Kue705FO: React.FC = ({ 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 = ({ ? tdrLocation[slotIndex] : "0" ); // Setze den Wert auf tdrLocation oder "0" als Fallback + dispatch(setActiveMode("TDR")); // 🔥 Speichert den Modus in Redux } }; diff --git a/components/modules/kue705FO/charts/ChartModal.tsx b/components/modules/kue705FO/charts/ChartModal.tsx new file mode 100644 index 0000000..371364d --- /dev/null +++ b/components/modules/kue705FO/charts/ChartModal.tsx @@ -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 = ({ isOpen, onClose }) => { + const chartRef = useRef(null); + const chartInstance = useRef(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 ( + + + + {/* Beibehaltung der UI mit Kalender und Buttons */} +
+ +
+ + {/* Canvas für das Chart */} + +
+ ); +}; + +export default ChartModal; diff --git a/components/modules/kue705FO/charts/LoopMeasurementChart.tsx b/components/modules/kue705FO/charts/LoopMeasurementChart.tsx new file mode 100644 index 0000000..d447746 --- /dev/null +++ b/components/modules/kue705FO/charts/LoopMeasurementChart.tsx @@ -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(null); + const chartInstance = useRef(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 ; +}; + +export default LoopMeasurementChart; diff --git a/components/modules/kue705FO/charts/TDRChart.tsx b/components/modules/kue705FO/charts/TDRChart.tsx new file mode 100644 index 0000000..fd31e62 --- /dev/null +++ b/components/modules/kue705FO/charts/TDRChart.tsx @@ -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(null); + const chartInstance = useRef(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 ; +}; + +export default TDRChart; diff --git a/components/modales/kueModal/handlers/firmwareUpdate.ts b/components/modules/kue705FO/handlers/firmwareUpdate.ts similarity index 100% rename from components/modales/kueModal/handlers/firmwareUpdate.ts rename to components/modules/kue705FO/handlers/firmwareUpdate.ts diff --git a/components/modales/kueModal/handlers/handleChange.ts b/components/modules/kue705FO/handlers/handleChange.ts similarity index 100% rename from components/modales/kueModal/handlers/handleChange.ts rename to components/modules/kue705FO/handlers/handleChange.ts diff --git a/components/modales/kueModal/handlers/handleDisplayEinschalten.ts b/components/modules/kue705FO/handlers/handleDisplayEinschalten.ts similarity index 100% rename from components/modales/kueModal/handlers/handleDisplayEinschalten.ts rename to components/modules/kue705FO/handlers/handleDisplayEinschalten.ts diff --git a/components/modales/kueModal/handlers/handleSave.ts b/components/modules/kue705FO/handlers/handleSave.ts similarity index 100% rename from components/modales/kueModal/handlers/handleSave.ts rename to components/modules/kue705FO/handlers/handleSave.ts diff --git a/config/webVersion.ts b/config/webVersion.ts index 0307670..37f877f 100644 --- a/config/webVersion.ts +++ b/config/webVersion.ts @@ -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; diff --git a/package-lock.json b/package-lock.json index 1820556..1750efe 100644 --- a/package-lock.json +++ b/package-lock.json @@ -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", diff --git a/package.json b/package.json index 3720a1b..9273c61 100644 --- a/package.json +++ b/package.json @@ -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", diff --git a/pages/kabelueberwachung.tsx b/pages/kabelueberwachung.tsx index e5a319b..a731e3e 100644 --- a/pages/kabelueberwachung.tsx +++ b/pages/kabelueberwachung.tsx @@ -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() { diff --git a/redux/slices/chartDataSlice.ts b/redux/slices/chartDataSlice.ts index 3a07edd..9a01b19 100644 --- a/redux/slices/chartDataSlice.ts +++ b/redux/slices/chartDataSlice.ts @@ -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;