refactor: rename einausgange to digitalOtputs and digitalInputs
This commit is contained in:
314
components/main/digitalInputs/digitalInputsModal.tsx
Normal file
314
components/main/digitalInputs/digitalInputsModal.tsx
Normal file
@@ -0,0 +1,314 @@
|
||||
"use client";
|
||||
// /components/main/digitalInputs/InputModal.tsx
|
||||
import React, { useEffect, useState } from "react";
|
||||
import { useSelector, useDispatch } from "react-redux";
|
||||
import { RootState } from "@/redux/store";
|
||||
import { updateInvert, updateLabel } from "@/redux/slices/digitalInputsSlice";
|
||||
|
||||
type InputModalProps = {
|
||||
selectedInput: {
|
||||
id: number;
|
||||
[key: string]: unknown;
|
||||
} | null;
|
||||
closeInputModal: () => void;
|
||||
isOpen: boolean;
|
||||
};
|
||||
|
||||
export default function InputModal({
|
||||
selectedInput,
|
||||
closeInputModal,
|
||||
isOpen,
|
||||
}: InputModalProps) {
|
||||
const dispatch = useDispatch();
|
||||
const reduxInput = useSelector((state: RootState) =>
|
||||
state.digitalInputsSlice.inputs.find(
|
||||
(input) => input.id === Number(selectedInput?.id)
|
||||
)
|
||||
);
|
||||
/* console.log("📦 selectedInput.id:", selectedInput?.id);
|
||||
console.log("📦 reduxInput:", reduxInput); */
|
||||
|
||||
const [isInitialLoad, setIsInitialLoad] = useState(true);
|
||||
const [label, setLabel] = useState("");
|
||||
const [invertiert, setInvertiert] = useState(false);
|
||||
const [timeFilter, setTimeFilter] = useState(0);
|
||||
const [weighting, setWeighting] = useState(0);
|
||||
const [zaehlerAktiv, setZaehlerAktiv] = useState(false);
|
||||
const [eingangOffline, setEingangOffline] = useState(0);
|
||||
|
||||
useEffect(() => {
|
||||
if (reduxInput && isInitialLoad) {
|
||||
//reduxInput
|
||||
//console.log("📦 reduxInput geladen:", reduxInput);
|
||||
setLabel(reduxInput.label || "");
|
||||
setInvertiert(reduxInput.invert);
|
||||
setTimeFilter(reduxInput.timeFilter);
|
||||
setWeighting(reduxInput.weighting);
|
||||
setZaehlerAktiv(reduxInput.zaehlerAktiv);
|
||||
setEingangOffline(reduxInput.eingangOffline ? 1 : 0);
|
||||
setIsInitialLoad(false);
|
||||
}
|
||||
}, [reduxInput, isInitialLoad]);
|
||||
|
||||
useEffect(() => {
|
||||
if (isOpen && selectedInput) {
|
||||
setIsInitialLoad(true);
|
||||
}
|
||||
}, [isOpen, selectedInput]);
|
||||
|
||||
useEffect(() => {
|
||||
if (isOpen && selectedInput) {
|
||||
setIsInitialLoad(true);
|
||||
}
|
||||
}, [isOpen, selectedInput]);
|
||||
|
||||
if (!isOpen || !selectedInput || !reduxInput) return null;
|
||||
|
||||
const handleClose = () => {
|
||||
closeInputModal();
|
||||
};
|
||||
|
||||
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 (label !== reduxInput.label) {
|
||||
await sendCgiUpdate(`DEN${id}=${encodeURIComponent(label)}`);
|
||||
dispatch(updateLabel({ id, label }));
|
||||
hasChange = true;
|
||||
}
|
||||
if (invertiert !== reduxInput.invert) {
|
||||
await sendCgiUpdate(`DEI${id}=${invertiert ? 1 : 0}`);
|
||||
dispatch(updateInvert({ id, invert: invertiert }));
|
||||
hasChange = true;
|
||||
}
|
||||
if (timeFilter !== reduxInput.timeFilter) {
|
||||
await sendCgiUpdate(`DEF${id}=${timeFilter}`);
|
||||
hasChange = true;
|
||||
}
|
||||
if (weighting !== reduxInput.weighting) {
|
||||
await sendCgiUpdate(`DEG${id}=${weighting}`);
|
||||
hasChange = true;
|
||||
}
|
||||
if (zaehlerAktiv !== reduxInput.zaehlerAktiv) {
|
||||
await sendCgiUpdate(`DEZ${id}=${zaehlerAktiv ? 1 : 0}`);
|
||||
hasChange = true;
|
||||
}
|
||||
if (eingangOffline !== (reduxInput.eingangOffline ? 1 : 0)) {
|
||||
await sendCgiUpdate(`DEO${id}=${eingangOffline ? 1 : 0}`);
|
||||
hasChange = true;
|
||||
}
|
||||
if (!hasChange) {
|
||||
alert("⚠️ Keine Änderungen erkannt.");
|
||||
return;
|
||||
}
|
||||
alert("✅ Daten erfolgreich an die CPL-Hardware gesendet!");
|
||||
} else {
|
||||
// ENTWICKLUNGSUMGEBUNG (lokale API)
|
||||
type Updates = {
|
||||
id: number;
|
||||
label?: string;
|
||||
invert?: number;
|
||||
timeFilter?: number;
|
||||
weighting?: number;
|
||||
zaehlerAktiv?: number;
|
||||
};
|
||||
const updates: Updates = { id };
|
||||
if (label !== reduxInput.label) {
|
||||
updates.label = label;
|
||||
dispatch(updateLabel({ id, label }));
|
||||
hasChange = true;
|
||||
}
|
||||
if (invertiert !== reduxInput.invert) {
|
||||
updates.invert = invertiert ? 1 : 0;
|
||||
dispatch(updateInvert({ id, invert: invertiert }));
|
||||
hasChange = true;
|
||||
}
|
||||
if (timeFilter !== reduxInput.timeFilter) {
|
||||
updates.timeFilter = timeFilter;
|
||||
hasChange = true;
|
||||
}
|
||||
if (weighting !== reduxInput.weighting) {
|
||||
updates.weighting = weighting;
|
||||
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/updateDigitalInputs", {
|
||||
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: unknown) {
|
||||
if (err instanceof Error) {
|
||||
alert("❌ Fehler beim Speichern: " + err.message);
|
||||
} else {
|
||||
alert("❌ Fehler beim Speichern: Unbekannter Fehler");
|
||||
}
|
||||
}
|
||||
};
|
||||
|
||||
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">
|
||||
<div className="mb-4 border-b pb-2 flex justify-between items-center">
|
||||
<h2 className="text-base font-bold">
|
||||
Einstellungen Meldungseingang {selectedInput.id}
|
||||
</h2>
|
||||
<button
|
||||
onClick={handleClose}
|
||||
className="text-2xl hover:text-gray-400"
|
||||
aria-label="Modal schließen"
|
||||
>
|
||||
<i className="bi bi-x-circle-fill"></i>
|
||||
</button>
|
||||
</div>
|
||||
|
||||
<div className="grid grid-cols-2 gap-x-4 gap-y-3">
|
||||
<div>
|
||||
<span className="font-normal">Bezeichnung:</span>
|
||||
</div>
|
||||
<div>
|
||||
<input
|
||||
type="text"
|
||||
value={label}
|
||||
onChange={(e) => setLabel(e.target.value)}
|
||||
className="border border-gray-300 rounded px-2 py-1 w-full"
|
||||
maxLength={32}
|
||||
/>
|
||||
</div>
|
||||
|
||||
<div>
|
||||
<span className="font-normal">Invertierung:</span>
|
||||
</div>
|
||||
<div className="flex items-center gap-2">
|
||||
<button
|
||||
type="button"
|
||||
role="switch"
|
||||
aria-checked={invertiert}
|
||||
onClick={() => setInvertiert(!invertiert)}
|
||||
className={`relative inline-flex h-6 w-11 items-center rounded-full transition-colors duration-200 ${
|
||||
invertiert ? "bg-littwin-blue" : "bg-gray-300"
|
||||
}`}
|
||||
>
|
||||
<span
|
||||
className={`inline-block h-4 w-4 transform rounded-full bg-white transition-transform duration-200 ${
|
||||
invertiert ? "translate-x-6" : "translate-x-1"
|
||||
}`}
|
||||
/>
|
||||
</button>
|
||||
<span>{invertiert ? "Ein" : "Aus"}</span>
|
||||
</div>
|
||||
|
||||
<div>
|
||||
<span className="font-normal">Filterzeit:</span>
|
||||
</div>
|
||||
<div className="relative">
|
||||
<input
|
||||
type="number"
|
||||
min={0}
|
||||
max={2000}
|
||||
value={timeFilter}
|
||||
onChange={(e) => {
|
||||
const val = Number(e.target.value);
|
||||
if (val <= 2000) {
|
||||
setTimeFilter(val);
|
||||
}
|
||||
}}
|
||||
className="border border-gray-300 rounded px-2 py-1 pr-10 w-full text-right"
|
||||
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>
|
||||
<span className="font-normal">Gewichtung:</span>
|
||||
</div>
|
||||
<div>
|
||||
<input
|
||||
type="number"
|
||||
min={0}
|
||||
max={1000}
|
||||
value={weighting}
|
||||
onChange={(e) => {
|
||||
const val = Number(e.target.value);
|
||||
if (val <= 1000) {
|
||||
setWeighting(val);
|
||||
}
|
||||
}}
|
||||
className="border border-gray-300 rounded px-2 py-1 w-full text-right"
|
||||
title="Maximal 1000 erlaubt"
|
||||
/>
|
||||
</div>
|
||||
|
||||
<div className="relative group inline-block">
|
||||
<span className="font-normal">Out of Service:</span>
|
||||
</div>
|
||||
|
||||
<div className="flex items-center gap-2">
|
||||
<button
|
||||
type="button"
|
||||
role="switch"
|
||||
aria-checked={!!eingangOffline}
|
||||
onClick={() => setEingangOffline(eingangOffline ? 0 : 1)}
|
||||
className={`relative inline-flex h-6 w-11 items-center rounded-full transition-colors duration-200 ${
|
||||
eingangOffline ? "bg-littwin-blue" : "bg-gray-300"
|
||||
}`}
|
||||
>
|
||||
<span
|
||||
className={`inline-block h-4 w-4 transform rounded-full bg-white transition-transform duration-200 ${
|
||||
eingangOffline ? "translate-x-6" : "translate-x-1"
|
||||
}`}
|
||||
/>
|
||||
</button>
|
||||
<span>{eingangOffline ? "Ein" : "Aus"}</span>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<div className="mt-6 flex justify-end gap-2">
|
||||
<button
|
||||
onClick={handleSpeichern}
|
||||
className="bg-littwin-blue text-white px-4 py-2 rounded flex items-center"
|
||||
>
|
||||
Speichern
|
||||
</button>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
);
|
||||
}
|
||||
Reference in New Issue
Block a user