407 lines
13 KiB
TypeScript
407 lines
13 KiB
TypeScript
"use client";
|
||
// /components/main/kabelueberwachung/kue705FO/Charts/LoopMeasurementChart/LoopChartActionBar.tsx
|
||
import React, {
|
||
useEffect,
|
||
useState,
|
||
forwardRef,
|
||
useImperativeHandle,
|
||
} from "react";
|
||
import { useAppDispatch } from "@/redux/store";
|
||
import { getMessagesThunk } from "@/redux/thunks/getMessagesThunk";
|
||
import { RSL_DURATION_SECONDS, NODE_ENV } from "@/utils/env";
|
||
import DateRangePicker from "@/components/common/DateRangePicker";
|
||
import { useSelector } from "react-redux";
|
||
import { RootState } from "@/redux/store";
|
||
import {
|
||
setLoopMeasurementCurveChartData,
|
||
setSelectedMode,
|
||
setChartOpen,
|
||
setLoading,
|
||
} from "@/redux/slices/kabelueberwachungChartSlice";
|
||
import { setBrushRange } from "@/redux/slices/brushSlice";
|
||
import { Listbox } from "@headlessui/react";
|
||
|
||
//-----------------------------------------------------------------------------------useLoopChartLoader
|
||
export const useLoopChartLoader = () => {
|
||
const dispatch = useAppDispatch();
|
||
const { vonDatum, bisDatum, selectedMode, selectedSlotType, slotNumber } =
|
||
useSelector((state: RootState) => state.kabelueberwachungChartSlice);
|
||
const hasShownNoDataAlert = React.useRef(false);
|
||
|
||
const formatDate = (dateString: string) => {
|
||
const [year, month, day] = dateString.split("-");
|
||
return `${year};${month};${day}`;
|
||
};
|
||
|
||
const getApiUrl = (
|
||
mode: "DIA0" | "DIA1" | "DIA2",
|
||
type: number,
|
||
slotNumber: number
|
||
) => {
|
||
const typeFolder = "schleifenwiderstand";
|
||
|
||
let url: string;
|
||
|
||
if (process.env.NODE_ENV === "development") {
|
||
url = `/api/cpl/slotDataAPIHandler?slot=${slotNumber}&messart=${typeFolder}&dia=${mode}&vonDatum=${vonDatum}&bisDatum=${bisDatum}`;
|
||
} else {
|
||
url = `${window.location.origin}/CPL?seite.ACP&${mode}=${formatDate(
|
||
vonDatum
|
||
)};${formatDate(bisDatum)};${slotNumber};${type};`;
|
||
}
|
||
|
||
console.log("API URL:", url); // Hier wird die URL in der Konsole ausgegeben
|
||
return url;
|
||
};
|
||
|
||
const loadLoopChartData = async () => {
|
||
const type = selectedSlotType === "schleifenwiderstand" ? 4 : 3;
|
||
if (slotNumber === null) return;
|
||
|
||
dispatch(setLoading(true));
|
||
dispatch(setChartOpen(false));
|
||
dispatch(setLoopMeasurementCurveChartData([]));
|
||
|
||
const startTime = Date.now();
|
||
const MIN_LOADING_TIME_MS = 1000;
|
||
|
||
try {
|
||
const apiUrl = getApiUrl(selectedMode, type, slotNumber);
|
||
const response = await fetch(apiUrl);
|
||
const data = await response.json();
|
||
|
||
const waitTime = Math.max(
|
||
0,
|
||
MIN_LOADING_TIME_MS - (Date.now() - startTime)
|
||
);
|
||
await new Promise((res) => setTimeout(res, waitTime));
|
||
|
||
if (Array.isArray(data) && data.length > 0) {
|
||
dispatch(setLoopMeasurementCurveChartData(data));
|
||
dispatch(setChartOpen(true));
|
||
} else {
|
||
dispatch(setLoopMeasurementCurveChartData([]));
|
||
dispatch(setChartOpen(false));
|
||
if (!hasShownNoDataAlert.current) {
|
||
alert("⚠️ Keine Messdaten im gewählten Zeitraum gefunden");
|
||
hasShownNoDataAlert.current = true; // ⬅️ Nur einmal zeigen
|
||
}
|
||
}
|
||
} catch (err) {
|
||
console.error("❌ Fehler beim Laden:", err);
|
||
alert("❌ Fehler beim Laden.");
|
||
} finally {
|
||
dispatch(setLoading(false));
|
||
}
|
||
};
|
||
|
||
return { loadLoopChartData };
|
||
};
|
||
|
||
//-----------------------------------------------------------------------------------LoopChartActionBar
|
||
const LoopChartActionBar = forwardRef((_props, ref) => {
|
||
const dispatch = useAppDispatch();
|
||
// RSL Progress State – Dauer konfigurierbar über NEXT_PUBLIC_RSL_DURATION_SECONDS
|
||
const TOTAL_DURATION = RSL_DURATION_SECONDS;
|
||
const [rslRunning, setRslRunning] = useState(false);
|
||
const [rslProgress, setRslProgress] = useState(0);
|
||
|
||
// Fortschritt aktualisieren
|
||
useEffect(() => {
|
||
if (!rslRunning) return;
|
||
setRslProgress(0);
|
||
const startedAt = Date.now();
|
||
const interval = setInterval(() => {
|
||
const elapsed = Math.floor((Date.now() - startedAt) / 1000);
|
||
if (elapsed >= TOTAL_DURATION) {
|
||
setRslProgress(TOTAL_DURATION);
|
||
setRslRunning(false);
|
||
clearInterval(interval);
|
||
// Optional automatische Daten-Nachladung anstoßen
|
||
} else {
|
||
setRslProgress(elapsed);
|
||
}
|
||
}, 1000);
|
||
return () => clearInterval(interval);
|
||
}, [rslRunning, TOTAL_DURATION]);
|
||
|
||
const startRslProgress = () => {
|
||
setRslRunning(true);
|
||
setRslProgress(0);
|
||
};
|
||
|
||
const {
|
||
vonDatum,
|
||
bisDatum,
|
||
selectedMode,
|
||
selectedSlotType,
|
||
slotNumber,
|
||
isLoading,
|
||
} = useSelector((state: RootState) => state.kabelueberwachungChartSlice);
|
||
|
||
const { chartTitle } = useSelector((state: RootState) => state.loopChartType);
|
||
// Vom DateRangePicker-Slice (vom UI gewählte Werte)
|
||
const { vonDatum: pickerVonDatum, bisDatum: pickerBisDatum } = useSelector(
|
||
(state: RootState) => state.dateRangePicker
|
||
);
|
||
|
||
const getApiUrl = (
|
||
mode: "DIA0" | "DIA1" | "DIA2",
|
||
type: number,
|
||
slotNumber: number,
|
||
fromDate: string,
|
||
toDate: string
|
||
) => {
|
||
const typeFolder = "schleifenwiderstand";
|
||
|
||
const baseUrl =
|
||
process.env.NODE_ENV === "development"
|
||
? `/api/cpl/slotDataAPIHandler?slot=${slotNumber}&messart=${typeFolder}&dia=${mode}&vonDatum=${fromDate}&bisDatum=${toDate}`
|
||
: `${window.location.origin}/CPL?seite.ACP&${mode}=${formatDate(
|
||
fromDate
|
||
)};${formatDate(toDate)};${slotNumber};${type};`;
|
||
console.log("baseUrl", baseUrl);
|
||
|
||
return baseUrl;
|
||
};
|
||
|
||
const formatDate = (dateString: string) => {
|
||
const [year, month, day] = dateString.split("-");
|
||
return `${year};${month};${day}`;
|
||
};
|
||
|
||
const handleStartRSL = async () => {
|
||
if (slotNumber === null) {
|
||
alert("⚠️ Bitte zuerst einen KÜ auswählen!");
|
||
return;
|
||
}
|
||
|
||
const cgiUrl = `${window.location.origin}/CPL?kabelueberwachung.html&KS_${slotNumber}=1`;
|
||
|
||
try {
|
||
console.log("🚀 Starte RSL Messung für Slot:", slotNumber);
|
||
console.log("📡 CGI URL:", cgiUrl);
|
||
|
||
if (NODE_ENV === "development") {
|
||
// DEV: externes Gerät mocken – sofort Erfolg simulieren
|
||
await new Promise((r) => setTimeout(r, 200));
|
||
console.log("✅ [DEV] RSL Mock-Start ok für Slot", slotNumber);
|
||
startRslProgress();
|
||
return;
|
||
}
|
||
|
||
const response = await fetch(cgiUrl);
|
||
if (!response.ok) throw new Error(`CGI-Fehler: ${response.status}`);
|
||
console.log("✅ RSL Messung gestartet für Slot", slotNumber);
|
||
startRslProgress();
|
||
} catch (err) {
|
||
console.error("❌ Fehler beim Starten der RSL Messung:", err);
|
||
alert("❌ Fehler beim Starten der RSL Messung.");
|
||
}
|
||
};
|
||
|
||
const handleFetchData = async () => {
|
||
const type = selectedSlotType === "schleifenwiderstand" ? 4 : 3;
|
||
|
||
if (slotNumber === null) {
|
||
alert("⚠️ Bitte zuerst einen KÜ auswählen!");
|
||
return;
|
||
}
|
||
|
||
// Meldungen laden, wenn Meldungen-Ansicht aktiv ist
|
||
if (chartTitle === "Meldungen") {
|
||
try {
|
||
dispatch(setLoading(true));
|
||
const fromDate = pickerVonDatum ?? vonDatum;
|
||
const toDate = pickerBisDatum ?? bisDatum;
|
||
await dispatch(getMessagesThunk({ fromDate, toDate })).unwrap();
|
||
} catch (err) {
|
||
const message = err instanceof Error ? err.message : String(err);
|
||
console.error("❌ Fehler beim Laden der Meldungen:", message);
|
||
alert("❌ Fehler beim Laden der Meldungen.");
|
||
} finally {
|
||
dispatch(setLoading(false));
|
||
}
|
||
return;
|
||
}
|
||
|
||
const fromDate = pickerVonDatum ?? vonDatum;
|
||
const toDate = pickerBisDatum ?? bisDatum;
|
||
const apiUrl = getApiUrl(selectedMode, type, slotNumber, fromDate, toDate);
|
||
if (!apiUrl) return;
|
||
|
||
dispatch(setLoading(true));
|
||
dispatch(setChartOpen(false));
|
||
dispatch(setLoopMeasurementCurveChartData([]));
|
||
|
||
const MIN_LOADING_TIME_MS = 1000;
|
||
const startTime = Date.now();
|
||
|
||
try {
|
||
const response = await fetch(apiUrl, {
|
||
method: "GET",
|
||
headers: { "Content-Type": "application/json" },
|
||
});
|
||
|
||
if (!response.ok) throw new Error(`Fehler: ${response.status}`);
|
||
|
||
const jsonData = await response.json();
|
||
const elapsedTime = Date.now() - startTime;
|
||
const waitTime = Math.max(0, MIN_LOADING_TIME_MS - elapsedTime);
|
||
await new Promise((resolve) => setTimeout(resolve, waitTime));
|
||
//------------------------
|
||
console.log("▶️ Lade Daten für:");
|
||
console.log(" Slot:", slotNumber);
|
||
console.log(" Typ:", selectedSlotType, "→", type);
|
||
console.log(" Modus:", selectedMode);
|
||
console.log(" Von:", fromDate);
|
||
console.log(" Bis:", toDate);
|
||
console.log(" URL:", apiUrl);
|
||
console.log(" Daten:", jsonData);
|
||
|
||
//-----------------------
|
||
|
||
if (Array.isArray(jsonData) && jsonData.length > 0) {
|
||
dispatch(setLoopMeasurementCurveChartData(jsonData));
|
||
dispatch(setChartOpen(true));
|
||
} else {
|
||
alert("⚠️ Keine Messdaten im gewählten Zeitraum gefunden.");
|
||
dispatch(setLoopMeasurementCurveChartData([]));
|
||
dispatch(setChartOpen(false));
|
||
}
|
||
} catch (err) {
|
||
console.error("❌ Fehler beim Laden der Daten:", err);
|
||
alert("❌ Fehler beim Laden der Daten.");
|
||
} finally {
|
||
dispatch(setLoading(false));
|
||
}
|
||
};
|
||
|
||
useImperativeHandle(ref, () => ({
|
||
handleFetchData,
|
||
}));
|
||
|
||
// Sichtbarkeits-Flags
|
||
const isMesskurve = chartTitle === "Messkurve";
|
||
const isMeldungen = chartTitle === "Meldungen";
|
||
|
||
return (
|
||
<div className="toolbar w-full flex flex-wrap items-center gap-2">
|
||
<div className="flex items-center mr-2 min-w-[4rem]">
|
||
<span className="text-xs font-semibold opacity-80 select-none">
|
||
KÜ {slotNumber !== null ? slotNumber + 1 : "-"}
|
||
</span>
|
||
</div>
|
||
|
||
<div className="flex items-center gap-3 flex-1 justify-end">
|
||
{/* DateRangePicker immer sichtbar */}
|
||
<DateRangePicker />
|
||
|
||
{/* Modus-Dropdown nur für Messkurve */}
|
||
<div className={isMesskurve ? "" : "hidden"}>
|
||
<Listbox
|
||
value={selectedMode}
|
||
onChange={(value) => {
|
||
dispatch(setSelectedMode(value));
|
||
dispatch(setBrushRange({ startIndex: 0, endIndex: 0 }));
|
||
}}
|
||
>
|
||
<div className="relative w-48">
|
||
<Listbox.Button className="dropdown-surface w-full flex items-center justify-between">
|
||
<span className="dropdown-text-fix">
|
||
{
|
||
{
|
||
DIA0: "Alle Messwerte",
|
||
DIA1: "Stündlich",
|
||
DIA2: "Täglich",
|
||
}[selectedMode]
|
||
}
|
||
</span>
|
||
<i className="bi bi-chevron-down opacity-70" />
|
||
</Listbox.Button>
|
||
<Listbox.Options className="dropdown-options absolute z-50 mt-1 w-full max-h-60 overflow-auto">
|
||
{["DIA0", "DIA1", "DIA2"].map((mode) => (
|
||
<Listbox.Option
|
||
key={mode}
|
||
value={mode}
|
||
className={({ selected, active }) =>
|
||
`px-3 py-1.5 cursor-pointer rounded-sm m-0.5 ${
|
||
selected
|
||
? "dropdown-option-active"
|
||
: active
|
||
? "dropdown-option-hover"
|
||
: ""
|
||
}`
|
||
}
|
||
>
|
||
{
|
||
{
|
||
DIA0: "Alle Messwerte",
|
||
DIA1: "Stündlich",
|
||
DIA2: "Täglich",
|
||
}[mode]
|
||
}
|
||
</Listbox.Option>
|
||
))}
|
||
</Listbox.Options>
|
||
</div>
|
||
</Listbox>
|
||
</div>
|
||
|
||
{/* Buttons */}
|
||
{isMesskurve && (
|
||
<div className="flex items-center gap-2">
|
||
<button
|
||
onClick={handleStartRSL}
|
||
className="btn-primary h-8 font-medium px-3"
|
||
disabled={isLoading || rslRunning}
|
||
type="button"
|
||
>
|
||
{rslRunning ? "RSL läuft…" : "RSL Messung starten"}
|
||
</button>
|
||
<button
|
||
onClick={handleFetchData}
|
||
className="btn-primary h-8 font-medium px-3"
|
||
disabled={rslRunning}
|
||
type="button"
|
||
>
|
||
Daten laden
|
||
</button>
|
||
</div>
|
||
)}
|
||
{isMeldungen && (
|
||
<button
|
||
onClick={handleFetchData}
|
||
className="btn-primary h-8 font-medium px-4"
|
||
disabled={rslRunning}
|
||
type="button"
|
||
>
|
||
Anzeigen
|
||
</button>
|
||
)}
|
||
</div>
|
||
|
||
{rslRunning && (
|
||
<div className="fixed inset-0 z-[1000] flex flex-col items-center justify-center bg-black/60 backdrop-blur-sm">
|
||
<div className="mb-4 text-center space-y-1">
|
||
<p className="text-sm font-semibold">RSL Messung läuft</p>
|
||
<p className="text-xs opacity-80">
|
||
Bitte warten…{" "}
|
||
{Math.min(100, Math.round((rslProgress / TOTAL_DURATION) * 100))}%
|
||
</p>
|
||
</div>
|
||
<div className="w-2/3 max-w-xl h-3 bg-[var(--color-border)] rounded overflow-hidden shadow-inner">
|
||
<div
|
||
className="h-full bg-littwin-blue transition-all ease-linear"
|
||
style={{ width: `${(rslProgress / TOTAL_DURATION) * 100}%` }}
|
||
/>
|
||
</div>
|
||
</div>
|
||
)}
|
||
</div>
|
||
);
|
||
});
|
||
|
||
LoopChartActionBar.displayName = "LoopChartActionBar";
|
||
export default LoopChartActionBar;
|