280 lines
8.9 KiB
TypeScript
280 lines
8.9 KiB
TypeScript
"use client";
|
|
// /components/main/einausgaenge/modals/InputModal.tsx
|
|
import React, { useEffect, useState } from "react";
|
|
import { useSelector, useDispatch } from "react-redux";
|
|
import { RootState } from "../../../../redux/store";
|
|
import {
|
|
updateInvertierung,
|
|
updateName,
|
|
} from "../../../../redux/slices/digitalInputsSlice";
|
|
|
|
export default function InputModal({ selectedInput, closeInputModal, isOpen }) {
|
|
const dispatch = useDispatch();
|
|
const reduxInput = useSelector((state: RootState) =>
|
|
state.digitalInputsSlice.inputs.find(
|
|
(input) => input.id === Number(selectedInput?.id)
|
|
)
|
|
);
|
|
|
|
const [isInitialLoad, setIsInitialLoad] = useState(true);
|
|
const [name, setName] = useState("");
|
|
const [invertiert, setInvertiert] = useState(false);
|
|
const [filterzeit, setFilterzeit] = useState(0);
|
|
const [gewichtung, setGewichtung] = useState(0);
|
|
const [zaehlerAktiv, setZaehlerAktiv] = useState(false);
|
|
|
|
useEffect(() => {
|
|
if (reduxInput && isInitialLoad) {
|
|
setName(reduxInput.name || "");
|
|
setInvertiert(reduxInput.invertierung);
|
|
setFilterzeit(reduxInput.filterzeit);
|
|
setGewichtung(reduxInput.gewichtung);
|
|
setZaehlerAktiv(reduxInput.zaehlerAktiv);
|
|
setIsInitialLoad(false);
|
|
}
|
|
}, [reduxInput, isInitialLoad]);
|
|
|
|
if (!isOpen || !selectedInput || !reduxInput) return null;
|
|
|
|
const sendCgiUpdate = async (param: string) => {
|
|
const url = `/CPL?/eingaenge.html&${param}`;
|
|
console.log("📡 CGI senden:", url);
|
|
|
|
const response = await fetch(url);
|
|
if (!response.ok) {
|
|
throw new Error(`Fehler bei CGI-Aufruf: ${param}`);
|
|
}
|
|
};
|
|
|
|
const handleSpeichern = async () => {
|
|
const id = reduxInput.id;
|
|
let hasChange = false;
|
|
|
|
try {
|
|
// PRODUKTIONSUMGEBUNG (CGI-Modus)
|
|
if (process.env.NEXT_PUBLIC_NODE_ENV === "production") {
|
|
if (name !== reduxInput.name) {
|
|
await sendCgiUpdate(`DEN${id}=${encodeURIComponent(name)}`);
|
|
dispatch(updateName({ id, name }));
|
|
hasChange = true;
|
|
}
|
|
if (invertiert !== reduxInput.invertierung) {
|
|
await sendCgiUpdate(`DEI${id}=${invertiert ? 1 : 0}`);
|
|
dispatch(updateInvertierung({ id, invertierung: invertiert }));
|
|
hasChange = true;
|
|
}
|
|
if (filterzeit !== reduxInput.filterzeit) {
|
|
await sendCgiUpdate(`DEF${id}=${filterzeit}`);
|
|
hasChange = true;
|
|
}
|
|
if (gewichtung !== reduxInput.gewichtung) {
|
|
await sendCgiUpdate(`DEG${id}=${gewichtung}`);
|
|
hasChange = true;
|
|
}
|
|
if (zaehlerAktiv !== reduxInput.zaehlerAktiv) {
|
|
await sendCgiUpdate(`DEZ${id}=${zaehlerAktiv ? 1 : 0}`);
|
|
hasChange = true;
|
|
}
|
|
if (!hasChange) {
|
|
alert("⚠️ Keine Änderungen erkannt.");
|
|
return;
|
|
}
|
|
alert("✅ Daten erfolgreich an die CPL-Hardware gesendet!");
|
|
} else {
|
|
// ENTWICKLUNGSUMGEBUNG (lokale API)
|
|
const updates: any = { id };
|
|
if (name !== reduxInput.name) {
|
|
updates.name = name;
|
|
dispatch(updateName({ id, name }));
|
|
hasChange = true;
|
|
}
|
|
if (invertiert !== reduxInput.invertierung) {
|
|
updates.invertierung = invertiert ? 1 : 0;
|
|
dispatch(updateInvertierung({ id, invertierung: invertiert }));
|
|
hasChange = true;
|
|
}
|
|
if (filterzeit !== reduxInput.filterzeit) {
|
|
updates.filterzeit = filterzeit;
|
|
hasChange = true;
|
|
}
|
|
if (gewichtung !== reduxInput.gewichtung) {
|
|
updates.gewichtung = gewichtung;
|
|
hasChange = true;
|
|
}
|
|
if (zaehlerAktiv !== reduxInput.zaehlerAktiv) {
|
|
updates.zaehlerAktiv = zaehlerAktiv ? 1 : 0;
|
|
hasChange = true;
|
|
}
|
|
|
|
if (!hasChange) {
|
|
alert("⚠️ Keine Änderungen erkannt.");
|
|
return;
|
|
}
|
|
|
|
const res = await fetch("/api/cpl/updateDigitaleEingaenge", {
|
|
method: "POST",
|
|
headers: { "Content-Type": "application/json" },
|
|
body: JSON.stringify(updates),
|
|
});
|
|
|
|
if (!res.ok) {
|
|
const errData = await res.json();
|
|
throw new Error(errData.error || "Unbekannter Fehler");
|
|
}
|
|
|
|
alert("✅ Daten lokal gespeichert!");
|
|
}
|
|
|
|
setIsInitialLoad(true);
|
|
closeInputModal();
|
|
} catch (err: any) {
|
|
alert("❌ Fehler beim Speichern: " + err.message);
|
|
}
|
|
};
|
|
|
|
const handleClose = () => {
|
|
setIsInitialLoad(true);
|
|
closeInputModal();
|
|
};
|
|
|
|
return (
|
|
<div className="fixed top-0 left-0 w-full h-full bg-black bg-opacity-50 flex justify-center items-center z-50">
|
|
<div className="bg-white rounded-lg shadow-lg p-6 w-1/2 max-w-lg">
|
|
<h2 className="text-xl font-semibold text-blue-500 mb-4 border-b pb-2">
|
|
Parameter für Eingang {selectedInput.id}
|
|
</h2>
|
|
|
|
<div className="grid grid-cols-2 gap-x-4 gap-y-3">
|
|
<div>
|
|
<strong>Zustand:</strong>
|
|
</div>
|
|
<div className="flex items-center gap-3 text-xl font-semibold col-span-1">
|
|
{reduxInput.status ? (
|
|
<>
|
|
<span className="text-red-500 text-2xl">●</span>
|
|
<span className="text-red-600">Aus</span>
|
|
</>
|
|
) : (
|
|
<>
|
|
<span className="text-green-500 text-2xl">●</span>
|
|
<span className="text-green-600">Ein</span>
|
|
</>
|
|
)}
|
|
</div>
|
|
|
|
<div>
|
|
<strong>Name:</strong>
|
|
</div>
|
|
<div>
|
|
<input
|
|
type="text"
|
|
value={name}
|
|
onChange={(e) => setName(e.target.value)}
|
|
className="border border-gray-300 rounded px-2 py-1 w-full"
|
|
maxLength={32}
|
|
/>
|
|
</div>
|
|
|
|
<div>
|
|
<strong>Invertierung:</strong>
|
|
</div>
|
|
<div className="flex justify-between items-center gap-2">
|
|
<span>{invertiert ? "Ein" : "Aus"}</span>
|
|
<button
|
|
onClick={() => setInvertiert(!invertiert)}
|
|
className="px-3 py-1 text-sm bg-gray-200 hover:bg-gray-300 rounded"
|
|
>
|
|
Umschalten
|
|
</button>
|
|
</div>
|
|
|
|
<div>
|
|
<strong>Zählerstand:</strong>
|
|
</div>
|
|
<div>{reduxInput.counter}</div>
|
|
|
|
<div>
|
|
<strong>Filterzeit:</strong>
|
|
</div>
|
|
<div className="relative">
|
|
<input
|
|
type="number"
|
|
min={0}
|
|
max={2000}
|
|
value={filterzeit}
|
|
onChange={(e) => {
|
|
const val = Number(e.target.value);
|
|
if (val <= 2000) {
|
|
setFilterzeit(val);
|
|
}
|
|
}}
|
|
className="border border-gray-300 rounded px-2 py-1 pr-10 w-full"
|
|
title="Maximal 2000 ms erlaubt"
|
|
/>
|
|
<span className="absolute right-2 top-1/2 transform -translate-y-1/2 text-gray-500 text-sm">
|
|
ms
|
|
</span>
|
|
</div>
|
|
|
|
<div>
|
|
<strong>Gewichtung:</strong>
|
|
</div>
|
|
<div>
|
|
<input
|
|
type="number"
|
|
min={0}
|
|
max={1000}
|
|
value={gewichtung}
|
|
onChange={(e) => {
|
|
const val = Number(e.target.value);
|
|
if (val <= 1000) {
|
|
setGewichtung(val);
|
|
}
|
|
}}
|
|
className="border border-gray-300 rounded px-2 py-1 w-full"
|
|
title="Maximal 1000 erlaubt"
|
|
/>
|
|
</div>
|
|
|
|
<div>
|
|
<strong>Zähler aktiv:</strong>
|
|
</div>
|
|
<div className="flex items-center gap-2">
|
|
<span>{zaehlerAktiv ? "Ja" : "Nein"}</span>
|
|
<button
|
|
onClick={() => setZaehlerAktiv(!zaehlerAktiv)}
|
|
className="ml-auto px-3 py-1 text-sm bg-gray-200 hover:bg-gray-300 rounded"
|
|
>
|
|
Umschalten
|
|
</button>
|
|
</div>
|
|
|
|
<div className="relative group inline-block">
|
|
<strong>OOS:</strong>
|
|
<span className="absolute top-full left-1/2 -translate-x-1/2 mt-1 px-2 py-1 text-xs text-white bg-gray-700 rounded opacity-0 group-hover:opacity-100 transition-opacity">
|
|
Out of Service
|
|
</span>
|
|
</div>
|
|
|
|
<div>{reduxInput.eingangOffline ? "aus" : "ein"}</div>
|
|
</div>
|
|
|
|
<div className="mt-6 flex justify-end gap-2">
|
|
<button
|
|
onClick={handleClose}
|
|
className="px-4 py-2 bg-gray-300 hover:bg-gray-400 text-black rounded-lg transition"
|
|
>
|
|
Schließen
|
|
</button>
|
|
<button
|
|
onClick={handleSpeichern}
|
|
className="px-4 py-2 bg-blue-500 hover:bg-blue-600 text-white rounded-lg transition"
|
|
>
|
|
Speichern
|
|
</button>
|
|
</div>
|
|
</div>
|
|
</div>
|
|
);
|
|
}
|