Files
CPLv4.0/components/main/kabelueberwachung/kue705FO/Charts/LoopMeasurementChart/LoopChartActionBar.tsx

376 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 } from "react";
import { RSL_DURATION_SECONDS, NODE_ENV } from "@/utils/env";
import DateRangePicker from "@/components/common/DateRangePicker";
import { useDispatch, 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 = useDispatch();
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: React.FC = () => {
const dispatch = useDispatch();
// 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);
const getApiUrl = (
mode: "DIA0" | "DIA1" | "DIA2",
type: number,
slotNumber: number
) => {
const typeFolder = "schleifenwiderstand";
const baseUrl =
process.env.NODE_ENV === "development"
? `/api/cpl/slotDataAPIHandler?slot=${slotNumber}&messart=${typeFolder}&dia=${mode}&vonDatum=${vonDatum}&bisDatum=${bisDatum}`
: `${window.location.origin}/CPL?seite.ACP&${mode}=${formatDate(
vonDatum
)};${formatDate(bisDatum)};${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;
}
const apiUrl = getApiUrl(selectedMode, type, slotNumber);
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:", vonDatum);
console.log(" Bis:", bisDatum);
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));
}
};
return (
<div className="flex justify-between p-1 bg-gray-100 rounded-lg ">
<div className="flex items-center">
<label className="text-sm font-semibold">
{slotNumber !== null ? slotNumber + 1 : "-"}
</label>
</div>
<div className="flex items-center space-x-2">
{/* DateRangePicker wie bei ISO nur sichtbar bei Messkurve */}
<div
style={{
visibility: chartTitle === "Messkurve" ? "visible" : "hidden",
}}
>
<DateRangePicker compact />
</div>
{/* DIA0/DIA1/DIA2 Dropdown nur sichtbar bei Messkurve */}
<div
style={{
visibility: chartTitle === "Messkurve" ? "visible" : "hidden",
}}
>
<Listbox
value={selectedMode}
onChange={(value) => {
dispatch(setSelectedMode(value));
dispatch(setBrushRange({ startIndex: 0, endIndex: 0 }));
}}
>
<div className="relative w-48">
<Listbox.Button className="w-full border px-3 py-1 rounded text-left bg-white flex justify-between items-center text-sm">
<span>
{
{
DIA0: "Alle Messwerte",
DIA1: "Stündliche Werte",
DIA2: "Tägliche Werte",
}[selectedMode]
}
</span>
<svg
className="w-5 h-5 text-gray-400"
viewBox="0 0 20 20"
fill="currentColor"
>
<path
fillRule="evenodd"
d="M5.23 7.21a.75.75 0 011.06.02L10 10.585l3.71-3.355a.75.75 0 111.02 1.1l-4.25 3.85a.75.75 0 01-1.02 0l-4.25-3.85a.75.75 0 01.02-1.06z"
clipRule="evenodd"
/>
</svg>
</Listbox.Button>
<Listbox.Options className="absolute z-50 mt-1 w-full border rounded bg-white shadow max-h-60 overflow-auto text-sm">
{["DIA0", "DIA1", "DIA2"].map((mode) => (
<Listbox.Option
key={mode}
value={mode}
className={({ selected, active }) =>
`px-4 py-1 cursor-pointer ${
selected
? "bg-littwin-blue text-white"
: active
? "bg-gray-200"
: ""
}`
}
>
{
{
DIA0: "Alle Messwerte",
DIA1: "Stündliche Werte",
DIA2: "Tägliche Werte",
}[mode]
}
</Listbox.Option>
))}
</Listbox.Options>
</div>
</Listbox>
</div>
{/* Dropdown für Messkurve / Meldungen in View-Header umgezogen */}
{/* Buttons nur sichtbar bei Messkurve, Platz bleibt erhalten */}
<div
style={{
visibility: chartTitle === "Messkurve" ? "visible" : "hidden",
}}
className="flex items-center space-x-2"
>
<button
onClick={handleStartRSL}
className="px-4 py-1 bg-littwin-blue text-white rounded text-sm whitespace-nowrap"
disabled={isLoading || rslRunning}
>
{rslRunning ? "RSL läuft..." : "RSL starten"}
</button>
<button
onClick={handleFetchData}
className="px-4 py-1 bg-littwin-blue text-white rounded text-sm whitespace-nowrap"
disabled={rslRunning}
>
Daten laden
</button>
</div>
</div>
{rslRunning && (
<div className="fixed inset-0 z-[1000] flex flex-col items-center justify-center bg-white/80 backdrop-blur-sm">
<div className="mb-4 text-center space-y-1">
<p className="text-lg font-semibold">RSL Messung läuft</p>
<p className="text-sm text-gray-700">
Bitte warten (noch {TOTAL_DURATION - rslProgress}s)
</p>
</div>
<div className="w-2/3 max-w-xl h-4 bg-gray-200 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>
);
};
export default LoopChartActionBar;