From 732c9820b91a2663e33bce3937e98a9eb03590e5 Mon Sep 17 00:00:00 2001 From: ISA Date: Fri, 2 May 2025 10:34:03 +0200 Subject: [PATCH] feat(analogeEingaenge): Einstellungs-Modal mit Offset, Faktor, Name, Loggerintervall + Speichern in Mock-Datei mit Kommentaren --- CHANGELOG.md | 11 ++ README.md | 4 + .../SERVICE/analogeEingaengeMockData.js | 23 +-- .../analogeEingaenge/AnalogInputsChart.tsx | 2 +- .../AnalogInputsSettingsModal.tsx | 133 ++++++++++++++++++ .../AnalogeEingaengeTable.tsx | 48 +++++-- config/webVersion.ts | 2 +- pages/analogeEingaenge.tsx | 16 ++- .../updateAnalogInputsSettingsAPIHandler.ts | 88 ++++++++++++ 9 files changed, 297 insertions(+), 30 deletions(-) create mode 100644 components/main/analogeEingaenge/AnalogInputsSettingsModal.tsx create mode 100644 pages/api/cpl/updateAnalogInputsSettingsAPIHandler.ts diff --git a/CHANGELOG.md b/CHANGELOG.md index 6b52b6d..70e3040 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -4,6 +4,17 @@ Alle Änderungen und Versionen des CPLv4.0 Frontends chronologisch dokumentiert. --- +## [1.6.339] – 2025-05-02 + +### Hinzugefügt + +- Modal für analoge Eingänge: Einstellungen für Offset, Faktor, Bezeichnung und Loggerintervall jetzt verfügbar +- Änderungen werden in der Entwicklungsumgebung über die zentrale Mock-API `/api/cpl/updateAnalogInputsSettingsAPIHandler` gespeichert +- Unterstützt Speichern im Format `var xyz = [ ... ];` mit Kommentaren vor und nach dem Block +- Speichern löst automatischen Reload aus zur Anzeige der neuen Werte + +--- + ## [1.6.338] – 2025-05-02 ### Verbesserungen diff --git a/README.md b/README.md index 7ca9aeb..d54aa86 100644 --- a/README.md +++ b/README.md @@ -117,6 +117,10 @@ Beispielaufruf im DEV-Modus (über UI gesteuert, nicht manuell notwendig): - Neue Chart.js Visualisierung: - Beim Klick auf einen Eingang in der Tabelle wird nur dessen Verlaufskurve angezeigt - Der Verlauf bezieht sich auf die letzten 24 Stunden, dargestellt mit deutscher Zeitachse +- **Bearbeitbare Felder im Modal:** Name, Offset, Faktor, Loggerintervall +- Änderungen werden in der Entwicklungsumgebung direkt in `/apiMockData/SERVICE/analogeEingaengeMockData.js` gespeichert +- Speicherung erfolgt einzeilig im Format `var xyz = [1, 2, 3];`, Kommentare im File bleiben erhalten +- Nach dem Speichern wird die Seite automatisch neu geladen - Entwicklung mit Mock-Daten: - In der Entwicklungsumgebung werden Mock-Daten über den API-Handler `/api/cpl/fetchAnalogInputsHistory` geladen diff --git a/apiMockData/SERVICE/analogeEingaengeMockData.js b/apiMockData/SERVICE/analogeEingaengeMockData.js index c9bffdf..f785a12 100644 --- a/apiMockData/SERVICE/analogeEingaengeMockData.js +++ b/apiMockData/SERVICE/analogeEingaengeMockData.js @@ -1,23 +1,12 @@ // /apiMockData/SERVICE/analogeEingaengeMockData.js -var win_analogInputsValues = [ - 4.771072, 5.665244, 0.005467, -0.007468, 0.000002, 0.000001, 0.000001, - 0.000007, -]; -var win_analogInputsNames = [ - "AE 1", - "AE 2", - "AE 3", - "AE 4", - "AE 5", - "AE 6", - "AE 7", - "AE 8", -]; + +var win_analogInputsValues = [4.771072, 5.665244, 0.005467, -0.007468, 0.000002, 0.000001, 0.000001, 0.000007]; +var win_analogInputsNames = ["AE5", "AE 2", "AE 3", "AE 4", "AE 5", "AE 6", "AE 7", "AE 8"]; +var win_analogInputsOffset = [5, 0.0, 0.0, 0.0, 0.0, 0.0, 0.0, 0.0]; +var win_analogInputsFactor = [5, 1.0, 1.0, 1.0, 1.0, 1.0, 1.0, 1.0]; +var win_analogInputsloggerIntervall = [5, 10, 10, 10, 10, 10, 10, 10]; var win_analogInputsUnits = ["V", "V", "V", "V", "mA", "mA", "mA", "mA"]; -var win_analogInputsFactor = [1.0, 1.0, 1.0, 1.0, 1.0, 1.0, 1.0, 1.0]; -var win_analogInputsOffset = [0.0, 0.0, 0.0, 0.0, 0.0, 0.0, 0.0, 0.0]; var win_analogInputsWeighting = [0, 0, 0, 0, 0, 0, 0, 0]; -var win_analogInputsloggerIntervall = [10, 10, 10, 10, 10, 10, 10, 10]; /* ID (z. B. 1, 2, ... 8) → Identifikation des Eingangs diff --git a/components/main/analogeEingaenge/AnalogInputsChart.tsx b/components/main/analogeEingaenge/AnalogInputsChart.tsx index 6855122..2f7bba8 100644 --- a/components/main/analogeEingaenge/AnalogInputsChart.tsx +++ b/components/main/analogeEingaenge/AnalogInputsChart.tsx @@ -1,4 +1,4 @@ -"use client"; +"use client"; // /components/main/analogeEingaenge/AnalogInputsChart.tsx import React, { useEffect } from "react"; import { Line } from "react-chartjs-2"; import { diff --git a/components/main/analogeEingaenge/AnalogInputsSettingsModal.tsx b/components/main/analogeEingaenge/AnalogInputsSettingsModal.tsx new file mode 100644 index 0000000..167ddb6 --- /dev/null +++ b/components/main/analogeEingaenge/AnalogInputsSettingsModal.tsx @@ -0,0 +1,133 @@ +"use client"; // /components/main/analogeEingaenge/AnalogInputsSettingsModal.tsx +import React, { useEffect, useState } from "react"; + +interface Props { + selectedInput: any; + isOpen: boolean; + onClose: () => void; +} + +export default function AnalogInputsSettingsModal({ + selectedInput, + isOpen, + onClose, +}: Props) { + const [name, setName] = useState(""); + const [offset, setOffset] = useState(0); + const [factor, setFactor] = useState(1); + const [loggerInterval, setLoggerInterval] = useState(10); + const [isSaving, setIsSaving] = useState(false); + + useEffect(() => { + if (selectedInput && isOpen) { + setName(selectedInput.name || ""); + setOffset(selectedInput.offset || 0); + setFactor(selectedInput.factor || 1); + setLoggerInterval(selectedInput.loggerInterval || 10); + } + }, [selectedInput, isOpen]); + + if (!isOpen || !selectedInput) return null; + + const handleSave = async () => { + setIsSaving(true); + try { + await fetch("/api/cpl/updateAnalogInputsSettingsAPIHandler", { + method: "POST", + headers: { "Content-Type": "application/json" }, + body: JSON.stringify({ + updates: [ + { + key: "win_analogInputsNames", + index: selectedInput.id - 1, + value: name, + }, + { + key: "win_analogInputsOffset", + index: selectedInput.id - 1, + value: parseFloat(offset.toString()), + }, + { + key: "win_analogInputsFactor", + index: selectedInput.id - 1, + value: parseFloat(factor.toString()), + }, + { + key: "win_analogInputsloggerIntervall", + index: selectedInput.id - 1, + value: parseInt(loggerInterval.toString()), + }, + ], + }), + }); + alert("Einstellungen gespeichert."); + onClose(); + location.reload(); + } catch (err) { + alert("Fehler beim Speichern der Einstellungen."); + console.error(err); + } finally { + setIsSaving(false); + } + }; + + return ( +
+
+

+ Eingang {selectedInput.id} – Einstellungen +

+ + + setName(e.target.value)} + /> + + + setOffset(parseFloat(e.target.value))} + /> + + + setFactor(parseFloat(e.target.value))} + /> + + + setLoggerInterval(parseInt(e.target.value))} + /> + +
+ + +
+
+
+ ); +} diff --git a/components/main/analogeEingaenge/AnalogeEingaengeTable.tsx b/components/main/analogeEingaenge/AnalogeEingaengeTable.tsx index 1075966..45ed2e8 100644 --- a/components/main/analogeEingaenge/AnalogeEingaengeTable.tsx +++ b/components/main/analogeEingaenge/AnalogeEingaengeTable.tsx @@ -1,13 +1,19 @@ -"use client"; +"use client"; // /components/main/analogeEingaenge/AnalogeEingaengeTable.tsx import React, { useEffect } from "react"; import { useDispatch, useSelector } from "react-redux"; import { RootState, AppDispatch } from "../../../redux/store"; import { fetchAnalogeEingaengeThunk } from "../../../redux/thunks/fetchAnalogeEingaengeThunk"; +import { Icon } from "@iconify/react"; +import settingsIcon from "@iconify/icons-mdi/settings"; export default function AnalogeEingaengeTable({ setSelectedId, + setSelectedInput, + setIsSettingsModalOpen, }: { setSelectedId: (id: number) => void; + setSelectedInput: (input: any) => void; + setIsSettingsModalOpen: (open: boolean) => void; }) { const dispatch = useDispatch(); @@ -29,20 +35,44 @@ export default function AnalogeEingaengeTable({ Eingang Messwert Bezeichnung + Aktion {Object.values(analogeEingaenge) .filter((e) => e?.id !== null && e?.id !== undefined) .map((e, index) => ( - setSelectedId(e.id!)} - > - {e.id ?? "-"} - {e.value ?? "-"} - {e.name || "----"} + + setSelectedId(e.id!)} + > + {e.id ?? "-"} + + setSelectedId(e.id!)} + > + {e.value ?? "-"} + + setSelectedId(e.id!)} + > + {e.name || "----"} + + + + ))} diff --git a/config/webVersion.ts b/config/webVersion.ts index 759b428..743300d 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.338"; +const webVersion = "1.6.339"; export default webVersion; diff --git a/pages/analogeEingaenge.tsx b/pages/analogeEingaenge.tsx index 34aaf06..c6125b2 100644 --- a/pages/analogeEingaenge.tsx +++ b/pages/analogeEingaenge.tsx @@ -1,13 +1,15 @@ "use client"; ///pages/analogeEingaenge.tsx - import React, { useState, useEffect } from "react"; import AnalogeEingaengeTabelle from "../components/main/analogeEingaenge/AnalogeEingaengeTable"; import AnalogInputsChart from "../components/main/analogeEingaenge/AnalogInputsChart"; +import AnalogInputsSettingsModal from "../components/main/analogeEingaenge/AnalogInputsSettingsModal"; import { fetchAnalogeEingaengeThunk } from "../redux/thunks/fetchAnalogeEingaengeThunk"; import { useAppDispatch } from "../redux/store"; function AnalogeEingaenge() { const [selectedId, setSelectedId] = useState(null); + const [selectedInput, setSelectedInput] = useState(null); + const [isSettingsModalOpen, setIsSettingsModalOpen] = useState(false); const dispatch = useAppDispatch(); useEffect(() => { @@ -28,7 +30,11 @@ function AnalogeEingaenge() {

Analoge Eingänge

- +
@@ -39,6 +45,12 @@ function AnalogeEingaenge() {
+ + setIsSettingsModalOpen(false)} + /> ); } diff --git a/pages/api/cpl/updateAnalogInputsSettingsAPIHandler.ts b/pages/api/cpl/updateAnalogInputsSettingsAPIHandler.ts new file mode 100644 index 0000000..20c64ec --- /dev/null +++ b/pages/api/cpl/updateAnalogInputsSettingsAPIHandler.ts @@ -0,0 +1,88 @@ +// /pages/api/cpl/updateAnalogInputsSettingsAPIHandler.ts +import { NextApiRequest, NextApiResponse } from "next"; +import path from "path"; +import fs from "fs/promises"; + +export default async function handler( + req: NextApiRequest, + res: NextApiResponse +) { + if (req.method !== "POST") { + return res.status(405).json({ error: "Method not allowed" }); + } + + const filePath = path.join( + process.cwd(), + "apiMockData", + "SERVICE", + "analogeEingaengeMockData.js" + ); + + try { + const { updates } = req.body; + + if (!Array.isArray(updates)) { + return res.status(400).json({ error: "Ungültige Datenstruktur" }); + } + + const raw = await fs.readFile(filePath, "utf-8"); + + // Teile den Inhalt in: + // 1. Vor dem ersten var + // 2. Die var-Zuweisungen (Arrays) + // 3. Nach dem letzten var (z. B. Block-Kommentare) + + const varRegex = /var\s+(\w+)\s*=\s*\[([\s\S]*?)\];/g; + let match; + + const updateMap: Record = {}; + const variableLines: string[] = []; + let matchStartIndexes: number[] = []; + + while ((match = varRegex.exec(raw)) !== null) { + const [fullMatch, key, valuesBlock] = match; + const values = valuesBlock + .split(",") + .map((v) => v.trim()) + .filter((v) => v.length > 0); + + updateMap[key] = values; + variableLines.push(fullMatch); + matchStartIndexes.push(match.index); + } + + // Kommentare vor und nach den Variablen extrahieren + const firstVarIndex = matchStartIndexes[0] ?? 0; + const lastVarMatch = variableLines[variableLines.length - 1] ?? ""; + const lastVarIndex = raw.lastIndexOf(lastVarMatch); + const commentsBefore = raw.slice(0, firstVarIndex).trim(); + const commentsAfter = raw.slice(lastVarIndex + lastVarMatch.length).trim(); + + // Updates anwenden + for (const { key, index, value } of updates) { + if (!updateMap[key]) { + console.warn(`⚠️ Schlüssel '${key}' fehlt – wird übersprungen`); + continue; + } + updateMap[key][index] = + typeof value === "string" ? `"${value}"` : value.toString(); + } + + // Arrays immer einzeilig schreiben + const variablesBlock = Object.entries(updateMap) + .map(([key, values]) => `var ${key} = [${values.join(", ")}];`) + .join("\n"); + + const finalContent = + [commentsBefore, variablesBlock, commentsAfter] + .filter(Boolean) + .join("\n\n") + "\n"; + + await fs.writeFile(filePath, finalContent, "utf-8"); + + res.status(200).json({ message: "Mockdaten gespeichert." }); + } catch (err) { + console.error("❌ Fehler beim Schreiben:", err); + res.status(500).json({ error: "Interner Serverfehler" }); + } +}