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

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