313 lines
12 KiB
TypeScript
313 lines
12 KiB
TypeScript
"use client";
|
||
// /components/main/kabelueberwachung/kue705FO/Charts/TDRChart/TDRChartActionBar.tsx
|
||
import React, { useEffect, useState } from "react";
|
||
import DateRangePicker from "@/components/common/DateRangePicker";
|
||
import { useAppDispatch } from "@/redux/store";
|
||
import { useSelector } from "react-redux";
|
||
import { RootState } from "@/redux/store";
|
||
import { Listbox } from "@headlessui/react";
|
||
import { getMessagesThunk } from "@/redux/thunks/getMessagesThunk";
|
||
import { setLoading } from "@/redux/slices/kabelueberwachungChartSlice";
|
||
import { fetchTDMDataBySlotThunk } from "@/redux/thunks/getTDMListBySlotThunk";
|
||
import { getTDRChartDataByIdThunk } from "@/redux/thunks/getTDRChartDataByIdThunk";
|
||
import { getReferenceCurveBySlotThunk } from "@/redux/thunks/getReferenceCurveBySlotThunk";
|
||
|
||
const TDRChartActionBar: React.FC = () => {
|
||
const dispatch = useAppDispatch();
|
||
|
||
const { vonDatum, bisDatum, chartTitle } = useSelector(
|
||
(s: RootState) => s.kabelueberwachungChartSlice
|
||
);
|
||
const { vonDatum: pickerVon, bisDatum: pickerBis } = useSelector(
|
||
(s: RootState) => s.dateRangePicker
|
||
);
|
||
|
||
const selectedSlot = useSelector(
|
||
(s: RootState) => s.kueChartModeSlice.selectedSlot
|
||
);
|
||
const tdmChartData = useSelector(
|
||
(s: RootState) => s.tdmSingleChartSlice.data
|
||
);
|
||
const idsForSlot =
|
||
selectedSlot !== null ? tdmChartData[selectedSlot] ?? [] : [];
|
||
const tdrDataById = useSelector(
|
||
(s: RootState) => s.tdrDataByIdSlice.dataById
|
||
);
|
||
const [selectedId, setSelectedId] = useState<number | null>(null);
|
||
const currentChartData = selectedId !== null ? tdrDataById[selectedId] : [];
|
||
|
||
const isMeldungen = chartTitle === "Meldungen";
|
||
|
||
// Progress for running TDR measurement
|
||
const TDR_TOTAL_DURATION = parseInt(
|
||
process.env.NEXT_PUBLIC_TDR_DURATION_SECONDS || "120",
|
||
10
|
||
);
|
||
const [tdrRunning, setTdrRunning] = useState(false);
|
||
const [tdrProgress, setTdrProgress] = useState(0);
|
||
|
||
useEffect(() => {
|
||
if (!tdrRunning) return;
|
||
setTdrProgress(0);
|
||
const started = Date.now();
|
||
const interval = setInterval(() => {
|
||
const elapsed = Math.floor((Date.now() - started) / 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);
|
||
};
|
||
|
||
const handleFetchMessages = async () => {
|
||
const fromDate = pickerVon ?? vonDatum;
|
||
const toDate = pickerBis ?? bisDatum;
|
||
try {
|
||
dispatch(setLoading(true));
|
||
await dispatch(getMessagesThunk({ fromDate, toDate })).unwrap();
|
||
} catch (e) {
|
||
console.error("❌ Fehler beim Laden der Meldungen", e);
|
||
alert("❌ Fehler beim Laden der Meldungen.");
|
||
} finally {
|
||
dispatch(setLoading(false));
|
||
}
|
||
};
|
||
|
||
const handleSetReference = async () => {
|
||
if (
|
||
selectedSlot === null ||
|
||
selectedId === null ||
|
||
!currentChartData.length
|
||
)
|
||
return;
|
||
const isDev = process.env.NEXT_PUBLIC_NODE_ENV === "development";
|
||
try {
|
||
if (!isDev) {
|
||
const url = `/CPL?KTR${selectedSlot + 1}=${selectedId}`;
|
||
const response = await fetch(url, { method: "GET" });
|
||
if (!response.ok)
|
||
throw new Error(
|
||
`Fehler beim Setzen der Referenz: ${response.statusText}`
|
||
);
|
||
}
|
||
localStorage.setItem(
|
||
`ref-curve-slot${selectedSlot}`,
|
||
JSON.stringify(currentChartData)
|
||
);
|
||
dispatch(getReferenceCurveBySlotThunk(selectedSlot));
|
||
alert("Referenzkurve wurde erfolgreich gesetzt!");
|
||
} catch (err) {
|
||
console.error("Fehler beim Setzen der Referenzkurve", err);
|
||
alert("Fehler beim Setzen der Referenzkurve.");
|
||
}
|
||
};
|
||
|
||
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 {
|
||
const isDev = process.env.NEXT_PUBLIC_NODE_ENV === "development";
|
||
if (isDev) {
|
||
await new Promise((r) => setTimeout(r, 150));
|
||
startTdrProgress();
|
||
return;
|
||
}
|
||
const response = await fetch(cgiUrl);
|
||
if (!response.ok) throw new Error(`CGI-Fehler: ${response.status}`);
|
||
startTdrProgress();
|
||
} catch (err) {
|
||
console.error("❌ Fehler beim Starten der TDR Messung", err);
|
||
}
|
||
};
|
||
|
||
// Load TDM list when slot changes
|
||
useEffect(() => {
|
||
if (selectedSlot !== null) {
|
||
dispatch(fetchTDMDataBySlotThunk(selectedSlot)).then((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; // latest first
|
||
setSelectedId(lastId);
|
||
dispatch(getTDRChartDataByIdThunk(lastId));
|
||
}
|
||
});
|
||
}
|
||
}, [selectedSlot, dispatch]);
|
||
|
||
return (
|
||
<>
|
||
<div className="toolbar w-full justify-between flex-wrap">
|
||
{/* KÜ number left, controls right, like IsoChartActionBar */}
|
||
<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>
|
||
<div className="flex items-center gap-3 flex-1 justify-end">
|
||
{isMeldungen ? (
|
||
<>
|
||
<DateRangePicker />
|
||
<button
|
||
type="button"
|
||
onClick={handleFetchMessages}
|
||
className="btn-primary h-8 font-medium px-4"
|
||
disabled={selectedSlot === null}
|
||
>
|
||
Anzeigen
|
||
</button>
|
||
</>
|
||
) : (
|
||
<>
|
||
{selectedId !== null && (
|
||
<button
|
||
onClick={handleSetReference}
|
||
type="button"
|
||
className="btn-primary h-8 px-3 font-medium"
|
||
disabled={selectedSlot === null}
|
||
>
|
||
TDR-Kurve als Referenz speichern
|
||
</button>
|
||
)}
|
||
<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>
|
||
<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}`;
|
||
}}
|
||
>
|
||
<span className="truncate w-full">{fullText}</span>
|
||
</Listbox.Option>
|
||
);
|
||
})}
|
||
</Listbox.Options>
|
||
</div>
|
||
</Listbox>
|
||
</div>
|
||
</>
|
||
)}
|
||
</div>
|
||
{/* Progress Overlay */}
|
||
{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>
|
||
)}
|
||
</div>
|
||
</>
|
||
);
|
||
};
|
||
|
||
export default TDRChartActionBar;
|