"use client"; // components/main/analogInputs/AnalogInputsChart.tsx import React, { useEffect, useRef } from "react"; import { useDispatch, useSelector } from "react-redux"; import { RootState, AppDispatch } from "@/redux/store"; import { Dialog } from "@headlessui/react"; import { Line } from "react-chartjs-2"; import { Chart as ChartJS, LineElement, PointElement, CategoryScale, LinearScale, Tooltip, Legend, Filler, TimeScale, TooltipItem, } from "chart.js"; import "chartjs-adapter-date-fns"; import { de } from "date-fns/locale"; import { Listbox } from "@headlessui/react"; import { getAnalogInputsHistoryThunk } from "@/redux/thunks/getAnalogInputsHistoryThunk"; import { setVonDatum, setBisDatum, setZeitraum, setAutoLoad, } from "@/redux/slices/analogInputs/analogInputsHistorySlice"; import { getColor } from "@/utils/colors"; import AnalogInputsDatePicker from "./AnalogInputsDatePicker"; import type { ChartJSOrUndefined } from "react-chartjs-2/dist/types"; // ✅ Nur die Basis-ChartJS-Module registrieren ChartJS.register( LineElement, PointElement, CategoryScale, LinearScale, Tooltip, Legend, Filler, TimeScale ); export default function AnalogInputsChart({ setLoading, loading, }: { setLoading: (loading: boolean) => void; loading: boolean; }) { useEffect(() => { if (typeof window !== "undefined") { import("chartjs-plugin-zoom").then((zoom) => { if (!ChartJS.registry.plugins.get("zoom")) { ChartJS.register(zoom.default); } }); } }, []); const dispatch = useDispatch(); const chartRef = useRef< ChartJSOrUndefined<"line", { x: Date; y: number | undefined }[], unknown> >(null); // Redux Werte für Chart-Daten const { zeitraum, vonDatum, bisDatum, data, autoLoad, selectedId } = useSelector((state: RootState) => state.analogInputsHistory); const selectedAnalogInput = useSelector( (state: RootState) => state.selectedAnalogInput ); // Redux initiale Datum-Werte const vonDatumRedux = useSelector( (state: RootState) => state.dateRangePicker.vonDatum ); const bisDatumRedux = useSelector( (state: RootState) => state.dateRangePicker.bisDatum ); // Hilfsfunktion für Default-Datum const getDefaultDate = (type: "from" | "to") => { const today = new Date(); if (type === "to") return today.toISOString().slice(0, 10); const fromDateObj = new Date(today); fromDateObj.setDate(today.getDate() - 30); return fromDateObj.toISOString().slice(0, 10); }; // ✅ Lokale States für Picker + Zeitraum const [localVonDatum, setLocalVonDatum] = React.useState( vonDatumRedux || getDefaultDate("from") ); const [localBisDatum, setLocalBisDatum] = React.useState( bisDatumRedux || getDefaultDate("to") ); const [localZeitraum, setLocalZeitraum] = React.useState(zeitraum); // Synchronisiere lokale Werte mit Redux (z.B. nach AutoLoad Reset) useEffect(() => { setLocalVonDatum(vonDatumRedux || getDefaultDate("from")); setLocalBisDatum(bisDatumRedux || getDefaultDate("to")); setLocalZeitraum(zeitraum); }, [vonDatumRedux, bisDatumRedux, zeitraum]); // Initiale Default-Werte: 30 Tage zurück (nur wenn Redux-Werte fehlen) useEffect(() => { if (!vonDatumRedux || !bisDatumRedux) { const today = new Date(); const toDate = today.toISOString().slice(0, 10); const fromDateObj = new Date(today); fromDateObj.setDate(today.getDate() - 30); const fromDate = fromDateObj.toISOString().slice(0, 10); setLocalVonDatum(fromDate); setLocalBisDatum(toDate); } }, [vonDatumRedux, bisDatumRedux]); // ✅ Nur lokale Änderung beim Picker const handleDateChange = (from: string, to: string) => { setLocalVonDatum(from); setLocalBisDatum(to); }; // ✅ Button → Redux + Fetch triggern const handleFetchData = () => { if (!selectedAnalogInput?.id) return; setLoading(true); // Set loading to true when fetching data // Fallback auf Redux-Werte, falls lokale Werte leer sind const from = localVonDatum || vonDatumRedux || ""; const to = localBisDatum || bisDatumRedux || ""; // Redux aktualisieren dispatch(setVonDatum(from)); dispatch(setBisDatum(to)); dispatch(setZeitraum(localZeitraum)); // Umgebung erkennen und URL generieren const isDev = window.location.hostname === "localhost" || window.location.hostname === "127.0.0.1"; let fetchUrl = ""; if (isDev) { fetchUrl = `/api/cpl/getAnalogInputsHistory?eingang=${selectedAnalogInput.id}&zeitraum=${localZeitraum}&von=${from}&bis=${to}`; } else { // Produktion: CPL-Webserver direkt abfragen const [vonJahr, vonMonat, vonTag] = from.split("-"); const [bisJahr, bisMonat, bisTag] = to.split("-"); const aeEingang = 100 + (selectedAnalogInput.id - 1); let diaType = "DIA1"; if (localZeitraum === "DIA0") diaType = "DIA0"; if (localZeitraum === "DIA2") diaType = "DIA2"; fetchUrl = `${window.location.origin}/CPL?seite.ACP&${diaType}=${vonJahr};${vonMonat};${vonTag};${bisJahr};${bisMonat};${bisTag};${aeEingang};1`; } console.log("Fetch-URL:", fetchUrl); // Thunk-Fetch mit neuen Werten dispatch( getAnalogInputsHistoryThunk({ eingang: selectedAnalogInput.id, zeitraum: localZeitraum, vonDatum: from, bisDatum: to, }) ).finally(() => setLoading(false)); // Reset loading after fetch }; // Auto-trigger fetch when a row is selected and id is not 0 (only once per selection) React.useEffect(() => { if (selectedAnalogInput?.id && selectedAnalogInput.id !== 0) { handleFetchData(); } // eslint-disable-next-line react-hooks/exhaustive-deps }, [selectedAnalogInput?.id]); // ✅ Chart-Daten aus Redux filtern (Chart reagiert nur nach Button) const chartKey = selectedAnalogInput?.id ? String(selectedAnalogInput.id + 99) : null; const inputData = chartKey ? data[chartKey] ?? [] : []; const filteredData = inputData.filter((point) => { const date = new Date(point.t); const from = vonDatumRedux ? new Date(vonDatumRedux) : null; const to = bisDatumRedux ? new Date(bisDatumRedux) : null; return (!from || date >= from) && (!to || date <= to); }); const memoizedChartData = React.useMemo(() => { return { datasets: filteredData.length > 0 ? zeitraum === "DIA0" ? [ { label: "Messwert Minimum ", // (i) data: filteredData .filter((p) => typeof p.i === "number") .map((p) => ({ x: new Date(p.t), y: p.i })), borderColor: "gray", borderWidth: 1, pointRadius: 0, tension: 0.1, order: 1, }, { label: selectedAnalogInput?.label ? //? `Messwert ${selectedAnalogInput.label}` // (m) `Messwert ` // (m) : "Messwert ", // (m) data: filteredData .filter((p) => typeof p.m === "number") .map((p) => ({ x: new Date(p.t), y: p.m })), borderColor: getColor("littwin-blue"), backgroundColor: "rgba(59,130,246,0.3)", borderWidth: 2, pointRadius: 0, tension: 0.1, order: 2, }, { label: "Messwert Maximum ", // (a) data: filteredData .filter((p) => typeof p.a === "number") .map((p) => ({ x: new Date(p.t), y: p.a })), borderColor: "gray", borderWidth: 1, pointRadius: 0, tension: 0.1, order: 3, }, ] : [ { label: "Messwert Minimum", // (i) data: filteredData .filter((p) => typeof p.i === "number") .map((p) => ({ x: new Date(p.t), y: p.i })), borderColor: "gray", borderWidth: 1, pointRadius: 0, tension: 0.1, order: 1, }, { label: "Durchschnitt", // (g) data: filteredData .filter((p) => typeof p.g === "number") .map((p) => ({ x: new Date(p.t), y: p.g })), borderColor: getColor("littwin-blue"), backgroundColor: "rgba(59,130,246,0.3)", borderWidth: 2, pointRadius: 0, tension: 0.1, order: 2, }, { label: "Messwert Maximum", // (a) data: filteredData .filter((p) => typeof p.a === "number") .map((p) => ({ x: new Date(p.t), y: p.a })), borderColor: "gray", borderWidth: 1, pointRadius: 0, tension: 0.1, order: 3, }, ] : [], }; }, [filteredData, zeitraum, selectedAnalogInput]); const memoizedChartOptions = React.useMemo(() => { return { responsive: true, maintainAspectRatio: false, plugins: { legend: { position: "top" as const }, tooltip: { mode: "index" as const, intersect: false, callbacks: { label: (context: TooltipItem<"line">) => { const label = context.dataset.label || ""; return `${label}: ${context.parsed.y}`; }, title: (items: TooltipItem<"line">[]) => { const date = items[0].parsed.x; return `Zeitpunkt: ${new Date(date).toLocaleString("de-DE")}`; }, }, }, title: { display: true, text: selectedAnalogInput?.label ? `Verlauf: ${selectedAnalogInput.label}` : "Messwert-Verlauf", }, zoom: { pan: { enabled: true, mode: "x" as const, }, zoom: { wheel: { enabled: true }, pinch: { enabled: true }, mode: "x" as const, }, }, }, scales: { x: { type: "time" as const, time: { unit: "day" as const, tooltipFormat: "dd.MM.yyyy HH:mm", displayFormats: { day: "dd.MM.yyyy", }, }, adapters: { date: { locale: de } }, title: { display: true, text: "Zeit" }, min: vonDatum ? new Date(vonDatum).getTime() : undefined, max: bisDatum ? new Date(bisDatum).getTime() : undefined, }, y: { title: { display: true, text: `Messwert ${selectedAnalogInput?.unit || ""}`, }, }, }, }; }, [vonDatum, bisDatum, selectedAnalogInput]); // ✅ AutoLoad nur beim ersten Laden useEffect(() => { if (autoLoad && selectedId) { dispatch( getAnalogInputsHistoryThunk({ eingang: selectedId, zeitraum, vonDatum, bisDatum, }) ); dispatch(setAutoLoad(false)); } }, [autoLoad, selectedId, dispatch, zeitraum, vonDatum, bisDatum]); // Dynamisches Importieren von chartjs-plugin-zoom nur im Browser useEffect(() => { if (typeof window !== "undefined") { import("chartjs-plugin-zoom").then((module) => { ChartJS.register(module.default); }); } }, []); return (
Eingang {selectedId ?? "–"}
{/* ✅ Neuer DatePicker mit schönem Styling (lokal, ohne Redux) */} {/* ✅ Zeitraum-Auswahl (Listbox nur lokal) */}
{localZeitraum === "DIA0" ? "Alle Messwerte" : localZeitraum === "DIA1" ? "Stündlich" : "Täglich"} {["DIA0", "DIA1", "DIA2"].map((option) => ( {option === "DIA0" ? "Alle Messwerte" : option === "DIA1" ? "Stündlich" : "Täglich"} ))}
{/* ✅ Button: lädt die Daten & aktualisiert Redux */}
{/* Chart-Anzeige */}
{!selectedAnalogInput?.id ? (
Bitte Eingang auswählen
) : ( )}
); }