diff --git a/.env.development b/.env.development index ba22d7f..72f4c0f 100644 --- a/.env.development +++ b/.env.development @@ -6,6 +6,6 @@ NEXT_PUBLIC_USE_MOCK_BACKEND_LOOP_START=false NEXT_PUBLIC_EXPORT_STATIC=false NEXT_PUBLIC_USE_CGI=false # App-Versionsnummer -NEXT_PUBLIC_APP_VERSION=1.6.661 +NEXT_PUBLIC_APP_VERSION=1.6.662 NEXT_PUBLIC_CPL_MODE=json # json (Entwicklungsumgebung) oder jsSimulatedProd (CPL ->CGI-Interface-Simulator) oder production (CPL-> CGI-Interface Platzhalter) diff --git a/.env.production b/.env.production index 6550f5d..530cd9e 100644 --- a/.env.production +++ b/.env.production @@ -5,5 +5,5 @@ NEXT_PUBLIC_CPL_API_PATH=/CPL NEXT_PUBLIC_EXPORT_STATIC=true NEXT_PUBLIC_USE_CGI=true # App-Versionsnummer -NEXT_PUBLIC_APP_VERSION=1.6.661 +NEXT_PUBLIC_APP_VERSION=1.6.662 NEXT_PUBLIC_CPL_MODE=production \ No newline at end of file diff --git a/CHANGELOG.md b/CHANGELOG.md index 8efb594..0803a8b 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -1,3 +1,8 @@ +## [1.6.662] – 2025-07-31 + +- Feat: KVz Bereich in EinstellungsModal in KÜs Modal + +--- ## [1.6.661] – 2025-07-31 - feat: TDR starten Button in KÜ Chart diff --git a/components/main/fall-detection-sensors/FallSensors.tsx b/components/main/fall-detection-sensors/FallSensors.tsx index e2b67d9..114280e 100644 --- a/components/main/fall-detection-sensors/FallSensors.tsx +++ b/components/main/fall-detection-sensors/FallSensors.tsx @@ -1,25 +1,49 @@ import React from "react"; +import { useSelector } from "react-redux"; +import { RootState } from "../../../redux/store"; + // components/main/fall-detection-sensors/FallSensors.tsx -const FallSensors = () => { - const sensors = [ - { id: "KVZ1", status: "inactive" }, - { id: "KVZ2", status: "active" }, - { id: "KVZ3", status: "active" }, - { id: "KVZ4", status: "active" }, - ]; +interface FallSensorsProps { + slotIndex: number; +} + +const FallSensors: React.FC = ({ slotIndex }) => { + const { kvzStatus } = useSelector((state: RootState) => state.kueDataSlice); + + // Nur 4 LEDs für den spezifischen Slot anzeigen + const leds = Array.from({ length: 4 }, (_, ledIndex) => { + const arrayIndex = slotIndex * 4 + ledIndex; + const ledValue = kvzStatus?.[arrayIndex]; + + // LED Status: 1 = grün, 0 = rot, 2 = grau, undefined = grau + if (ledValue === 1) return "green"; + if (ledValue === 0) return "red"; + return "gray"; // für 2 oder undefined + }); return ( -
- {sensors.map((sensor) => ( -
- {sensor.id} +
+ {leds.map((ledStatus, ledIndex) => { + // LED Farben: grün (1), rot (0), grau (2) + let bgColor = "bg-gray-400"; // Standard grau + let statusText = "Unbekannt"; + + if (ledStatus === "green") { + bgColor = "bg-green-500"; + statusText = "Ein"; + } else if (ledStatus === "red") { + bgColor = "bg-red-500"; + statusText = "Aus"; + } + + return (
-
- ))} + key={ledIndex} + className={`w-3 h-3 rounded-full border-2 border-gray-300 shadow-sm ${bgColor} transition-all duration-200 hover:scale-110 flex-shrink-0`} + title={`Slot ${slotIndex} LED${ledIndex + 1}: ${statusText}`} + /> + ); + })}
); }; diff --git a/components/main/kabelueberwachung/kue705FO/Kue705FO.tsx b/components/main/kabelueberwachung/kue705FO/Kue705FO.tsx index 3286323..2054423 100644 --- a/components/main/kabelueberwachung/kue705FO/Kue705FO.tsx +++ b/components/main/kabelueberwachung/kue705FO/Kue705FO.tsx @@ -24,6 +24,7 @@ import useKueVersion from "./hooks/useKueVersion"; import useIsoDisplay from "./hooks/useIsoDisplay"; import useLoopDisplay from "./hooks/useLoopDisplay"; import useModulName from "./hooks/useModulName"; +import { useAdminAuth } from "../../settingsPageComponents/hooks/useAdminAuth"; //--------handlers---------------- // Keep needed imports @@ -48,6 +49,9 @@ const Kue705FO: React.FC = ({ const dispatch = useDispatch(); const { kueName } = useSelector((state: RootState) => state.kueDataSlice); + // Admin authentication hook for security - using showModal as true for continuous auth check + const { isAdminLoggedIn } = useAdminAuth(true); + const [activeButton, setActiveButton] = useState<"Schleife" | "TDR" | "ISO">( "Schleife" ); @@ -79,7 +83,9 @@ const Kue705FO: React.FC = ({ kueOverflow: kueOverflowRaw, kuePSTmMinus96V, // <- richtig, weil so im State vorhanden tdrActive, // <- TDR aktiv Status hinzugefügt - win_fallSensorsActive, // <- KVz aktiv Status hinzugefügt + kvzPresence, // <- KVz Presence Array hinzugefügt + kvzActive, // <- KVz Active Array hinzugefügt + kvzStatus, // <- KVz LED Status Array hinzugefügt } = useSelector((state: RootState) => state.kueDataSlice); //--------------------------------------------- @@ -228,8 +234,17 @@ const Kue705FO: React.FC = ({ // TDR aktiv Status für diesen Slot prüfen const isTdrActiveForSlot = tdrActive?.[slotIndex] === 1; - // KVz aktiv Status für diesen Slot prüfen - const isKvzActiveForSlot = win_fallSensorsActive?.[slotIndex] === 1; + // KVz aktiv Status für diesen Slot prüfen - nur wenn Admin authentifiziert ist, KVz vorhanden ist UND aktiviert ist + const isKvzActiveForSlot = + kvzPresence?.[slotIndex] === 1 && + kvzActive?.[slotIndex] === 1 && + isAdminLoggedIn; + + // KVz LED Status abrufen (4 LEDs pro Slot) + const getKvzLedStatus = (ledIndex: number) => { + const arrayIndex = slotIndex * 4 + ledIndex; + return kvzStatus?.[arrayIndex] === 1; + }; // Removed useChartData(loopMeasurementCurveChartData) as the state was unused @@ -453,7 +468,7 @@ const Kue705FO: React.FC = ({ {/* KVz Panel - Anzeige ganz unten, nur wenn KVz aktiv ist */} {showKvzPanel && isKvzActiveForSlot && (
- +
)} diff --git a/components/main/kabelueberwachung/kue705FO/modals/KvzModalView.tsx b/components/main/kabelueberwachung/kue705FO/modals/KvzModalView.tsx index 54ab098..2dbf2ff 100644 --- a/components/main/kabelueberwachung/kue705FO/modals/KvzModalView.tsx +++ b/components/main/kabelueberwachung/kue705FO/modals/KvzModalView.tsx @@ -1,9 +1,9 @@ "use client"; import React, { useState } from "react"; -import { useSelector, useDispatch } from "react-redux"; -import { RootState } from "../../../../../redux/store"; -import { setKueData } from "../../../../../redux/slices/kueDataSlice"; +import { useSelector } from "react-redux"; +import { RootState, useAppDispatch } from "../../../../../redux/store"; +import { updateKvzData } from "../../../../../redux/thunks/kvzThunks"; import { useAdminAuth } from "../../../settingsPageComponents/hooks/useAdminAuth"; type KvzData = { @@ -11,12 +11,6 @@ type KvzData = { kvzSettings: string; }; -declare global { - interface Window { - __kvzCache?: Record; - } -} - interface Props { slot: number; onClose?: () => void; @@ -24,153 +18,93 @@ interface Props { export default function KvzModalView({ slot, onClose }: Props) { const { isAdminLoggedIn } = useAdminAuth(true); - const dispatch = useDispatch(); + const dispatch = useAppDispatch(); const kvzSlice = useSelector((state: RootState) => state.kueDataSlice); - const cacheKey = `kvz_slot_${slot}`; - if (typeof window !== "undefined") { - window.__kvzCache = window.__kvzCache || {}; - } - const cachedKvz = - typeof window !== "undefined" - ? window.__kvzCache?.[cacheKey] ?? null - : null; + // KVZ System: 32 Slots mit je 4 LEDs + const isKvzPresent = kvzSlice.kvzPresence?.[slot] === 1; + const isKvzActive = kvzSlice.kvzActive?.[slot] === 1; - const [kvzData] = useState( - () => - cachedKvz?.data || { - kvzSettings: "", // Placeholder für zukünftige Einstellungen - } - ); - - const [kvzActive, setKvzActive] = useState( - () => cachedKvz?.kvzActive ?? kvzSlice.win_fallSensorsActive?.[slot] === 1 - ); - - const updateCache = (data: typeof kvzData, active = kvzActive) => { - if (typeof window !== "undefined") { - (window.__kvzCache ??= {})[cacheKey] = { - data, - kvzActive: active, - }; - } + // LED Status für diesen Slot (4 LEDs pro Slot) + const getKvzLedStatus = (ledIndex: number) => { + const arrayIndex = slot * 4 + ledIndex; + return kvzSlice.kvzStatus?.[arrayIndex] === 1; }; - const handleKvzToggle = () => { - const newState = !kvzActive; - setKvzActive(newState); - updateCache(kvzData, newState); + const [localKvzActive, setLocalKvzActive] = useState(() => isKvzActive); - // Redux State sofort aktualisieren für UI-Update - const updatedKvzActive = [...(kvzSlice.win_fallSensorsActive || [])]; - updatedKvzActive[slot] = newState ? 1 : 0; - dispatch(setKueData({ win_fallSensorsActive: updatedKvzActive })); + // Synchronisiere localState mit Redux State + React.useEffect(() => { + setLocalKvzActive(isKvzActive); + }, [isKvzActive]); - const isDev = window.location.hostname === "localhost"; - const slotParam = `KVZ${slot}=${newState ? 1 : 0}`; + const handleKvzToggle = async () => { + const newState = !localKvzActive; + setLocalKvzActive(newState); + + try { + // API Update mit neuem Thunk - kvzActive statt kvzPresence + await dispatch( + updateKvzData([{ key: "kvzActive", slot, value: newState ? 1 : 0 }]) + ); - const reloadAfterConfirm = () => { const msg = newState ? "✅ KVz wurde aktiviert." : "⚠️ KVz wurde deaktiviert."; alert(msg); location.reload(); - }; - - if (isDev) { - const updates = [ - { key: "win_fallSensorsActive", slot, value: newState ? 1 : 0 }, - ]; - - fetch("/api/cpl/updateKvzSettingsDataAPIHandler", { - method: "POST", - headers: { "Content-Type": "application/json" }, - body: JSON.stringify({ updates }), - }) - .then((res) => res.json()) - .then(() => { - console.log("KVz-Aktiv-Status gespeichert."); - reloadAfterConfirm(); - }) - .catch((err) => { - console.error("Fehler beim Speichern von KVz aktiv:", err); - }); - } else { - const url = `${window.location.origin}/CPL?/kabelueberwachung.html&${slotParam}`; - fetch(url) - .then((res) => { - if (!res.ok) throw new Error("KVz-Befehl fehlgeschlagen"); - console.log("KVz aktiviert/deaktiviert:", res.status); - reloadAfterConfirm(); - }) - .catch((err) => { - console.error("Fehler beim KVz-Befehl:", err); - alert("Fehler beim Umschalten der KVz-Funktion."); - }); + } catch (error) { + console.error("Fehler beim KVz-Toggle:", error); + alert("Fehler beim Umschalten der KVz-Funktion."); + // State zurücksetzen bei Fehler + setLocalKvzActive(!newState); } }; - const handleSave = () => { - const isDev = window.location.hostname === "localhost"; - - if (isDev) { - const updates = [ - { key: "win_fallSensorsActive", slot, value: kvzActive ? 1 : 0 }, - // Hier können später weitere KVz-Einstellungen hinzugefügt werden - ]; - - fetch("/api/cpl/updateKvzSettingsDataAPIHandler", { - method: "POST", - headers: { "Content-Type": "application/json" }, - body: JSON.stringify({ updates }), - }) - .then((res) => res.json()) - .then(() => { - alert("KVz-Einstellungen erfolgreich gespeichert."); - if (typeof onClose === "function") onClose(); - }) - .catch((err) => { - console.error("Fehler beim Speichern:", err); - alert("Speichern fehlgeschlagen."); - }); - } else { - // Originaler Webservice-Teil für Produktionsumgebung - alert("KVz-Einstellungen gespeichert."); - if (typeof onClose === "function") onClose(); - } - - updateCache(kvzData, kvzActive); - }; - return (
- {/* KVz-Funktion */} - {isAdminLoggedIn && ( + {/* KVz-Funktion - nur anzeigen wenn KVZ vorhanden ist */} + {isAdminLoggedIn && isKvzPresent && (
KVz-Funktion:
- {kvzActive ? "aktiviert" : "deaktiviert"} + {localKvzActive ? "aktiviert" : "deaktiviert"}
)} + {/* Meldung wenn KVZ nicht vorhanden */} + {!isKvzPresent && ( +
+
+ ⚠️ +

+ Kein KVZ-Gerät vorhanden +
+ Für Slot {slot + 1} ist kein KVZ-Gerät installiert oder + konfiguriert. +

+
+
+ )} + {/* Zukünftige KVz-Einstellungen können hier hinzugefügt werden */} {!isAdminLoggedIn && (
diff --git a/components/main/settingsPageComponents/hooks/useAdminAuth.ts b/components/main/settingsPageComponents/hooks/useAdminAuth.ts index a9d0c4f..7f7d53f 100644 --- a/components/main/settingsPageComponents/hooks/useAdminAuth.ts +++ b/components/main/settingsPageComponents/hooks/useAdminAuth.ts @@ -29,6 +29,17 @@ export function useAdminAuth(showModal: boolean) { function logoutAdmin() { sessionStorage.removeItem("token"); localStorage.setItem("isAdminLoggedIn", "false"); + + // KVz localStorage-Werte löschen für alle Slots + const keysToRemove = []; + for (let i = 0; i < localStorage.length; i++) { + const key = localStorage.key(i); + if (key && key.startsWith("kvz_slot_")) { + keysToRemove.push(key); + } + } + keysToRemove.forEach((key) => localStorage.removeItem(key)); + setAdminLoggedIn(false); } diff --git a/docs/KVZ/kvz-system-understanding.md b/docs/KVZ/kvz-system-understanding.md new file mode 100644 index 0000000..035362a --- /dev/null +++ b/docs/KVZ/kvz-system-understanding.md @@ -0,0 +1,142 @@ +# KVZ System - Mein aktuelles Verständnis + +## System Übersicht + +```mermaid +graph TB + subgraph "Kabelüberwachung System (32 Slots)" + Slot0["Slot 0
Kabelüberwachung"] + Slot1["Slot 1
Kabelüberwachung"] + Slot2["Slot 2
Kabelüberwachung"] + Slot3["Slot 3
Kabelüberwachung"] + SlotDots["..."] + Slot31["Slot 31
Kabelüberwachung"] + end + + subgraph "KVZ Geräte (Optional pro Slot)" + KVZ0["KVZ Gerät
für Slot 0"] + KVZ1["KVZ Gerät
für Slot 1"] + KVZ2["KVZ Gerät
für Slot 2"] + KVZ3["KVZ Gerät
für Slot 3"] + end + + subgraph "KVZ LEDs (4 pro KVZ Gerät)" + subgraph "KVZ0 LEDs" + LED0_0["LED 1"] + LED0_1["LED 2"] + LED0_2["LED 3"] + LED0_3["LED 4"] + end + + subgraph "KVZ1 LEDs" + LED1_0["LED 1"] + LED1_1["LED 2"] + LED1_2["LED 3"] + LED1_3["LED 4"] + end + end + + Slot0 -.-> KVZ0 + Slot1 -.-> KVZ1 + Slot2 -.-> KVZ2 + Slot3 -.-> KVZ3 + + KVZ0 --> LED0_0 + KVZ0 --> LED0_1 + KVZ0 --> LED0_2 + KVZ0 --> LED0_3 + + KVZ1 --> LED1_0 + KVZ1 --> LED1_1 + KVZ1 --> LED1_2 + KVZ1 --> LED1_3 +``` + +## Redux Data Structure - Mein Verständnis + +```mermaid +graph TB + subgraph "Redux Store" + subgraph "kvzPresence Array (32 Elemente)" + P0["Index 0: 1
(KVZ vorhanden)"] + P1["Index 1: 0
(KVZ nicht vorhanden)"] + P2["Index 2: 0"] + P3["Index 3: 0"] + PDots["..."] + P31["Index 31: 0"] + end + + subgraph "kvzStatus Array (128 Elemente)" + subgraph "Slot 0 LEDs (Index 0-3)" + S0_0["Index 0: 1 (grün)"] + S0_1["Index 1: 0 (rot)"] + S0_2["Index 2: 1 (grün)"] + S0_3["Index 3: 0 (rot)"] + end + + subgraph "Slot 1 LEDs (Index 4-7)" + S1_0["Index 4: 0"] + S1_1["Index 5: 0"] + S1_2["Index 6: 0"] + S1_3["Index 7: 0"] + end + + StatusDots["...weitere 120 Elemente"] + end + end +``` + +## UI Darstellung - Mein aktuelles Verständnis + +```mermaid +graph LR + subgraph "FallSensors UI Component" + subgraph "Aktuelle Implementierung (FALSCH?)" + UI1["KVZ1: 🟢
(kvzPresence[0] = 1)"] + UI2["KVZ2: 🔴
(kvzPresence[1] = 0)"] + UI3["KVZ3: 🔴
(kvzPresence[2] = 0)"] + UI4["KVZ4: 🔴
(kvzPresence[3] = 0)"] + end + end + + subgraph "Problem" + Problem["Alle KVZ zeigen den gleichen Status
basierend auf kvzPresence Array
→ NICHT korrekt!"] + end + + UI1 -.-> Problem + UI2 -.-> Problem + UI3 -.-> Problem + UI4 -.-> Problem +``` + +## Fragen zu meinem Verständnis + +1. **KVZ Geräte Zuordnung**: + + - Ist ein KVZ-Gerät einem Slot zugeordnet oder unabhängig? + - Wie viele KVZ-Geräte gibt es insgesamt? + +2. **UI KVZ1-KVZ4**: + + - Repräsentieren KVZ1-KVZ4 in der UI die ersten 4 Slots (0-3)? + - Oder sind es 4 separate, unabhängige KVZ-Geräte? + +3. **LED Status Mapping**: + + - Welche LED von welchem KVZ soll in KVZ1, KVZ2, KVZ3, KVZ4 angezeigt werden? + - Soll jedes UI-KVZ eine andere LED des gleichen Geräts zeigen? + - Oder soll jedes UI-KVZ ein anderes KVZ-Gerät repräsentieren? + +4. **kvzStatus Array**: + - Wie soll das 128-Element Array für die UI-Darstellung genutzt werden? + - Welche Indizes entsprechen welchen UI-Elementen? + +## Verdacht + +Ich vermute, dass mein aktueller Ansatz falsch ist, weil: + +- KVZ2 sollte nicht eine Kopie von KVZ1 Status sein +- Jedes KVZ in der UI sollte einen eigenen, unabhängigen Status haben +- Die Zuordnung zwischen Redux Arrays und UI ist unklar + +**Bitte korrigieren Sie mein Verständnis! 🤔** diff --git a/docs/KVZ/mock-data.md b/docs/KVZ/mock-data.md new file mode 100644 index 0000000..1ac9cad --- /dev/null +++ b/docs/KVZ/mock-data.md @@ -0,0 +1,141 @@ +# KVZ Mock Data - Dokumentation + +## Mock-Daten Struktur + +Die KVZ Mock-Daten befinden sich in `mocks/kvzData.json` und haben folgende Struktur: + +### Beispiel-Daten + +```json +{ + "kvzPresence": [1, 0, 1, 0, ...], // 32 Elemente + "kvzStatus": [1, 0, 1, 0, ...], // 128 Elemente + "timestamp": "2025-01-31T12:00:00.000Z" +} +``` + +### kvzPresence Array (32 Elemente) + +- **Index 0-31**: Repräsentiert Slots 0-31 +- **Wert 1**: KVZ-Gerät vorhanden +- **Wert 0**: KVZ-Gerät nicht vorhanden + +**Aktuelle Mock-Daten:** + +- Slot 0: KVZ vorhanden (1) +- Slot 1: KVZ nicht vorhanden (0) +- Slot 2: KVZ vorhanden (1) +- Slot 3-31: KVZ nicht vorhanden (0) + +### kvzStatus Array (128 Elemente) + +- **Index Berechnung**: `slotIndex * 4 + ledIndex` +- **4 LEDs pro Slot**: LED 0, LED 1, LED 2, LED 3 +- **Wert 1**: LED aktiv (grün) +- **Wert 0**: LED inaktiv (rot) + +**Aktuelle Mock-Daten:** + +- Slot 0 LEDs (Index 0-3): [1, 0, 1, 0] → LED1=grün, LED2=rot, LED3=grün, LED4=rot +- Slot 1 LEDs (Index 4-7): [0, 0, 0, 0] → Alle LEDs rot (KVZ nicht vorhanden) +- Slot 2 LEDs (Index 8-11): [1, 1, 0, 1] → LED1=grün, LED2=grün, LED3=rot, LED4=grün +- Slot 3-31: Alle LEDs rot (0) + +## API Endpunkte + +### GET /api/kvz/data + +Holt alle KVZ-Daten. + +**Response:** + +```json +{ + "kvzPresence": [...], + "kvzStatus": [...], + "timestamp": "2025-01-31T12:00:00.000Z" +} +``` + +### POST /api/kvz/data + +Ersetzt komplette KVZ-Daten. + +**Request Body:** + +```json +{ + "kvzPresence": [...], + "kvzStatus": [...] +} +``` + +### POST /api/kvz/updateSettings + +Aktualisiert spezifische KVZ-Einstellungen. + +**Request Body:** + +```json +{ + "updates": [ + { + "key": "kvzPresence", + "slot": 0, + "value": 1 + }, + { + "key": "kvzStatus", + "slot": 0, + "ledIndex": 1, + "value": 1 + } + ] +} +``` + +## Service Functions + +### fetchKvzData() + +```typescript +import { fetchKvzData } from "../services/fetchKvzDataService"; + +const data = await fetchKvzData(); +console.log(data.kvzPresence); // [1, 0, 1, 0, ...] +``` + +### updateKvzSettings() + +```typescript +import { updateKvzSettings } from "../services/fetchKvzDataService"; + +await updateKvzSettings([ + { key: "kvzPresence", slot: 0, value: 1 }, + { key: "kvzStatus", slot: 0, ledIndex: 1, value: 1 }, +]); +``` + +## Redux Integration + +Die Mock-Daten können in Redux geladen werden: + +```typescript +// In einem Thunk oder useEffect +const data = await fetchKvzData(); +dispatch( + setKueData({ + kvzPresence: data.kvzPresence, + kvzStatus: data.kvzStatus, + }) +); +``` + +## Testen + +Die Mock-Daten ermöglichen es: + +1. **KVZ-Geräte zu simulieren** (Slots 0 und 2 haben KVZ) +2. **LED-Status zu testen** (verschiedene Kombinationen) +3. **API-Updates zu testen** (Presence und Status ändern) +4. **UI-Verhalten zu validieren** (bedingte Anzeige, Farben) diff --git a/mocks/device-cgi-simulator/kvz/kvzData.json b/mocks/device-cgi-simulator/kvz/kvzData.json new file mode 100644 index 0000000..c80c4cb --- /dev/null +++ b/mocks/device-cgi-simulator/kvz/kvzData.json @@ -0,0 +1,24 @@ +{ + "kvzPresence": [ + 1, 1, 1, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, + 0, 0, 0, 0, 0, 0, 0 + ], + "kvzActive": [ + 1, 1, 1, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, + 0, 0, 0, 0, 0, 0, 0 + ], + "kvzStatus": [ + 1, 0, 2, 1, 2, 0, 1, 0, 2, 1, 0, 2, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, + 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, + 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, + 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, + 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, + 0, 0, 0 + ], + "timestamp": "2025-07-31T11:39:22.951Z", + "description": { + "kvzPresence": "32 Slots: 1=KVZ Gerät vorhanden, 0=nicht vorhanden. Slots 0,2 haben KVZ-Geräte", + "kvzActive": "32 Slots: 1=KVZ aktiviert, 0=deaktiviert. Nur Slot 0 ist aktiviert", + "kvzStatus": "128 LEDs: 4 LEDs pro Slot. Slot 0: [1,0,1,0], Slot 2: [1,1,0,1] (aber Slot 2 ist deaktiviert)" + } +} diff --git a/package-lock.json b/package-lock.json index ec2ecdd..ef23d9a 100644 --- a/package-lock.json +++ b/package-lock.json @@ -1,12 +1,12 @@ { "name": "cpl-v4", - "version": "1.6.661", + "version": "1.6.662", "lockfileVersion": 3, "requires": true, "packages": { "": { "name": "cpl-v4", - "version": "1.6.661", + "version": "1.6.662", "dependencies": { "@fontsource/roboto": "^5.1.0", "@headlessui/react": "^2.2.4", diff --git a/package.json b/package.json index 43c3921..90e07cb 100644 --- a/package.json +++ b/package.json @@ -1,6 +1,6 @@ { "name": "cpl-v4", - "version": "1.6.661", + "version": "1.6.662", "private": true, "scripts": { "dev": "next dev", diff --git a/pages/_app.tsx b/pages/_app.tsx index 292e945..f080ee5 100644 --- a/pages/_app.tsx +++ b/pages/_app.tsx @@ -26,6 +26,7 @@ import { getAllTDRReferenceChartThunk } from "@/redux/thunks/getAllTDRReferenceC import { getTDRChartDataByIdThunk } from "@/redux/thunks/getTDRChartDataByIdThunk"; import { getLoopChartDataThunk } from "@/redux/thunks/getLoopChartDataThunk"; import { getAuthThunks } from "@/redux/thunks/getAuthThunks"; +import { loadKvzData } from "@/redux/thunks/kvzThunks"; import Modal from "react-modal"; import { ToastContainer } from "react-toastify"; import "react-toastify/dist/ReactToastify.css"; @@ -78,6 +79,10 @@ function AppContent({ const loadAndDispatch = () => { dispatch(getAuthThunks()); + + // KVZ-Daten beim Start laden + dispatch(loadKvzData()); + if (pathname.includes("kabelueberwachung")) { dispatch(getKueDataThunk()); } else if (pathname.includes("analogInputs")) { diff --git a/pages/api/kvz/data.ts b/pages/api/kvz/data.ts new file mode 100644 index 0000000..c323769 --- /dev/null +++ b/pages/api/kvz/data.ts @@ -0,0 +1,101 @@ +// pages/api/kvz/data.ts +import type { NextApiRequest, NextApiResponse } from "next"; +import fs from "fs"; +import path from "path"; + +export interface KvzData { + kvzPresence: number[]; + kvzActive: number[]; + kvzStatus: number[]; + timestamp: string; + description?: { + kvzPresence: string; + kvzActive: string; + kvzStatus: string; + }; +} + +export default function handler( + req: NextApiRequest, + res: NextApiResponse +) { + if (req.method === "GET") { + try { + const filePath = path.join( + process.cwd(), + "mocks", + "device-cgi-simulator", + "kvz", + "kvzData.json" + ); + const fileContents = fs.readFileSync(filePath, "utf8"); + const kvzData: KvzData = JSON.parse(fileContents); + + // Update timestamp to current time + kvzData.timestamp = new Date().toISOString(); + + res.status(200).json(kvzData); + } catch (error) { + console.error("Fehler beim Lesen der KVZ-Daten:", error); + res.status(500).json({ error: "Fehler beim Laden der KVZ-Daten" }); + } + } else if (req.method === "POST") { + try { + const { kvzPresence, kvzActive, kvzStatus } = req.body; + + if ( + !Array.isArray(kvzPresence) || + !Array.isArray(kvzActive) || + !Array.isArray(kvzStatus) + ) { + return res.status(400).json({ error: "Ungültige Datenstruktur" }); + } + + if (kvzPresence.length !== 32) { + return res + .status(400) + .json({ error: "kvzPresence muss 32 Elemente haben" }); + } + + if (kvzActive.length !== 32) { + return res + .status(400) + .json({ error: "kvzActive muss 32 Elemente haben" }); + } + + if (kvzStatus.length !== 128) { + return res + .status(400) + .json({ error: "kvzStatus muss 128 Elemente haben" }); + } + + const filePath = path.join( + process.cwd(), + "mocks", + "device-cgi-simulator", + "kvz", + "kvzData.json" + ); + const fileContents = fs.readFileSync(filePath, "utf8"); + const existingData: KvzData = JSON.parse(fileContents); + + const updatedData: KvzData = { + ...existingData, + kvzPresence, + kvzActive, + kvzStatus, + timestamp: new Date().toISOString(), + }; + + fs.writeFileSync(filePath, JSON.stringify(updatedData, null, 2)); + + res.status(200).json(updatedData); + } catch (error) { + console.error("Fehler beim Speichern der KVZ-Daten:", error); + res.status(500).json({ error: "Fehler beim Speichern der KVZ-Daten" }); + } + } else { + res.setHeader("Allow", ["GET", "POST"]); + res.status(405).json({ error: `Method ${req.method} not allowed` }); + } +} diff --git a/pages/api/kvz/updateSettings.ts b/pages/api/kvz/updateSettings.ts new file mode 100644 index 0000000..c8d5b8c --- /dev/null +++ b/pages/api/kvz/updateSettings.ts @@ -0,0 +1,77 @@ +// pages/api/kvz/updateSettings.ts +import type { NextApiRequest, NextApiResponse } from "next"; +import fs from "fs"; +import path from "path"; +import { KvzData } from "./data"; + +interface UpdateRequest { + updates: Array<{ + key: "kvzPresence" | "kvzActive" | "kvzStatus"; + slot?: number; + ledIndex?: number; + value: number; + }>; +} + +export default function handler( + req: NextApiRequest, + res: NextApiResponse +) { + if (req.method !== "POST") { + res.setHeader("Allow", ["POST"]); + return res.status(405).json({ error: `Method ${req.method} not allowed` }); + } + + try { + const { updates }: UpdateRequest = req.body; + + if (!Array.isArray(updates)) { + return res.status(400).json({ error: "Updates muss ein Array sein" }); + } + + const filePath = path.join( + process.cwd(), + "mocks", + "device-cgi-simulator", + "kvz", + "kvzData.json" + ); + const fileContents = fs.readFileSync(filePath, "utf8"); + const kvzData: KvzData = JSON.parse(fileContents); + + // Updates anwenden + updates.forEach((update) => { + const { key, slot, ledIndex, value } = update; + + if (key === "kvzPresence" && typeof slot === "number") { + if (slot >= 0 && slot < 32) { + kvzData.kvzPresence[slot] = value; + } + } else if (key === "kvzActive" && typeof slot === "number") { + if (slot >= 0 && slot < 32) { + kvzData.kvzActive[slot] = value; + } + } else if ( + key === "kvzStatus" && + typeof slot === "number" && + typeof ledIndex === "number" + ) { + const arrayIndex = slot * 4 + ledIndex; + if (arrayIndex >= 0 && arrayIndex < 128) { + kvzData.kvzStatus[arrayIndex] = value; + } + } + }); + + // Timestamp aktualisieren + kvzData.timestamp = new Date().toISOString(); + + // Datei speichern + fs.writeFileSync(filePath, JSON.stringify(kvzData, null, 2)); + + res.status(200).json(kvzData); + } catch (error) { + console.error("Fehler beim Update der KVZ-Einstellungen:", error); + res.status(500).json({ error: "Fehler beim Update der KVZ-Einstellungen" }); + } +} diff --git a/redux/slices/kueDataSlice.ts b/redux/slices/kueDataSlice.ts index 93752ad..e49e4dd 100644 --- a/redux/slices/kueDataSlice.ts +++ b/redux/slices/kueDataSlice.ts @@ -44,6 +44,10 @@ interface KueDataState { memoryInterval: number[]; // Fallsensors win_fallSensorsActive: number[]; + // KVZ System - 32 Slots mit je 4 LEDs + kvzPresence: number[]; // 32 Werte: 1 = KVZ vorhanden, 0 = nicht vorhanden + kvzActive: number[]; // 32 Werte: 1 = KVZ aktiviert, 0 = deaktiviert + kvzStatus: number[]; // 128 Werte: 4 LEDs pro Slot (32 * 4) } const initialState: KueDataState = { @@ -89,6 +93,10 @@ const initialState: KueDataState = { memoryInterval: [], // Fallsensors win_fallSensorsActive: [], // Fallsensors aktiv + // KVZ System - 32 Slots mit je 4 LEDs + kvzPresence: new Array(32).fill(0), // 32 Slots: 1 = KVZ vorhanden, 0 = nicht vorhanden + kvzActive: new Array(32).fill(0), // 32 Slots: 1 = KVZ aktiviert, 0 = deaktiviert + kvzStatus: new Array(128).fill(0), // 128 LEDs: 4 LEDs pro Slot (32 * 4) }; const kueDataSlice = createSlice({ diff --git a/redux/thunks/kvzThunks.ts b/redux/thunks/kvzThunks.ts new file mode 100644 index 0000000..c828f1f --- /dev/null +++ b/redux/thunks/kvzThunks.ts @@ -0,0 +1,67 @@ +// redux/thunks/kvzThunks.ts +import { createAsyncThunk } from "@reduxjs/toolkit"; +import { + fetchKvzData, + updateKvzSettings, +} from "../../services/fetchKvzDataService"; +import { setKueData } from "../slices/kueDataSlice"; + +/** + * Lädt KVZ-Daten von der API und aktualisiert Redux Store + */ +export const loadKvzData = createAsyncThunk( + "kvz/loadData", + async (_, { dispatch }) => { + try { + const data = await fetchKvzData(); + + // KVZ-Daten in Redux Store laden + dispatch( + setKueData({ + kvzPresence: data.kvzPresence, + kvzActive: data.kvzActive, + kvzStatus: data.kvzStatus, + }) + ); + + return data; + } catch (error) { + console.error("Fehler beim Laden der KVZ-Daten:", error); + throw error; + } + } +); + +/** + * Aktualisiert KVZ-Einstellungen und synchronisiert Redux Store + */ +export const updateKvzData = createAsyncThunk( + "kvz/updateData", + async ( + updates: Array<{ + key: "kvzPresence" | "kvzActive" | "kvzStatus"; + slot?: number; + ledIndex?: number; + value: number; + }>, + { dispatch } + ) => { + try { + const data = await updateKvzSettings(updates); + + // Aktualisierte Daten in Redux Store laden + dispatch( + setKueData({ + kvzPresence: data.kvzPresence, + kvzActive: data.kvzActive, + kvzStatus: data.kvzStatus, + }) + ); + + return data; + } catch (error) { + console.error("Fehler beim Update der KVZ-Daten:", error); + throw error; + } + } +); diff --git a/services/fetchKvzDataService.ts b/services/fetchKvzDataService.ts new file mode 100644 index 0000000..5db4515 --- /dev/null +++ b/services/fetchKvzDataService.ts @@ -0,0 +1,53 @@ +// services/fetchKvzDataService.ts +import { KvzData } from "../pages/api/kvz/data"; + +/** + * Holt KVZ-Daten vom Server + */ +export const fetchKvzData = async (): Promise => { + try { + const response = await fetch("/api/kvz/data"); + + if (!response.ok) { + throw new Error(`HTTP error! status: ${response.status}`); + } + + const data: KvzData = await response.json(); + return data; + } catch (error) { + console.error("Fehler beim Laden der KVZ-Daten:", error); + throw error; + } +}; + +/** + * Aktualisiert KVZ-Einstellungen auf dem Server + */ +export const updateKvzSettings = async ( + updates: Array<{ + key: "kvzPresence" | "kvzActive" | "kvzStatus"; + slot?: number; + ledIndex?: number; + value: number; + }> +): Promise => { + try { + const response = await fetch("/api/kvz/updateSettings", { + method: "POST", + headers: { + "Content-Type": "application/json", + }, + body: JSON.stringify({ updates }), + }); + + if (!response.ok) { + throw new Error(`HTTP error! status: ${response.status}`); + } + + const data: KvzData = await response.json(); + return data; + } catch (error) { + console.error("Fehler beim Update der KVZ-Einstellungen:", error); + throw error; + } +};