fix: Modal-Eingaben vor automatischem Redux-Refresh geschützt
- isInitialLoad hinzugefügt, um Eingabefelder nur beim ersten Öffnen zu initialisieren - verhindert, dass Hintergrundaktualisierungen durch Redux (fetchThunk) die Benutzereingaben überschreiben - saubere Rücksetzung von Zustand bei Modal-Schließen
This commit is contained in:
@@ -1,4 +1,5 @@
|
||||
"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";
|
||||
@@ -8,55 +9,94 @@ import {
|
||||
} from "../../../../redux/slices/digitalInputsSlice";
|
||||
|
||||
export default function InputModal({ selectedInput, closeInputModal, isOpen }) {
|
||||
const [invertiert, setInvertiert] = useState(false);
|
||||
const [name, setName] = useState("");
|
||||
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) {
|
||||
setInvertiert(reduxInput.invertierung);
|
||||
if (reduxInput && isInitialLoad) {
|
||||
setName(reduxInput.name || "");
|
||||
setInvertiert(reduxInput.invertierung);
|
||||
setFilterzeit(reduxInput.filterzeit);
|
||||
setGewichtung(reduxInput.gewichtung);
|
||||
setZaehlerAktiv(reduxInput.zaehlerAktiv);
|
||||
setIsInitialLoad(false);
|
||||
}
|
||||
}, [reduxInput]);
|
||||
}, [reduxInput, isInitialLoad]);
|
||||
|
||||
if (!isOpen || !selectedInput || !reduxInput) return null;
|
||||
|
||||
const handleInvertierungToggle = async () => {
|
||||
const neueInvertierung = !invertiert;
|
||||
setInvertiert(neueInvertierung);
|
||||
dispatch(
|
||||
updateInvertierung({ id: reduxInput.id, invertierung: neueInvertierung })
|
||||
);
|
||||
const handleSpeichern = async () => {
|
||||
const updates: any = { id: reduxInput.id };
|
||||
let hasChange = false;
|
||||
|
||||
// Update Mock-API
|
||||
await fetch("/api/cpl/updateDigitaleEingaenge", {
|
||||
method: "POST",
|
||||
headers: { "Content-Type": "application/json" },
|
||||
body: JSON.stringify({
|
||||
id: reduxInput.id,
|
||||
invertierung: neueInvertierung ? 1 : 0,
|
||||
}),
|
||||
});
|
||||
if (name !== reduxInput.name) {
|
||||
updates.name = name;
|
||||
dispatch(updateName({ id: reduxInput.id, name }));
|
||||
hasChange = true;
|
||||
}
|
||||
|
||||
if (invertiert !== reduxInput.invertierung) {
|
||||
updates.invertierung = invertiert ? 1 : 0;
|
||||
dispatch(
|
||||
updateInvertierung({ id: reduxInput.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;
|
||||
}
|
||||
|
||||
try {
|
||||
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");
|
||||
}
|
||||
|
||||
setIsInitialLoad(true); // Reset
|
||||
closeInputModal(); // Modal schließen bei Erfolg
|
||||
} catch (err: any) {
|
||||
alert("❌ Fehler beim Speichern: " + err.message);
|
||||
}
|
||||
};
|
||||
|
||||
const handleNameSpeichern = async () => {
|
||||
dispatch(updateName({ id: reduxInput.id, name }));
|
||||
|
||||
// Update Mock-API
|
||||
await fetch("/api/cpl/updateDigitaleEingaenge", {
|
||||
method: "POST",
|
||||
headers: { "Content-Type": "application/json" },
|
||||
body: JSON.stringify({
|
||||
id: reduxInput.id,
|
||||
name: name,
|
||||
}),
|
||||
});
|
||||
const handleClose = () => {
|
||||
setIsInitialLoad(true);
|
||||
closeInputModal();
|
||||
};
|
||||
|
||||
return (
|
||||
@@ -68,18 +108,18 @@ export default function InputModal({ selectedInput, closeInputModal, isOpen }) {
|
||||
|
||||
<div className="grid grid-cols-2 gap-x-4 gap-y-3">
|
||||
<div>
|
||||
<strong>Status:</strong>
|
||||
<strong>Zustand:</strong>
|
||||
</div>
|
||||
<div className="flex items-center gap-3 text-xl font-semibold col-span-1">
|
||||
{reduxInput.status ? (
|
||||
<>
|
||||
<span className="w-4 h-4 bg-red-500 rounded-full inline-block"></span>
|
||||
<span className="text-red-600">Inaktiv</span>
|
||||
<span className="text-red-600">Aus</span>
|
||||
</>
|
||||
) : (
|
||||
<>
|
||||
<span className="w-4 h-4 bg-green-500 rounded-full inline-block"></span>
|
||||
<span className="text-green-600">Aktiv</span>
|
||||
<span className="text-green-600">Ein</span>
|
||||
</>
|
||||
)}
|
||||
</div>
|
||||
@@ -87,7 +127,7 @@ export default function InputModal({ selectedInput, closeInputModal, isOpen }) {
|
||||
<div>
|
||||
<strong>Name:</strong>
|
||||
</div>
|
||||
<div className="flex gap-2">
|
||||
<div>
|
||||
<input
|
||||
type="text"
|
||||
value={name}
|
||||
@@ -95,19 +135,13 @@ export default function InputModal({ selectedInput, closeInputModal, isOpen }) {
|
||||
className="border border-gray-300 rounded px-2 py-1 w-full"
|
||||
maxLength={32}
|
||||
/>
|
||||
<button
|
||||
onClick={handleNameSpeichern}
|
||||
className="px-2 py-1 text-sm bg-blue-500 hover:bg-blue-600 text-white rounded"
|
||||
>
|
||||
Speichern
|
||||
</button>
|
||||
</div>
|
||||
|
||||
<div className="flex items-center gap-2 col-span-2">
|
||||
<strong>Invertierung:</strong>
|
||||
<span>{invertiert ? "Ein" : "Aus"}</span>
|
||||
<button
|
||||
onClick={handleInvertierungToggle}
|
||||
onClick={() => setInvertiert(!invertiert)}
|
||||
className="ml-auto px-3 py-1 text-sm bg-gray-200 hover:bg-gray-300 rounded"
|
||||
>
|
||||
Umschalten
|
||||
@@ -122,30 +156,65 @@ export default function InputModal({ selectedInput, closeInputModal, isOpen }) {
|
||||
<div>
|
||||
<strong>Filterzeit:</strong>
|
||||
</div>
|
||||
<div>{reduxInput.filterzeit} ms</div>
|
||||
<div>
|
||||
<input
|
||||
type="number"
|
||||
min={0}
|
||||
max={2000}
|
||||
value={filterzeit}
|
||||
onChange={(e) => setFilterzeit(Number(e.target.value))}
|
||||
className="border border-gray-300 rounded px-2 py-1 w-full"
|
||||
/>{" "}
|
||||
ms
|
||||
</div>
|
||||
|
||||
<div>
|
||||
<strong>Gewichtung:</strong>
|
||||
</div>
|
||||
<div>{reduxInput.gewichtung}</div>
|
||||
<div>
|
||||
<input
|
||||
type="number"
|
||||
min={0}
|
||||
max={1000}
|
||||
value={gewichtung}
|
||||
onChange={(e) => setGewichtung(Number(e.target.value))}
|
||||
className="border border-gray-300 rounded px-2 py-1 w-full"
|
||||
/>
|
||||
</div>
|
||||
|
||||
<div>
|
||||
<strong>Zähler aktiv:</strong>
|
||||
</div>
|
||||
<div>{reduxInput.zaehlerAktiv ? "Ja" : "Nein"}</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>
|
||||
<strong>Eingang offline:</strong>
|
||||
<strong>Status:</strong>
|
||||
</div>
|
||||
<div>{reduxInput.eingangOffline ? "Offline" : "Online"}</div>
|
||||
<div>{reduxInput.eingangOffline ? "Inaktiv" : "Aktiv"}</div>
|
||||
</div>
|
||||
|
||||
<button
|
||||
onClick={closeInputModal}
|
||||
className="mt-6 px-4 py-2 bg-blue-500 hover:bg-blue-600 text-white rounded-lg transition"
|
||||
>
|
||||
Schließen
|
||||
</button>
|
||||
<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>
|
||||
);
|
||||
|
||||
@@ -6,5 +6,5 @@
|
||||
2: Patch oder Hotfix (Bugfixes oder kleine Änderungen).
|
||||
|
||||
*/
|
||||
const webVersion = "1.6.268";
|
||||
const webVersion = "1.6.269";
|
||||
export default webVersion;
|
||||
|
||||
Reference in New Issue
Block a user