Files
CPLv4.0/components/main/kabelueberwachung/kue705FO/Charts/TDRChart/TDRChartActionBar.tsx

305 lines
11 KiB
TypeScript
Raw Blame History

This file contains ambiguous Unicode characters

This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.

"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 flex items-center gap-3 flex-wrap">
<div className="flex items-center gap-2 pr-4">
<span className="font-semibold uppercase tracking-wide text-muted">
</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>
{/* Date range always visible */}
<DateRangePicker />
{isMeldungen ? (
<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>
{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;