feat: KVZ API JSON Data

This commit is contained in:
ISA
2025-07-31 13:44:30 +02:00
parent 97eb40e1c6
commit 421e1f5425
18 changed files with 750 additions and 143 deletions

View File

@@ -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)

View File

@@ -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

View File

@@ -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

View File

@@ -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<FallSensorsProps> = ({ 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 (
<div className="flex gap-1 p-1 border rounded bg-gray-200">
{sensors.map((sensor) => (
<div key={sensor.id} className="flex flex-col items-center gap-1">
<span className="text-[0.5rem]">{sensor.id}</span>
<div className="flex justify-center items-center gap-2 p-3 border rounded-lg bg-gray-100 shadow-sm">
{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 (
<div
className={`w-4 h-4 flex items-center justify-center rounded-full border ${
sensor.status === "active" ? "bg-green-400" : "bg-red-400"
}`}
></div>
</div>
))}
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}`}
/>
);
})}
</div>
);
};

View File

@@ -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<Kue705FOProps> = ({
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<Kue705FOProps> = ({
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<Kue705FOProps> = ({
// 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<Kue705FOProps> = ({
{/* KVz Panel - Anzeige ganz unten, nur wenn KVz aktiv ist */}
{showKvzPanel && isKvzActiveForSlot && (
<div className=" bg-gray-400 mt-4 border p-1">
<FallSensors />
<FallSensors slotIndex={slotIndex} />
</div>
)}

View File

@@ -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<string, { data: KvzData; kvzActive: boolean }>;
}
}
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 (
<div className="p-4 text-sm">
{/* KVz-Funktion */}
{isAdminLoggedIn && (
{/* KVz-Funktion - nur anzeigen wenn KVZ vorhanden ist */}
{isAdminLoggedIn && isKvzPresent && (
<div className="mb-4 mt-4 grid grid-cols-3 items-center gap-2 w-full">
<span className="text-sm font-medium">KVz-Funktion:</span>
<div className="col-span-2 flex items-center gap-4">
<button
type="button"
role="switch"
aria-checked={kvzActive}
aria-checked={localKvzActive}
onClick={handleKvzToggle}
className={`relative inline-flex h-6 w-11 items-center rounded-full transition-colors duration-200 ${
kvzActive ? "bg-littwin-blue" : "bg-gray-300"
localKvzActive ? "bg-littwin-blue" : "bg-gray-300"
}`}
>
<span
className={`inline-block h-4 w-4 transform rounded-full bg-white transition-transform duration-200 ${
kvzActive ? "translate-x-6" : "translate-x-1"
localKvzActive ? "translate-x-6" : "translate-x-1"
}`}
/>
</button>
<span className="text-sm text-gray-600">
{kvzActive ? "aktiviert" : "deaktiviert"}
{localKvzActive ? "aktiviert" : "deaktiviert"}
</span>
</div>
</div>
)}
{/* Meldung wenn KVZ nicht vorhanden */}
{!isKvzPresent && (
<div className="mb-4 mt-4 p-4 bg-yellow-50 border border-yellow-200 rounded">
<div className="flex items-center gap-2">
<span className="text-yellow-600"></span>
<p className="text-sm text-yellow-800">
<strong>Kein KVZ-Gerät vorhanden</strong>
<br />
Für Slot {slot + 1} ist kein KVZ-Gerät installiert oder
konfiguriert.
</p>
</div>
</div>
)}
{/* Zukünftige KVz-Einstellungen können hier hinzugefügt werden */}
{!isAdminLoggedIn && (
<div className="mt-6 mb-4">

View File

@@ -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);
}

View File

@@ -0,0 +1,142 @@
# KVZ System - Mein aktuelles Verständnis
## System Übersicht
```mermaid
graph TB
subgraph "Kabelüberwachung System (32 Slots)"
Slot0["Slot 0<br/>Kabelüberwachung"]
Slot1["Slot 1<br/>Kabelüberwachung"]
Slot2["Slot 2<br/>Kabelüberwachung"]
Slot3["Slot 3<br/>Kabelüberwachung"]
SlotDots["..."]
Slot31["Slot 31<br/>Kabelüberwachung"]
end
subgraph "KVZ Geräte (Optional pro Slot)"
KVZ0["KVZ Gerät<br/>für Slot 0"]
KVZ1["KVZ Gerät<br/>für Slot 1"]
KVZ2["KVZ Gerät<br/>für Slot 2"]
KVZ3["KVZ Gerät<br/>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<br/>(KVZ vorhanden)"]
P1["Index 1: 0<br/>(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: 🟢<br/>(kvzPresence[0] = 1)"]
UI2["KVZ2: 🔴<br/>(kvzPresence[1] = 0)"]
UI3["KVZ3: 🔴<br/>(kvzPresence[2] = 0)"]
UI4["KVZ4: 🔴<br/>(kvzPresence[3] = 0)"]
end
end
subgraph "Problem"
Problem["Alle KVZ zeigen den gleichen Status<br/>basierend auf kvzPresence Array<br/>→ 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! 🤔**

141
docs/KVZ/mock-data.md Normal file
View File

@@ -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)

View File

@@ -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)"
}
}

4
package-lock.json generated
View File

@@ -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",

View File

@@ -1,6 +1,6 @@
{
"name": "cpl-v4",
"version": "1.6.661",
"version": "1.6.662",
"private": true,
"scripts": {
"dev": "next dev",

View File

@@ -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")) {

101
pages/api/kvz/data.ts Normal file
View File

@@ -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<KvzData | { error: string }>
) {
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` });
}
}

View File

@@ -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<KvzData | { error: string }>
) {
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" });
}
}

View File

@@ -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({

67
redux/thunks/kvzThunks.ts Normal file
View File

@@ -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;
}
}
);

View File

@@ -0,0 +1,53 @@
// services/fetchKvzDataService.ts
import { KvzData } from "../pages/api/kvz/data";
/**
* Holt KVZ-Daten vom Server
*/
export const fetchKvzData = async (): Promise<KvzData> => {
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<KvzData> => {
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;
}
};