WIP: von bis Zeitraum in ISO und TDR, aber TDR ist WIP

This commit is contained in:
ISA
2025-09-09 14:48:03 +02:00
parent 4c6fe0db03
commit 18c9c886ec
9 changed files with 262 additions and 239 deletions

View File

@@ -6,6 +6,6 @@ NEXT_PUBLIC_USE_MOCK_BACKEND_LOOP_START=false
NEXT_PUBLIC_EXPORT_STATIC=false NEXT_PUBLIC_EXPORT_STATIC=false
NEXT_PUBLIC_USE_CGI=false NEXT_PUBLIC_USE_CGI=false
# App-Versionsnummer # App-Versionsnummer
NEXT_PUBLIC_APP_VERSION=1.6.884 NEXT_PUBLIC_APP_VERSION=1.6.885
NEXT_PUBLIC_CPL_MODE=json # json (Entwicklungsumgebung) oder jsSimulatedProd (CPL ->CGI-Interface-Simulator) oder production (CPL-> CGI-Interface Platzhalter) NEXT_PUBLIC_CPL_MODE=json # json (Entwicklungsumgebung) oder jsSimulatedProd (CPL ->CGI-Interface-Simulator) oder production (CPL-> CGI-Interface Platzhalter)

View File

@@ -5,5 +5,5 @@ NEXT_PUBLIC_CPL_API_PATH=/CPL
NEXT_PUBLIC_EXPORT_STATIC=true NEXT_PUBLIC_EXPORT_STATIC=true
NEXT_PUBLIC_USE_CGI=true NEXT_PUBLIC_USE_CGI=true
# App-Versionsnummer # App-Versionsnummer
NEXT_PUBLIC_APP_VERSION=1.6.884 NEXT_PUBLIC_APP_VERSION=1.6.885
NEXT_PUBLIC_CPL_MODE=production NEXT_PUBLIC_CPL_MODE=production

View File

@@ -1,3 +1,8 @@
## [1.6.885] 2025-09-09
- test: rename test files *.test.ts
---
## [1.6.884] 2025-09-09 ## [1.6.884] 2025-09-09
- Tests: TDR ISO und RSL - Tests: TDR ISO und RSL

View File

@@ -271,7 +271,7 @@ const IsoChartActionBar = forwardRef((_props, ref) => {
useImperativeHandle(ref, () => ({ handleFetchData })); useImperativeHandle(ref, () => ({ handleFetchData }));
const hiddenWhenMeldungen = chartTitle !== "Messkurve"; const isMeldungen = chartTitle === "Meldungen";
return ( return (
<div className="toolbar w-full justify-between flex-wrap"> <div className="toolbar w-full justify-between flex-wrap">
@@ -283,80 +283,76 @@ const IsoChartActionBar = forwardRef((_props, ref) => {
{slotNumber !== null ? slotNumber + 1 : "-"} {slotNumber !== null ? slotNumber + 1 : "-"}
</span> </span>
</div> </div>
<div className="flex items-center gap-2 flex-1 justify-end"> <div className="flex items-center gap-3 flex-1 justify-end">
<div {/* Always show date range; requirement: in Meldungen only Von/Bis + Anzeigen */}
className={ <DateRangePicker />
hiddenWhenMeldungen {!isMeldungen && (
? "opacity-0 pointer-events-none" <>
: "opacity-100" <Listbox
} value={selectedMode}
> onChange={(value) => {
<DateRangePicker /> dispatch(setSelectedMode(value));
</div> dispatch(setBrushRange({ startIndex: 0, endIndex: 0 }));
<div }}
className={`transition-opacity ${ >
hiddenWhenMeldungen <div className="relative w-48">
? "opacity-0 pointer-events-none" <Listbox.Button className="dropdown-surface w-full flex items-center justify-between">
: "opacity-100" <span className="dropdown-text-fix">
}`}
>
<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", DIA0: "Alle Messwerte",
DIA1: "Stündlich", DIA1: "Stündlich",
DIA2: "Täglich", DIA2: "Täglich",
}[mode as "DIA0" | "DIA1" | "DIA2"] }[selectedMode]
} }
</Listbox.Option> </span>
))} <i className="bi bi-chevron-down opacity-70" />
</Listbox.Options> </Listbox.Button>
</div> <Listbox.Options className="dropdown-options absolute z-50 mt-1 w-full max-h-60 overflow-auto">
</Listbox> {["DIA0", "DIA1", "DIA2"].map((mode) => (
</div> <Listbox.Option
<button key={mode}
onClick={handleFetchData} value={mode}
className={`btn-primary h-8 font-medium px-3 ${ className={({ selected, active }) =>
hiddenWhenMeldungen ? "invisible" : "visible" `px-3 py-1.5 cursor-pointer rounded-sm m-0.5 ${
}`} selected
type="button" ? "dropdown-option-active"
> : active
Daten laden ? "dropdown-option-hover"
</button> : ""
}`
}
>
{
{
DIA0: "Alle Messwerte",
DIA1: "Stündlich",
DIA2: "Täglich",
}[mode as "DIA0" | "DIA1" | "DIA2"]
}
</Listbox.Option>
))}
</Listbox.Options>
</div>
</Listbox>
<button
onClick={handleFetchData}
className="btn-primary h-8 font-medium px-3"
type="button"
>
Daten laden
</button>
</>
)}
{isMeldungen && (
<button
onClick={handleFetchData}
className="btn-primary h-8 font-medium px-4"
type="button"
>
Anzeigen
</button>
)}
</div> </div>
</div> </div>
); );

View File

@@ -283,6 +283,7 @@ const LoopChartActionBar = forwardRef((_props, ref) => {
// Sichtbarkeits-Flags // Sichtbarkeits-Flags
const isMesskurve = chartTitle === "Messkurve"; const isMesskurve = chartTitle === "Messkurve";
const isMeldungen = chartTitle === "Meldungen";
return ( return (
<div className="toolbar w-full flex flex-wrap items-center gap-2"> <div className="toolbar w-full flex flex-wrap items-center gap-2">
@@ -347,25 +348,37 @@ const LoopChartActionBar = forwardRef((_props, ref) => {
</Listbox> </Listbox>
</div> </div>
{/* Buttons nur für Messkurve */} {/* Buttons */}
<div className={isMesskurve ? "flex items-center gap-2" : "hidden"}> {isMesskurve && (
<button <div className="flex items-center gap-2">
onClick={handleStartRSL} <button
className="btn-primary h-8 font-medium px-3" onClick={handleStartRSL}
disabled={isLoading || rslRunning} className="btn-primary h-8 font-medium px-3"
type="button" disabled={isLoading || rslRunning}
> type="button"
{rslRunning ? "RSL läuft…" : "RSL Messung starten"} >
</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 <button
onClick={handleFetchData} onClick={handleFetchData}
className="btn-primary h-8 font-medium px-3" className="btn-primary h-8 font-medium px-4"
disabled={rslRunning} disabled={rslRunning}
type="button" type="button"
> >
Daten laden Anzeigen
</button> </button>
</div> )}
</div> </div>
{rslRunning && ( {rslRunning && (

View File

@@ -1,49 +1,57 @@
"use client";
// /components/main/kabelueberwachung/kue705FO/Charts/TDRChart/TDRChartActionBar.tsx // /components/main/kabelueberwachung/kue705FO/Charts/TDRChart/TDRChartActionBar.tsx
import React, { useEffect, useState } from "react";
import React, { useState, useEffect } from "react"; import DateRangePicker from "@/components/common/DateRangePicker";
import { useSelector } from "react-redux";
import { useAppDispatch } from "@/redux/store"; import { useAppDispatch } from "@/redux/store";
import { useSelector } from "react-redux";
import { RootState } from "@/redux/store"; 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 { fetchTDMDataBySlotThunk } from "@/redux/thunks/getTDMListBySlotThunk";
import { getTDRChartDataByIdThunk } from "@/redux/thunks/getTDRChartDataByIdThunk"; import { getTDRChartDataByIdThunk } from "@/redux/thunks/getTDRChartDataByIdThunk";
import { getReferenceCurveBySlotThunk } from "@/redux/thunks/getReferenceCurveBySlotThunk"; // ⬅ import ergänzen import { getReferenceCurveBySlotThunk } from "@/redux/thunks/getReferenceCurveBySlotThunk";
import { Listbox } from "@headlessui/react";
const TDRChartActionBar: React.FC = () => { const TDRChartActionBar: React.FC = () => {
const dispatch = useAppDispatch(); const dispatch = useAppDispatch();
// ✅ Redux: selectedSlot aus kueChartMode (0-basiert) const { vonDatum, bisDatum, chartTitle } = useSelector(
(s: RootState) => s.kabelueberwachungChartSlice
);
const { vonDatum: pickerVon, bisDatum: pickerBis } = useSelector(
(s: RootState) => s.dateRangePicker
);
const selectedSlot = useSelector( const selectedSlot = useSelector(
(state: RootState) => state.kueChartModeSlice.selectedSlot (s: RootState) => s.kueChartModeSlice.selectedSlot
); );
const tdmChartData = useSelector( const tdmChartData = useSelector(
(state: RootState) => state.tdmSingleChartSlice.data (s: RootState) => s.tdmSingleChartSlice.data
); );
const idsForSlot = const idsForSlot =
selectedSlot !== null ? tdmChartData[selectedSlot] ?? [] : []; selectedSlot !== null ? tdmChartData[selectedSlot] ?? [] : [];
const tdrDataById = useSelector( const tdrDataById = useSelector(
(state: RootState) => state.tdrDataByIdSlice.dataById (s: RootState) => s.tdrDataByIdSlice.dataById
); );
const [selectedId, setSelectedId] = useState<number | null>(null); const [selectedId, setSelectedId] = useState<number | null>(null);
const currentChartData = selectedId !== null ? tdrDataById[selectedId] : []; const currentChartData = selectedId !== null ? tdrDataById[selectedId] : [];
// ▶ Fortschrittsanzeige für laufende TDR-Messung (max. 120s bzw. konfigurierbar) const isMeldungen = chartTitle === "Meldungen";
// Progress for running TDR measurement
const TDR_TOTAL_DURATION = parseInt( const TDR_TOTAL_DURATION = parseInt(
process.env.NEXT_PUBLIC_TDR_DURATION_SECONDS || "120", process.env.NEXT_PUBLIC_TDR_DURATION_SECONDS || "120",
10 10
); );
const [tdrRunning, setTdrRunning] = useState(false); const [tdrRunning, setTdrRunning] = useState(false);
const [tdrProgress, setTdrProgress] = useState(0); // Sekunden const [tdrProgress, setTdrProgress] = useState(0);
useEffect(() => { useEffect(() => {
if (!tdrRunning) return; if (!tdrRunning) return;
setTdrProgress(0); setTdrProgress(0);
const startedAt = Date.now(); const started = Date.now();
const interval = setInterval(() => { const interval = setInterval(() => {
const elapsed = Math.floor((Date.now() - startedAt) / 1000); const elapsed = Math.floor((Date.now() - started) / 1000);
if (elapsed >= TDR_TOTAL_DURATION) { if (elapsed >= TDR_TOTAL_DURATION) {
setTdrProgress(TDR_TOTAL_DURATION); setTdrProgress(TDR_TOTAL_DURATION);
setTdrRunning(false); setTdrRunning(false);
@@ -60,102 +68,82 @@ const TDRChartActionBar: React.FC = () => {
setTdrProgress(0); setTdrProgress(0);
}; };
// 📌 Referenz setzen (nutzt Slotnummer + 1 für die API) 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 () => { const handleSetReference = async () => {
if ( if (
selectedSlot === null || selectedSlot === null ||
selectedId === null || selectedId === null ||
!currentChartData?.length !currentChartData.length
) )
return; return;
const isDev = process.env.NEXT_PUBLIC_NODE_ENV === "development";
try { try {
const slotNumber = selectedSlot + 1; // Slot ist 0-basiert, API will 1-basiert
const isDev = process.env.NEXT_PUBLIC_NODE_ENV === "development";
if (isDev) {
await fetch("/api/cpl/updateTdrReferenceCurveAPIHandler", {
method: "POST",
headers: { "Content-Type": "application/json" },
body: JSON.stringify({
slot: slotNumber,
data: currentChartData,
}),
});
} else {
const url = `/CPL?/${window.location.pathname}&KTR${slotNumber}=${selectedId}`;
await fetch(url, { method: "GET" });
}
if (!isDev) { if (!isDev) {
const url = `/CPL?KTR${slotNumber}=${selectedId}`; const url = `/CPL?KTR${selectedSlot + 1}=${selectedId}`;
const response = await fetch(url, { method: "GET" }); const response = await fetch(url, { method: "GET" });
if (!response.ok)
if (!response.ok) {
throw new Error( throw new Error(
`Fehler beim Setzen der Referenz: ${response.statusText}` `Fehler beim Setzen der Referenz: ${response.statusText}`
); );
}
} }
// Optional: lokale Speicherung und Redux-Update
localStorage.setItem( localStorage.setItem(
`ref-curve-slot${selectedSlot}`, `ref-curve-slot${selectedSlot}`,
JSON.stringify(currentChartData) JSON.stringify(currentChartData)
); );
dispatch(getReferenceCurveBySlotThunk(selectedSlot)); dispatch(getReferenceCurveBySlotThunk(selectedSlot));
alert("Referenzkurve wurde erfolgreich gesetzt!"); alert("Referenzkurve wurde erfolgreich gesetzt!");
} catch (error) { } catch (err) {
console.error("Fehler beim Setzen der Referenzkurve:", error); console.error("Fehler beim Setzen der Referenzkurve", err);
alert("Fehler beim Setzen der Referenzkurve."); alert("Fehler beim Setzen der Referenzkurve.");
} }
}; };
// 📌 TDR Messung starten
const handleStartTDR = async () => { const handleStartTDR = async () => {
if (selectedSlot === null) { if (selectedSlot === null) {
alert("⚠️ Bitte zuerst einen KÜ auswählen!"); alert("⚠️ Bitte zuerst einen KÜ auswählen!");
return; return;
} }
const cgiUrl = `${window.location.origin}/CPL?/${window.location.pathname}&KTT${selectedSlot}=1`; const cgiUrl = `${window.location.origin}/CPL?/${window.location.pathname}&KTT${selectedSlot}=1`;
try { try {
console.log("🚀 Starte TDR Messung für Slot:", selectedSlot);
console.log("📡 CGI URL:", cgiUrl);
const isDev = process.env.NEXT_PUBLIC_NODE_ENV === "development"; const isDev = process.env.NEXT_PUBLIC_NODE_ENV === "development";
if (isDev) { if (isDev) {
// Dev / Simulator: sofort starten & Progress anzeigen
await new Promise((r) => setTimeout(r, 150)); await new Promise((r) => setTimeout(r, 150));
console.log("✅ [DEV] TDR Mock-Start ok für Slot", selectedSlot);
startTdrProgress(); startTdrProgress();
return; return;
} }
const response = await fetch(cgiUrl); const response = await fetch(cgiUrl);
if (!response.ok) throw new Error(`CGI-Fehler: ${response.status}`); if (!response.ok) throw new Error(`CGI-Fehler: ${response.status}`);
console.log("✅ TDR Messung gestartet für Slot", selectedSlot);
startTdrProgress(); startTdrProgress();
} catch (err) { } catch (err) {
console.error("❌ Fehler beim Starten der TDR Messung:", err); console.error("❌ Fehler beim Starten der TDR Messung", err);
//alert("❌ Fehler beim Starten der TDR Messung.");
} }
}; };
// 📥 Beim Slot-Wechsel TDM-Liste + letzte ID laden // Load TDM list when slot changes
useEffect(() => { useEffect(() => {
if (selectedSlot !== null) { if (selectedSlot !== null) {
dispatch(fetchTDMDataBySlotThunk(selectedSlot)).then((action) => { dispatch(fetchTDMDataBySlotThunk(selectedSlot)).then((action) => {
// action can be a PayloadAction with payload or a rejected action
const payload = ( const payload = (
action as { action as {
payload?: { data?: { id: number; t: string; d: number }[] }; payload?: { data?: { id: number; t: string; d: number }[] };
} }
).payload; ).payload;
const slotData = payload?.data; const slotData = payload?.data ?? [];
if ((slotData ?? []).length > 0) { if (slotData.length > 0) {
const lastId = (slotData ?? [])[0].id; const lastId = slotData[0].id; // latest first
setSelectedId(lastId); setSelectedId(lastId);
dispatch(getTDRChartDataByIdThunk(lastId)); dispatch(getTDRChartDataByIdThunk(lastId));
} }
@@ -166,7 +154,6 @@ const TDRChartActionBar: React.FC = () => {
return ( return (
<> <>
<div className="toolbar w-full flex items-center gap-3 flex-wrap"> <div className="toolbar w-full flex items-center gap-3 flex-wrap">
{/* Slot Badge */}
<div className="flex items-center gap-2 pr-4"> <div className="flex items-center gap-2 pr-4">
<span className="font-semibold uppercase tracking-wide text-muted"> <span className="font-semibold uppercase tracking-wide text-muted">
@@ -175,98 +162,117 @@ const TDRChartActionBar: React.FC = () => {
{selectedSlot !== null ? selectedSlot + 1 : "-"} {selectedSlot !== null ? selectedSlot + 1 : "-"}
</span> </span>
</div> </div>
{/* Referenz speichern */} {/* Date range always visible */}
{selectedId !== null && ( <DateRangePicker />
{isMeldungen ? (
<button <button
onClick={handleSetReference}
type="button" type="button"
className="btn-primary h-8 px-3 font-medium" onClick={handleFetchMessages}
className="btn-primary h-8 font-medium px-4"
disabled={selectedSlot === null}
> >
TDR-Kurve als Referenz speichern Anzeigen
</button> </button>
)} ) : (
{/* Start TDR */} <>
<button {selectedId !== null && (
onClick={handleStartTDR} <button
type="button" onClick={handleSetReference}
disabled={selectedSlot === null || tdrRunning} type="button"
className={`btn-primary h-8 px-4 whitespace-nowrap ${ className="btn-primary h-8 px-3 font-medium"
tdrRunning ? "opacity-90" : "" disabled={selectedSlot === null}
}`} >
> TDR-Kurve als Referenz speichern
{tdrRunning </button>
? `TDR läuft... (${Math.min( )}
100, <button
Math.round((tdrProgress / TDR_TOTAL_DURATION) * 100) onClick={handleStartTDR}
)}%)` type="button"
: "TDR-Messung starten"} disabled={selectedSlot === null || tdrRunning}
</button> className={`btn-primary h-8 px-4 whitespace-nowrap ${
{/* Messung Dropdown */} tdrRunning ? "opacity-90" : ""
<div className="ml-auto flex-1 min-w-[14rem] max-w-[30rem]"> }`}
<Listbox >
value={selectedId} {tdrRunning
onChange={(id) => { ? `TDR läuft... (${Math.min(
setSelectedId(id); 100,
if (id !== null) dispatch(getTDRChartDataByIdThunk(id)); Math.round((tdrProgress / TDR_TOTAL_DURATION) * 100)
}} )}%)`
disabled={idsForSlot.length === 0} : "TDR-Messung starten"}
> </button>
<div className="relative w-full"> <div className="ml-auto flex-1 min-w-[14rem] max-w-[30rem]">
<Listbox.Button className="dropdown-surface w-full flex items-center justify-between h-8 disabled:opacity-50 disabled:cursor-not-allowed"> <Listbox
<span className="dropdown-text-fix whitespace-nowrap overflow-hidden text-ellipsis pr-2"> value={selectedId}
{selectedId onChange={(id) => {
? (() => { setSelectedId(id);
const selected = idsForSlot.find( if (id !== null) dispatch(getTDRChartDataByIdThunk(id));
(e) => e.id === selectedId }}
); disabled={idsForSlot.length === 0}
return selected >
? `${new Date(selected.t).toLocaleString("de-DE", { <div className="relative w-full">
day: "2-digit", <Listbox.Button className="dropdown-surface w-full flex items-center justify-between h-8 disabled:opacity-50 disabled:cursor-not-allowed">
month: "2-digit", <span className="dropdown-text-fix whitespace-nowrap overflow-hidden text-ellipsis pr-2">
year: "numeric", {selectedId
hour: "2-digit", ? (() => {
minute: "2-digit", const selected = idsForSlot.find(
second: "2-digit", (e) => e.id === selectedId
})} Fehlerstelle: ${selected.d} m` );
: "Wähle Messung"; return selected
})() ? `${new Date(selected.t).toLocaleString(
: "Wähle Messung"} "de-DE",
</span> {
<i className="bi bi-chevron-down text-sm opacity-70" /> day: "2-digit",
</Listbox.Button> month: "2-digit",
<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"> year: "numeric",
{idsForSlot.map((entry) => { hour: "2-digit",
const dateLabel = new Date(entry.t).toLocaleString("de-DE", { minute: "2-digit",
day: "2-digit", second: "2-digit",
month: "2-digit", }
year: "numeric", )} Fehlerstelle: ${selected.d} m`
hour: "2-digit", : "Wähle Messung";
minute: "2-digit", })()
second: "2-digit", : "Wähle Messung"}
}); </span>
const fullText = `${dateLabel} Fehlerstelle: ${entry.d} m`; <i className="bi bi-chevron-down text-sm opacity-70" />
return ( </Listbox.Button>
<Listbox.Option <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">
key={entry.id} {idsForSlot.map((entry) => {
value={entry.id} const dateLabel = new Date(entry.t).toLocaleString(
title={fullText} "de-DE",
className={({ selected, active }) => { {
const base = day: "2-digit",
"px-3 h-8 cursor-pointer rounded-sm m-0.5 flex items-center justify-start transition-colors text-[13px]"; month: "2-digit",
if (selected) year: "numeric",
return `${base} dropdown-option-active font-medium`; hour: "2-digit",
if (active) return `${base} dropdown-option-hover`; minute: "2-digit",
return `${base}`; // neutral text color comes from parent/theme second: "2-digit",
}} }
> );
<span className="truncate w-full">{fullText}</span> const fullText = `${dateLabel} Fehlerstelle: ${entry.d} m`;
</Listbox.Option> return (
); <Listbox.Option
})} key={entry.id}
</Listbox.Options> 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>
</Listbox> </>
</div> )}
</div> </div>
{tdrRunning && ( {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="fixed inset-0 z-[1000] flex flex-col items-center justify-center bg-[rgba(0,0,0,0.55)] backdrop-blur-sm">

4
package-lock.json generated
View File

@@ -1,12 +1,12 @@
{ {
"name": "cpl-v4", "name": "cpl-v4",
"version": "1.6.884", "version": "1.6.885",
"lockfileVersion": 3, "lockfileVersion": 3,
"requires": true, "requires": true,
"packages": { "packages": {
"": { "": {
"name": "cpl-v4", "name": "cpl-v4",
"version": "1.6.884", "version": "1.6.885",
"dependencies": { "dependencies": {
"@emotion/react": "^11.13.0", "@emotion/react": "^11.13.0",
"@emotion/styled": "^11.13.0", "@emotion/styled": "^11.13.0",

View File

@@ -1,6 +1,6 @@
{ {
"name": "cpl-v4", "name": "cpl-v4",
"version": "1.6.884", "version": "1.6.885",
"private": true, "private": true,
"scripts": { "scripts": {
"dev": "next dev -p 3000", "dev": "next dev -p 3000",

View File

@@ -78,9 +78,12 @@ test.describe("ISO Modal", () => {
dialog.getByRole("cell", { name: header }) dialog.getByRole("cell", { name: header })
); );
} }
// In Meldungen view only Von/Bis + Anzeigen (no mode dropdown)
await expect(dialog.getByText("Von")).not.toBeVisible(); await expect(dialog.getByText("Von")).toBeVisible();
await expect(dialog.getByText("Bis")).not.toBeVisible(); await expect(dialog.getByText("Bis")).toBeVisible();
await expect(
dialog.getByRole("button", { name: "Anzeigen" })
).toBeVisible();
await viewToggle.click(); await viewToggle.click();
await page.getByRole("option", { name: "Messkurve" }).click(); await page.getByRole("option", { name: "Messkurve" }).click();