309 lines
9.3 KiB
TypeScript
309 lines
9.3 KiB
TypeScript
"use client";
|
|
import React, { useCallback, useEffect, useMemo } from "react";
|
|
import { useSelector, useDispatch } from "react-redux";
|
|
import { RootState } from "../../../../../../redux/store";
|
|
import {
|
|
ComposedChart,
|
|
XAxis,
|
|
YAxis,
|
|
CartesianGrid,
|
|
Tooltip,
|
|
Legend,
|
|
ResponsiveContainer,
|
|
Line,
|
|
Brush,
|
|
} from "recharts";
|
|
import { setBrushRange } from "../../../../../../redux/slices/brushSlice";
|
|
|
|
const CustomTooltip = ({ active, payload, label, unit }: any) => {
|
|
if (active && payload && payload.length) {
|
|
const messwertMax = payload.find(
|
|
(p: any) => p.dataKey === "messwertMaximum"
|
|
);
|
|
const messwert = payload.find((p: any) => p.dataKey === "messwert");
|
|
const messwertMin = payload.find(
|
|
(p: any) => p.dataKey === "messwertMinimum"
|
|
);
|
|
const messwertDurchschnitt = payload.find(
|
|
(p: any) => p.dataKey === "messwertDurchschnitt"
|
|
);
|
|
|
|
return (
|
|
<div
|
|
style={{
|
|
background: "white",
|
|
padding: "8px",
|
|
border: "1px solid lightgrey",
|
|
borderRadius: "5px",
|
|
}}
|
|
>
|
|
<strong>{new Date(label).toLocaleString()}</strong>
|
|
{messwertMax && (
|
|
<div style={{ color: "grey" }}>
|
|
Messwert Maximum: {messwertMax.value.toFixed(2)} {unit}
|
|
</div>
|
|
)}
|
|
{messwert && (
|
|
<div style={{ color: "#00AEEF", fontWeight: "bold" }}>
|
|
Messwert: {messwert.value.toFixed(2)} {unit}
|
|
</div>
|
|
)}
|
|
{messwertDurchschnitt && (
|
|
<div
|
|
style={{ color: "#00AEEF" }}
|
|
>{`Messwert Durchschnitt: ${messwertDurchschnitt.value.toFixed(
|
|
2
|
|
)} ${unit}`}</div>
|
|
)}
|
|
{messwertMin && (
|
|
<div
|
|
style={{ color: "grey" }}
|
|
>{`Messwert Minimum: ${messwertMin.value.toFixed(2)} ${unit}`}</div>
|
|
)}
|
|
</div>
|
|
);
|
|
}
|
|
return null;
|
|
};
|
|
|
|
const LoopMeasurementChart = () => {
|
|
const dispatch = useDispatch();
|
|
const unit = useSelector(
|
|
(state: RootState) => state.kabelueberwachungChart.unit
|
|
);
|
|
|
|
const brushRange = useSelector((state: RootState) => state.brush);
|
|
const {
|
|
loopMeasurementCurveChartData,
|
|
selectedMode,
|
|
vonDatum,
|
|
bisDatum,
|
|
isFullScreen,
|
|
} = useSelector((state: RootState) => state.kabelueberwachungChart);
|
|
|
|
const formatierteDaten = useMemo(
|
|
() =>
|
|
loopMeasurementCurveChartData
|
|
.map((eintrag) => ({
|
|
zeit: new Date(eintrag.t).getTime(),
|
|
messwertMinimum: eintrag.i,
|
|
messwertMaximum: eintrag.a,
|
|
messwert: eintrag.m ?? null,
|
|
messwertDurchschnitt: ["DIA0", "DIA1", "DIA2"].includes(selectedMode)
|
|
? eintrag.g ?? null
|
|
: null,
|
|
}))
|
|
.reverse(),
|
|
[loopMeasurementCurveChartData, selectedMode]
|
|
);
|
|
|
|
// Initialisierung des Brush-Bereichs nur beim ersten Laden der Daten
|
|
useEffect(() => {
|
|
if (brushRange.endIndex === 0 && formatierteDaten.length) {
|
|
dispatch(
|
|
setBrushRange({
|
|
startIndex: 0,
|
|
endIndex: formatierteDaten.length - 1,
|
|
})
|
|
);
|
|
}
|
|
}, [formatierteDaten, brushRange.endIndex, dispatch]);
|
|
|
|
const handleBrushChange = useCallback(
|
|
({ startIndex, endIndex }: { startIndex?: number; endIndex?: number }) => {
|
|
if (startIndex === undefined || endIndex === undefined) return; // Verhindert Fehler
|
|
|
|
dispatch(
|
|
setBrushRange({
|
|
startIndex,
|
|
endIndex,
|
|
startDate: new Date(
|
|
formatierteDaten[startIndex]?.zeit || formatierteDaten[0].zeit
|
|
)
|
|
.toISOString()
|
|
.split("T")[0],
|
|
endDate: new Date(
|
|
formatierteDaten[endIndex]?.zeit ||
|
|
formatierteDaten[formatierteDaten.length - 1].zeit
|
|
)
|
|
.toISOString()
|
|
.split("T")[0],
|
|
})
|
|
);
|
|
},
|
|
[dispatch, formatierteDaten]
|
|
);
|
|
|
|
//--------------------------------------------------------------------------------
|
|
useEffect(() => {
|
|
if (formatierteDaten.length) {
|
|
console.log("🔍 Redux vonDatum:", vonDatum);
|
|
console.log("🔍 Redux bisDatum:", bisDatum);
|
|
|
|
//console.log("🔍 Verfügbare Zeitpunkte in formatierteDaten:");
|
|
/* formatierteDaten.forEach((d, index) => {
|
|
console.log(
|
|
`${index}: ${new Date(d.zeit).toISOString().split("T")[0]}`
|
|
);
|
|
});
|
|
*/
|
|
const startIndex = formatierteDaten.findIndex(
|
|
(d) => new Date(d.zeit).toISOString().split("T")[0] === vonDatum
|
|
);
|
|
//console.log("startIndex in Brush ", startIndex);
|
|
|
|
const endIndex = formatierteDaten.findIndex(
|
|
(d) => new Date(d.zeit).toISOString().split("T")[0] === bisDatum
|
|
);
|
|
//console.log("endIndex in Brush ", endIndex);
|
|
|
|
if (startIndex !== -1 && endIndex !== -1) {
|
|
dispatch(
|
|
setBrushRange({
|
|
startIndex,
|
|
endIndex,
|
|
startDate: vonDatum,
|
|
endDate: bisDatum,
|
|
})
|
|
);
|
|
}
|
|
}
|
|
}, [vonDatum, bisDatum, formatierteDaten, dispatch]);
|
|
//----------------------------------------------------------------
|
|
useEffect(() => {
|
|
if (formatierteDaten.length > 0) {
|
|
console.log(
|
|
"📊 Brush wird nach Modus-Wechsel aktualisiert:",
|
|
selectedMode
|
|
);
|
|
|
|
dispatch(
|
|
setBrushRange({
|
|
startIndex: 0,
|
|
endIndex: formatierteDaten.length - 1, // Stellt sicher, dass der Brush-Bereich korrekt gesetzt wird
|
|
startDate: new Date(formatierteDaten[0].zeit)
|
|
.toISOString()
|
|
.split("T")[0],
|
|
endDate: new Date(formatierteDaten[formatierteDaten.length - 1].zeit)
|
|
.toISOString()
|
|
.split("T")[0],
|
|
})
|
|
);
|
|
}
|
|
}, [selectedMode, formatierteDaten, dispatch]);
|
|
|
|
//--------------------------------------------------------------------------------
|
|
|
|
return (
|
|
<div style={{ width: "100%", height: isFullScreen ? "90%" : "400px" }}>
|
|
<ResponsiveContainer width="100%" height="100%">
|
|
<ComposedChart data={formatierteDaten}>
|
|
<CartesianGrid strokeDasharray="3 3" />
|
|
<XAxis
|
|
dataKey="zeit"
|
|
domain={["dataMin", "dataMax"]}
|
|
tickFormatter={(zeit) => {
|
|
const date = new Date(zeit);
|
|
return `${date.getDate()}.${date.getMonth() + 1}`;
|
|
}}
|
|
tick={(props) => {
|
|
const { x, y, payload } = props;
|
|
const date = new Date(payload.value);
|
|
return (
|
|
<text
|
|
x={x}
|
|
y={y}
|
|
dy={5}
|
|
textAnchor="end"
|
|
transform={`rotate(-25, ${x}, ${y})`}
|
|
>
|
|
{`${date.getDate()}.${date.getMonth() + 1}`}
|
|
</text>
|
|
);
|
|
}} // Dreht die Labels für bessere Übersicht
|
|
/>
|
|
|
|
<YAxis
|
|
label={{ value: unit, angle: -90, position: "insideLeft" }}
|
|
domain={["auto", "auto"]}
|
|
tickFormatter={(wert) => `${wert.toFixed(0)} `}
|
|
/>
|
|
<Tooltip content={<CustomTooltip unit={unit} />} />
|
|
<Legend
|
|
verticalAlign="top"
|
|
align="center"
|
|
content={({ payload }) => {
|
|
if (!payload) return null;
|
|
|
|
// Reihenfolge der Legende anpassen
|
|
const orderedPayload = [...payload].sort((a, b) => {
|
|
const order = [
|
|
"messwertMinimum",
|
|
"messwert",
|
|
"messwertDurchschnitt",
|
|
"messwertMaximum",
|
|
];
|
|
return order.indexOf(a.value) - order.indexOf(b.value);
|
|
});
|
|
|
|
return (
|
|
<div
|
|
style={{
|
|
width: "100%",
|
|
display: "flex",
|
|
justifyContent: "center",
|
|
}}
|
|
>
|
|
{orderedPayload.map((entry, index) => (
|
|
<span
|
|
key={index}
|
|
style={{ margin: "0 10px", color: entry.color }}
|
|
>
|
|
⬤ {entry.value}
|
|
</span>
|
|
))}
|
|
</div>
|
|
);
|
|
}}
|
|
/>
|
|
|
|
<Line
|
|
type="monotone"
|
|
dataKey="messwertMinimum"
|
|
stroke="lightgrey"
|
|
dot={false}
|
|
/>
|
|
<Line
|
|
type="monotone"
|
|
dataKey="messwertMaximum"
|
|
stroke="lightgrey"
|
|
dot={false}
|
|
/>
|
|
{["DIA1", "DIA2"].includes(selectedMode) && (
|
|
<Line
|
|
type="monotone"
|
|
dataKey="messwertDurchschnitt"
|
|
stroke="#00AEEF"
|
|
dot
|
|
/>
|
|
)}
|
|
{selectedMode === "DIA0" && (
|
|
<Line type="monotone" dataKey="messwert" stroke="#00AEEF" dot />
|
|
)}
|
|
<Brush
|
|
dataKey="zeit"
|
|
height={30}
|
|
stroke="#8884d8"
|
|
onChange={handleBrushChange}
|
|
startIndex={brushRange.startIndex}
|
|
endIndex={brushRange.endIndex || formatierteDaten.length - 1}
|
|
tickFormatter={(zeit) => new Date(zeit).toLocaleDateString()} // Datum statt Zahl
|
|
/>
|
|
</ComposedChart>
|
|
</ResponsiveContainer>
|
|
</div>
|
|
);
|
|
};
|
|
|
|
export default LoopMeasurementChart;
|