Files
CPLv4.0/components/main/kabelueberwachung/kue705FO/Charts/LoopMeasurementChart/LoopChartActionBar.tsx
2025-09-10 07:35:11 +02:00

407 lines
13 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/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">
{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;