293 lines
11 KiB
TypeScript
293 lines
11 KiB
TypeScript
// /components/main/kabelueberwachung/kue705FO/Charts/TDRChart/TDRChartActionBar.tsx
|
||
|
||
import React, { useState, useEffect } from "react";
|
||
import { useSelector } from "react-redux";
|
||
import { useAppDispatch } from "@/redux/store";
|
||
import { RootState } from "@/redux/store";
|
||
import { fetchTDMDataBySlotThunk } from "@/redux/thunks/getTDMListBySlotThunk";
|
||
import { getTDRChartDataByIdThunk } from "@/redux/thunks/getTDRChartDataByIdThunk";
|
||
import { getReferenceCurveBySlotThunk } from "@/redux/thunks/getReferenceCurveBySlotThunk"; // ⬅ import ergänzen
|
||
import { Listbox } from "@headlessui/react";
|
||
|
||
const TDRChartActionBar: React.FC = () => {
|
||
const dispatch = useAppDispatch();
|
||
|
||
// ✅ Redux: selectedSlot aus kueChartMode (0-basiert)
|
||
const selectedSlot = useSelector(
|
||
(state: RootState) => state.kueChartModeSlice.selectedSlot
|
||
);
|
||
|
||
const tdmChartData = useSelector(
|
||
(state: RootState) => state.tdmSingleChartSlice.data
|
||
);
|
||
|
||
const idsForSlot =
|
||
selectedSlot !== null ? tdmChartData[selectedSlot] ?? [] : [];
|
||
|
||
const tdrDataById = useSelector(
|
||
(state: RootState) => state.tdrDataByIdSlice.dataById
|
||
);
|
||
const [selectedId, setSelectedId] = useState<number | null>(null);
|
||
const currentChartData = selectedId !== null ? tdrDataById[selectedId] : [];
|
||
|
||
// ▶ Fortschrittsanzeige für laufende TDR-Messung (max. 120s bzw. konfigurierbar)
|
||
const TDR_TOTAL_DURATION = parseInt(
|
||
process.env.NEXT_PUBLIC_TDR_DURATION_SECONDS || "120",
|
||
10
|
||
);
|
||
const [tdrRunning, setTdrRunning] = useState(false);
|
||
const [tdrProgress, setTdrProgress] = useState(0); // Sekunden
|
||
|
||
useEffect(() => {
|
||
if (!tdrRunning) return;
|
||
setTdrProgress(0);
|
||
const startedAt = Date.now();
|
||
const interval = setInterval(() => {
|
||
const elapsed = Math.floor((Date.now() - startedAt) / 1000);
|
||
if (elapsed >= TDR_TOTAL_DURATION) {
|
||
setTdrProgress(TDR_TOTAL_DURATION);
|
||
setTdrRunning(false);
|
||
clearInterval(interval);
|
||
} else {
|
||
setTdrProgress(elapsed);
|
||
}
|
||
}, 1000);
|
||
return () => clearInterval(interval);
|
||
}, [tdrRunning, TDR_TOTAL_DURATION]);
|
||
|
||
const startTdrProgress = () => {
|
||
setTdrRunning(true);
|
||
setTdrProgress(0);
|
||
};
|
||
|
||
// 📌 Referenz setzen (nutzt Slotnummer + 1 für die API)
|
||
const handleSetReference = async () => {
|
||
if (
|
||
selectedSlot === null ||
|
||
selectedId === null ||
|
||
!currentChartData?.length
|
||
)
|
||
return;
|
||
|
||
try {
|
||
const slotNumber = selectedSlot + 1; // Slot ist 0-basiert, API will 1-basiert
|
||
const isDev = process.env.NEXT_PUBLIC_NODE_ENV === "development";
|
||
|
||
if (isDev) {
|
||
await fetch("/api/cpl/updateTdrReferenceCurveAPIHandler", {
|
||
method: "POST",
|
||
headers: { "Content-Type": "application/json" },
|
||
body: JSON.stringify({
|
||
slot: slotNumber,
|
||
data: currentChartData,
|
||
}),
|
||
});
|
||
} else {
|
||
const url = `/CPL?/${window.location.pathname}&KTR${slotNumber}=${selectedId}`;
|
||
await fetch(url, { method: "GET" });
|
||
}
|
||
if (!isDev) {
|
||
const url = `/CPL?KTR${slotNumber}=${selectedId}`;
|
||
const response = await fetch(url, { method: "GET" });
|
||
|
||
if (!response.ok) {
|
||
throw new Error(
|
||
`Fehler beim Setzen der Referenz: ${response.statusText}`
|
||
);
|
||
}
|
||
}
|
||
|
||
// Optional: lokale Speicherung und Redux-Update
|
||
localStorage.setItem(
|
||
`ref-curve-slot${selectedSlot}`,
|
||
JSON.stringify(currentChartData)
|
||
);
|
||
|
||
dispatch(getReferenceCurveBySlotThunk(selectedSlot));
|
||
|
||
alert("Referenzkurve wurde erfolgreich gesetzt!");
|
||
} catch (error) {
|
||
console.error("Fehler beim Setzen der Referenzkurve:", error);
|
||
alert("Fehler beim Setzen der Referenzkurve.");
|
||
}
|
||
};
|
||
|
||
// 📌 TDR Messung starten
|
||
const handleStartTDR = async () => {
|
||
if (selectedSlot === null) {
|
||
alert("⚠️ Bitte zuerst einen KÜ auswählen!");
|
||
return;
|
||
}
|
||
|
||
const cgiUrl = `${window.location.origin}/CPL?/${window.location.pathname}&KTT${selectedSlot}=1`;
|
||
|
||
try {
|
||
console.log("🚀 Starte TDR Messung für Slot:", selectedSlot);
|
||
console.log("📡 CGI URL:", cgiUrl);
|
||
const isDev = process.env.NEXT_PUBLIC_NODE_ENV === "development";
|
||
if (isDev) {
|
||
// Dev / Simulator: sofort starten & Progress anzeigen
|
||
await new Promise((r) => setTimeout(r, 150));
|
||
console.log("✅ [DEV] TDR Mock-Start ok für Slot", selectedSlot);
|
||
startTdrProgress();
|
||
return;
|
||
}
|
||
|
||
const response = await fetch(cgiUrl);
|
||
if (!response.ok) throw new Error(`CGI-Fehler: ${response.status}`);
|
||
console.log("✅ TDR Messung gestartet für Slot", selectedSlot);
|
||
startTdrProgress();
|
||
} catch (err) {
|
||
console.error("❌ Fehler beim Starten der TDR Messung:", err);
|
||
//alert("❌ Fehler beim Starten der TDR Messung.");
|
||
}
|
||
};
|
||
|
||
// 📥 Beim Slot-Wechsel TDM-Liste + letzte ID laden
|
||
useEffect(() => {
|
||
if (selectedSlot !== null) {
|
||
dispatch(fetchTDMDataBySlotThunk(selectedSlot)).then((action) => {
|
||
// action can be a PayloadAction with payload or a rejected action
|
||
const payload = (
|
||
action as {
|
||
payload?: { data?: { id: number; t: string; d: number }[] };
|
||
}
|
||
).payload;
|
||
const slotData = payload?.data;
|
||
if ((slotData ?? []).length > 0) {
|
||
const lastId = (slotData ?? [])[0].id;
|
||
setSelectedId(lastId);
|
||
dispatch(getTDRChartDataByIdThunk(lastId));
|
||
}
|
||
});
|
||
}
|
||
}, [selectedSlot, dispatch]);
|
||
|
||
return (
|
||
<>
|
||
<div className="toolbar w-full flex items-center gap-3 flex-wrap">
|
||
{/* Slot Badge */}
|
||
<div className="flex items-center gap-2 pr-4">
|
||
<span className="font-semibold uppercase tracking-wide text-muted">KÜ</span>
|
||
<span className="font-medium px-2 py-0.5 rounded bg-surface-alt border border-base min-w-[3rem] text-center">
|
||
{selectedSlot !== null ? selectedSlot + 1 : "-"}
|
||
</span>
|
||
</div>
|
||
{/* Referenz speichern */}
|
||
{selectedId !== null && (
|
||
<button
|
||
onClick={handleSetReference}
|
||
type="button"
|
||
className="btn-primary h-8 px-3 font-medium"
|
||
>
|
||
TDR-Kurve als Referenz speichern
|
||
</button>
|
||
)}
|
||
{/* Start TDR */}
|
||
<button
|
||
onClick={handleStartTDR}
|
||
type="button"
|
||
disabled={selectedSlot === null || tdrRunning}
|
||
className={`btn-primary h-8 px-4 whitespace-nowrap ${
|
||
tdrRunning ? "opacity-90" : ""
|
||
}`}
|
||
>
|
||
{tdrRunning
|
||
? `TDR läuft... (${Math.min(
|
||
100,
|
||
Math.round((tdrProgress / TDR_TOTAL_DURATION) * 100)
|
||
)}%)`
|
||
: "TDR-Messung starten"}
|
||
</button>
|
||
{/* Messung Dropdown */}
|
||
<div className="ml-auto flex-1 min-w-[14rem] max-w-[30rem]">
|
||
<Listbox
|
||
value={selectedId}
|
||
onChange={(id) => {
|
||
setSelectedId(id);
|
||
if (id !== null) dispatch(getTDRChartDataByIdThunk(id));
|
||
}}
|
||
disabled={idsForSlot.length === 0}
|
||
>
|
||
<div className="relative w-full">
|
||
<Listbox.Button className="dropdown-surface w-full flex items-center justify-between h-8 disabled:opacity-50 disabled:cursor-not-allowed">
|
||
<span className="dropdown-text-fix whitespace-nowrap overflow-hidden text-ellipsis pr-2">
|
||
{selectedId
|
||
? (() => {
|
||
const selected = idsForSlot.find((e) => e.id === selectedId);
|
||
return selected
|
||
? `${new Date(selected.t).toLocaleString("de-DE", {
|
||
day: "2-digit",
|
||
month: "2-digit",
|
||
year: "numeric",
|
||
hour: "2-digit",
|
||
minute: "2-digit",
|
||
second: "2-digit",
|
||
})} – Fehlerstelle: ${selected.d} m`
|
||
: "Wähle Messung";
|
||
})()
|
||
: "Wähle Messung"}
|
||
</span>
|
||
<i className="bi bi-chevron-down text-sm opacity-70" />
|
||
</Listbox.Button>
|
||
<Listbox.Options className="dropdown-options absolute z-50 mt-1 w-full max-h-72 overflow-auto text-sm bg-[var(--color-surface)] border border-base rounded-md shadow-lg p-1">
|
||
{idsForSlot.map((entry) => {
|
||
const dateLabel = new Date(entry.t).toLocaleString("de-DE", {
|
||
day: "2-digit",
|
||
month: "2-digit",
|
||
year: "numeric",
|
||
hour: "2-digit",
|
||
minute: "2-digit",
|
||
second: "2-digit",
|
||
});
|
||
const fullText = `${dateLabel} – Fehlerstelle: ${entry.d} m`;
|
||
return (
|
||
<Listbox.Option
|
||
key={entry.id}
|
||
value={entry.id}
|
||
title={fullText}
|
||
className={({ selected, active }) => {
|
||
const base = "px-3 h-8 cursor-pointer rounded-sm m-0.5 flex items-center justify-start transition-colors text-[13px]";
|
||
if (selected)
|
||
return `${base} dropdown-option-active font-medium`;
|
||
if (active)
|
||
return `${base} dropdown-option-hover`;
|
||
return `${base}`; // neutral text color comes from parent/theme
|
||
}}
|
||
>
|
||
<span className="truncate w-full">{fullText}</span>
|
||
</Listbox.Option>
|
||
);
|
||
})}
|
||
</Listbox.Options>
|
||
</div>
|
||
</Listbox>
|
||
</div>
|
||
</div>
|
||
{tdrRunning && (
|
||
<div className="fixed inset-0 z-[1000] flex flex-col items-center justify-center bg-[rgba(0,0,0,0.55)] backdrop-blur-sm">
|
||
<div className="mb-4 text-center space-y-1">
|
||
<p className="text-lg font-semibold text-white">
|
||
TDR Messung läuft... kann bis zu zwei Minuten dauern
|
||
</p>
|
||
<p className="text-sm text-white/80">
|
||
Bitte warten… {Math.min(
|
||
100,
|
||
Math.round((tdrProgress / TDR_TOTAL_DURATION) * 100)
|
||
)}%
|
||
</p>
|
||
</div>
|
||
<div className="w-2/3 max-w-xl h-3 bg-white/20 rounded overflow-hidden shadow-inner">
|
||
<div
|
||
className="h-full bg-accent transition-all ease-linear"
|
||
style={{ width: `${(tdrProgress / TDR_TOTAL_DURATION) * 100}%` }}
|
||
/>
|
||
</div>
|
||
</div>
|
||
)}
|
||
</>
|
||
);
|
||
};
|
||
|
||
export default TDRChartActionBar;
|