From e341f4320400906cd48c36d3c98467c83c71a467 Mon Sep 17 00:00:00 2001 From: ISA Date: Tue, 29 Apr 2025 10:55:20 +0200 Subject: [PATCH] =?UTF-8?q?fix:=20Mock-Datenzugriff=20=C3=BCber=20API-Hand?= =?UTF-8?q?ler=20in=20Entwicklungsumgebung=20integriert?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit - fetchAnalogInputsHistoryService angepasst: nutzt /api/cpl/fetchAnalogInputsHistory bei NODE_ENV=development - Produktionsdaten weiterhin direkt vom CPL-Webserver über CGI-Endpunkte geladen - Chart- und Redux-Datenstrom jetzt vollständig stabil in Entwicklung und Produktion - Fehler beim direkten Zugriff auf Mock-Dateien in Pages Router Next.js behoben --- .../analogeEingaenge/AnalogInputsChart.tsx | 188 ++++++++++-------- config/webVersion.ts | 2 +- redux/slices/analogInputsHistorySlice.ts | 41 ++++ redux/store.ts | 2 + redux/thunks/fetchAnalogInputsHistoryThunk.ts | 15 ++ services/fetchAnalogInputsHistoryService.ts | 35 ++-- 6 files changed, 174 insertions(+), 109 deletions(-) create mode 100644 redux/slices/analogInputsHistorySlice.ts create mode 100644 redux/thunks/fetchAnalogInputsHistoryThunk.ts diff --git a/components/main/analogeEingaenge/AnalogInputsChart.tsx b/components/main/analogeEingaenge/AnalogInputsChart.tsx index 23f5f28..b612f77 100644 --- a/components/main/analogeEingaenge/AnalogInputsChart.tsx +++ b/components/main/analogeEingaenge/AnalogInputsChart.tsx @@ -1,109 +1,121 @@ -"use client"; -// components/main/analogeEingaenge/AnalogInputsChart.tsx -import React, { useState, useEffect } from "react"; +"use client"; // components/main/analogeEingaenge/AnalogInputsChart.tsx +import React, { useEffect } from "react"; +import { Line } from "react-chartjs-2"; import { - LineChart, - Line, - XAxis, - YAxis, - CartesianGrid, + Chart as ChartJS, + LineElement, + PointElement, + CategoryScale, + LinearScale, Tooltip, - ResponsiveContainer, -} from "recharts"; + Legend, + Filler, + TimeScale, +} from "chart.js"; +import "chartjs-adapter-date-fns"; +import { useSelector, useDispatch } from "react-redux"; +import type { RootState, AppDispatch } from "../../../redux/store"; +import { fetchAnalogInputsHistoryThunk } from "../../../redux/thunks/fetchAnalogInputsHistoryThunk"; -interface HistoryDataPoint { - t: string; // Timestamp - m: number; // Messwert (Value) - v: number; // Gültigkeit (0/1) -} +ChartJS.register( + LineElement, + PointElement, + CategoryScale, + LinearScale, + Tooltip, + Legend, + Filler, + TimeScale +); + +const colors = [ + "#007bff", + "#28a745", + "#dc3545", + "#ffc107", + "#17a2b8", + "#6f42c1", + "#fd7e14", + "#20c997", +]; export default function AnalogInputsChart() { - const [selectedInput, setSelectedInput] = useState(1); - const [historyData, setHistoryData] = useState([]); - const [isLoading, setIsLoading] = useState(false); - - // Simulierter API-Aufruf für Historische Daten - const fetchHistoryData = async (inputNumber: number) => { - setIsLoading(true); - try { - // Hier ersetzt du später mit deinem echten API-Call - // Beispiel: const response = await fetch(`/api/analogInputs/history?input=${inputNumber}`); - // const data = await response.json(); - - // Temporär: Dummy-Daten für Simulation - const now = new Date(); - const dummyData = Array.from({ length: 24 }, (_, i) => ({ - t: new Date(now.getTime() - i * 60 * 60 * 1000).toISOString(), // letzte 24 Stunden - m: Math.random() * 100, // Zufälliger Wert - v: 1, // Gültig - })).reverse(); - - setHistoryData(dummyData); - } catch (error) { - console.error("Fehler beim Laden der Historie:", error); - setHistoryData([]); - } finally { - setIsLoading(false); - } - }; + const dispatch = useDispatch(); + const { data, isLoading, error } = useSelector( + (state: RootState) => state.analogInputsHistory + ); useEffect(() => { - fetchHistoryData(selectedInput); - }, [selectedInput]); + dispatch(fetchAnalogInputsHistoryThunk()); + }, [dispatch]); - const handleInputChange = (e: React.ChangeEvent) => { - setSelectedInput(Number(e.target.value)); + const datasets = Object.entries(data).map(([key, inputData], index) => ({ + label: `Eingang ${Number(key) - 99}`, + data: inputData.map((point: any) => ({ + x: point.t, + y: point.m, + })), + fill: false, + borderColor: colors[index % colors.length], + backgroundColor: colors[index % colors.length], + tension: 0.3, + })); + + const chartData = { + datasets, + }; + + const chartOptions = { + responsive: true, + plugins: { + legend: { + position: "top" as const, + labels: { + usePointStyle: true, + }, + }, + tooltip: { + mode: "index" as const, + intersect: false, + }, + }, + scales: { + x: { + type: "time" as const, + time: { + unit: "hour", + tooltipFormat: "HH:mm", + displayFormats: { + hour: "HH:mm", + }, + }, + title: { + display: true, + text: "Zeit", + }, + }, + y: { + title: { + display: true, + text: "Messwert", + }, + }, + }, }; return (

- Historische Werte für Analogen Eingang der letzten 24 Stunden + Alle analogen Eingänge – Verlauf der letzten 24 Stunden

-
- - -
- {isLoading ? (
Lade Daten...
+ ) : error ? ( +
Fehler: {error}
) : ( -
- - - - str.substring(11, 16)} - /> - - `Zeit: ${label}`} /> - - - +
+
)}
diff --git a/config/webVersion.ts b/config/webVersion.ts index a8f28a3..3044dea 100644 --- a/config/webVersion.ts +++ b/config/webVersion.ts @@ -6,5 +6,5 @@ 2: Patch oder Hotfix (Bugfixes oder kleine Änderungen). */ -const webVersion = "1.6.305"; +const webVersion = "1.6.306"; export default webVersion; diff --git a/redux/slices/analogInputsHistorySlice.ts b/redux/slices/analogInputsHistorySlice.ts new file mode 100644 index 0000000..a7797d0 --- /dev/null +++ b/redux/slices/analogInputsHistorySlice.ts @@ -0,0 +1,41 @@ +// /redux/slices/analogInputsHistorySlice.ts +import { createSlice, PayloadAction } from "@reduxjs/toolkit"; +import { fetchAnalogInputsHistoryThunk } from "../thunks/fetchAnalogInputsHistoryThunk"; + +type InputHistoryState = { + data: Record; + isLoading: boolean; + error: string | null; +}; + +const initialState: InputHistoryState = { + data: {}, + isLoading: false, + error: null, +}; + +const analogInputsHistorySlice = createSlice({ + name: "analogInputsHistory", + initialState, + reducers: {}, + extraReducers: (builder) => { + builder + .addCase(fetchAnalogInputsHistoryThunk.pending, (state) => { + state.isLoading = true; + state.error = null; + }) + .addCase( + fetchAnalogInputsHistoryThunk.fulfilled, + (state, action: PayloadAction>) => { + state.data = action.payload; + state.isLoading = false; + } + ) + .addCase(fetchAnalogInputsHistoryThunk.rejected, (state, action) => { + state.isLoading = false; + state.error = action.payload as string; + }); + }, +}); + +export default analogInputsHistorySlice.reducer; diff --git a/redux/store.ts b/redux/store.ts index e8151bb..83fb31c 100644 --- a/redux/store.ts +++ b/redux/store.ts @@ -23,6 +23,7 @@ import tdmSingleChartReducer from "./slices/tdmSingleChartSlice"; import tdrReferenceChartDataBySlotReducer from "./slices/tdrReferenceChartDataBySlotSlice"; import loopChartTypeSlice from "./slices/loopChartTypeSlice"; import systemVoltTempReducer from "./slices/systemVoltTempSlice"; +import analogInputsHistoryReducer from "./slices/analogInputsHistorySlice"; const store = configureStore({ reducer: { @@ -48,6 +49,7 @@ const store = configureStore({ tdrReferenceChartDataBySlotSlice: tdrReferenceChartDataBySlotReducer, loopChartType: loopChartTypeSlice, systemVoltTemp: systemVoltTempReducer, + analogInputsHistory: analogInputsHistoryReducer, }, }); diff --git a/redux/thunks/fetchAnalogInputsHistoryThunk.ts b/redux/thunks/fetchAnalogInputsHistoryThunk.ts new file mode 100644 index 0000000..aaff7da --- /dev/null +++ b/redux/thunks/fetchAnalogInputsHistoryThunk.ts @@ -0,0 +1,15 @@ +// /redux/thunks/fetchAnalogInputsHistoryThunk.ts +import { createAsyncThunk } from "@reduxjs/toolkit"; +import { fetchAnalogInputsHistoryService } from "../../services/fetchAnalogInputsHistoryService"; + +export const fetchAnalogInputsHistoryThunk = createAsyncThunk( + "analogInputsHistory/fetch", + async (_, { rejectWithValue }) => { + try { + const data = await fetchAnalogInputsHistoryService(); + return data; + } catch (error: any) { + return rejectWithValue(error.message || "Unbekannter Fehler"); + } + } +); diff --git a/services/fetchAnalogInputsHistoryService.ts b/services/fetchAnalogInputsHistoryService.ts index 6a277dc..5835350 100644 --- a/services/fetchAnalogInputsHistoryService.ts +++ b/services/fetchAnalogInputsHistoryService.ts @@ -20,29 +20,24 @@ export async function fetchAnalogInputsHistoryService(): Promise< const isDev = process.env.NODE_ENV === "development"; + if (isDev) { + // ⬇️ ENTWICKLUNG: über API-Handler + const response = await fetch("/api/cpl/fetchAnalogInputsHistory"); + if (!response.ok) throw new Error("Fehler beim Laden der Mock-Daten"); + return await response.json(); + } + + // ⬇️ PRODUKTION: direkt vom CPL-Webserver holen for (let i = 0; i < 8; i++) { - const inputNumber = i + 1; // 1–8 - const sourceId = 99 + inputNumber; // 100–107 für AE1–AE8 + const inputNumber = i + 1; + const sourceId = 99 + inputNumber; try { - let data: any[]; - - if (isDev) { - // ⬇️ Lädt lokale Mock-Dateien (z. B. /api/cpl/fetchAnalogInputsHistory) - const response = await fetch( - `/apiMockData/analogInputsHistoryData/analogInput${inputNumber}.json` - ); - if (!response.ok) throw new Error("Mock-Daten nicht verfügbar"); - data = await response.json(); - } else { - // ⬇️ Holt Live-Daten über CPL-HTTP-Endpunkt - const url = `${window.location.origin}/CPL?Service/empty.acp&DIA0=${fromDate};${toDate};${sourceId};1`; - const response = await fetch(url); - if (!response.ok) - throw new Error(`Fehler bei AE${inputNumber}: ${response.status}`); - data = await response.json(); - } - + const url = `${window.location.origin}/CPL?Service/empty.acp&DIA0=${fromDate};${toDate};${sourceId};1`; + const response = await fetch(url); + if (!response.ok) + throw new Error(`Fehler bei AE${inputNumber}: ${response.status}`); + const data = await response.json(); result[sourceId] = data; } catch (error) { console.error(`❌ Fehler beim Laden von AE${inputNumber}:`, error);