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,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

@@ -0,0 +1,16 @@
const firmwareUpdate = (slot: number) => {
const url = `${window.location.origin}/CPL?/kabelueberwachung.html&KSU${slot}=1`;
fetch(url, { method: "GET" })
.then((response) => {
if (response.ok) {
alert(`Update an ${slot + 1} erfolgreich gestartet!`);
} else {
alert("Fehler beim Update!");
}
})
.catch((error) => {
console.error("Fehler:", error);
alert("Fehler beim Update!");
});
};
export default firmwareUpdate;

View File

@@ -0,0 +1,18 @@
import { Dispatch, SetStateAction } from "react";
// Funktion zur Änderung der Werte
const handleChange = (
setter: Dispatch<SetStateAction<any[]>>, // Typ für den Setter
e: React.ChangeEvent<HTMLInputElement>, // Typ für das Event
slot: number // Typ für den Slot
) => {
const value = e.target.value;
setter((prev: any[]) => {
// Typ für den vorherigen Zustand
const updated = [...prev];
updated[slot] = value;
return updated;
});
};
export default handleChange;

View File

@@ -0,0 +1,16 @@
const handleDisplayEinschalten = (slot: number) => {
const url = `/CPL?/kabelueberwachung.html&KSD${slot}=1`;
fetch(url, { method: "GET" })
.then((response) => {
if (response.ok) {
alert(`Display für Slot ${slot + 1} erfolgreich eingeschaltet!`);
} else {
alert("Fehler beim Einschalten des Displays!");
}
})
.catch((error) => {
console.error("Fehler:", error);
alert("Fehler beim Einschalten des Displays!");
});
};
export default handleDisplayEinschalten;

View File

@@ -0,0 +1,123 @@
// components/modales/kueModal/handlers/handleSave.ts
import { setVariables } from "../../../../redux/slices/variablesSlice";
export interface OriginalValues {
kueID: string[];
kueBezeichnungen: string[];
isolationsgrenzwerte: number[];
verzoegerung: number[];
untereSchleifenGrenzwerte: number[];
obereSchleifenGrenzwerte: number[];
schleifenintervall: number[];
}
interface HandleSaveParams {
ids: string[]; // kueID im Redux-Slice
bezeichnungen: string[]; // kueBezeichnungen im Redux-Slice
isolationsgrenzwerte: number[];
verzoegerung: number[];
untereSchleifenGrenzwerte: number[];
obereSchleifenGrenzwerte: number[];
schleifenintervall: number[];
originalValues: OriginalValues;
slot: number;
dispatch: (action: any) => void;
onModulNameChange: (id: string) => void;
onClose: () => void;
}
const handleSave = ({
ids,
bezeichnungen,
isolationsgrenzwerte,
verzoegerung,
untereSchleifenGrenzwerte,
obereSchleifenGrenzwerte,
schleifenintervall,
originalValues,
slot,
dispatch,
onModulNameChange,
onClose,
}: HandleSaveParams): void => {
const changes: Partial<{
KID: string;
KIA: string;
KL_: number;
KD_: number;
KR_: number;
KRO_: number;
KRI: number;
}> = {};
if (ids[slot] !== originalValues.kueID[slot]) {
changes.KID = ids[slot];
}
if (bezeichnungen[slot] !== originalValues.kueBezeichnungen[slot]) {
changes.KIA = bezeichnungen[slot];
}
if (
isolationsgrenzwerte[slot] !== originalValues.isolationsgrenzwerte[slot]
) {
changes.KL_ = isolationsgrenzwerte[slot];
}
if (verzoegerung[slot] !== originalValues.verzoegerung[slot]) {
changes.KD_ = verzoegerung[slot];
}
if (
untereSchleifenGrenzwerte[slot] !==
originalValues.untereSchleifenGrenzwerte[slot]
) {
changes.KR_ = untereSchleifenGrenzwerte[slot];
}
if (
obereSchleifenGrenzwerte[slot] !==
originalValues.obereSchleifenGrenzwerte[slot]
) {
changes.KRO_ = obereSchleifenGrenzwerte[slot];
}
if (schleifenintervall[slot] !== originalValues.schleifenintervall[slot]) {
changes.KRI = schleifenintervall[slot];
}
if (Object.keys(changes).length > 0) {
let url = `/cpl?/kabelueberwachung.html&slot=${slot}`;
Object.entries(changes).forEach(([paramKey, paramValue]) => {
if (paramValue !== undefined) {
url += `&${paramKey}${slot}=${encodeURIComponent(paramValue)}`;
}
});
fetch(url, { method: "GET" })
.then((response) => {
if (response.ok) {
alert("Daten erfolgreich gespeichert!");
onModulNameChange(ids[slot]);
dispatch(
setVariables({
kueID: [...ids],
kueBezeichnungen: [...bezeichnungen],
isolationsgrenzwerte: [...isolationsgrenzwerte],
verzoegerung: [...verzoegerung],
untereSchleifenGrenzwerte: [...untereSchleifenGrenzwerte],
obereSchleifenGrenzwerte: [...obereSchleifenGrenzwerte],
schleifenintervall: [...schleifenintervall],
})
);
} else {
alert("Fehler beim Speichern der Daten!");
}
})
.catch((error) => {
console.error("Fehler:", error);
alert("Fehler beim Senden der Daten!");
});
} else {
alert("Keine Änderungen vorgenommen.");
}
onClose();
};
export default handleSave;