feat: TDR Meldungen DateRangePicker

This commit is contained in:
ISA
2025-09-10 08:13:37 +02:00
parent d6703c8870
commit 28612f9cd0
8 changed files with 146 additions and 135 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.886 NEXT_PUBLIC_APP_VERSION=1.6.887
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.886 NEXT_PUBLIC_APP_VERSION=1.6.887
NEXT_PUBLIC_CPL_MODE=production NEXT_PUBLIC_CPL_MODE=production

View File

@@ -1,3 +1,8 @@
## [1.6.887] 2025-09-10
- style: actionbar in RSL und ISO
---
## [1.6.886] 2025-09-10 ## [1.6.886] 2025-09-10
- WIP: von bis Zeitraum in ISO und TDR, aber TDR ist WIP - WIP: von bis Zeitraum in ISO und TDR, aber TDR ist WIP

View File

@@ -7,7 +7,6 @@ import { AppDispatch } from "../../../../../../redux/store";
import { Chart, registerables } from "chart.js"; import { Chart, registerables } from "chart.js";
import "chartjs-adapter-date-fns"; import "chartjs-adapter-date-fns";
import { getColor } from "../../../../../../utils/colors"; import { getColor } from "../../../../../../utils/colors";
import TDRChartActionBar from "./TDRChartActionBar";
import { getReferenceCurveBySlotThunk } from "../../../../../../redux/thunks/getReferenceCurveBySlotThunk"; import { getReferenceCurveBySlotThunk } from "../../../../../../redux/thunks/getReferenceCurveBySlotThunk";
const TDRChart: React.FC<{ isFullScreen: boolean }> = ({ isFullScreen }) => { const TDRChart: React.FC<{ isFullScreen: boolean }> = ({ isFullScreen }) => {
@@ -212,8 +211,7 @@ const TDRChart: React.FC<{ isFullScreen: boolean }> = ({ isFullScreen }) => {
//-------------------------------------- //--------------------------------------
return ( return (
<div style={{ width: "100%", height: isFullScreen ? "90%" : "28rem" }}> <div style={{ width: "100%", height: isFullScreen ? "90%" : "28rem" }}>
<TDRChartActionBar />
{tdrChartData.length === 0 ? ( {tdrChartData.length === 0 ? (
<div className="flex items-center justify-center h-full text-gray-500 italic"> <div className="flex items-center justify-center h-full text-gray-500 italic">

View File

@@ -153,7 +153,8 @@ const TDRChartActionBar: React.FC = () => {
return ( return (
<> <>
<div className="toolbar w-full flex items-center gap-3 flex-wrap"> <div className="toolbar w-full justify-between flex-wrap">
{/* KÜ number left, controls right, like IsoChartActionBar */}
<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">
@@ -162,141 +163,145 @@ const TDRChartActionBar: React.FC = () => {
{selectedSlot !== null ? selectedSlot + 1 : "-"} {selectedSlot !== null ? selectedSlot + 1 : "-"}
</span> </span>
</div> </div>
{/* Date range only in Meldungen (requirement) */} <div className="flex items-center gap-3 flex-1 justify-end">
{isMeldungen && <DateRangePicker />} {isMeldungen ? (
{isMeldungen ? ( <>
<button <DateRangePicker />
type="button"
onClick={handleFetchMessages}
className="btn-primary h-8 font-medium px-4"
disabled={selectedSlot === null}
>
Anzeigen
</button>
) : (
<>
{selectedId !== null && (
<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} disabled={selectedSlot === null}
> >
TDR-Kurve als Referenz speichern Anzeigen
</button> </button>
)} </>
<button ) : (
onClick={handleStartTDR} <>
type="button" {selectedId !== null && (
disabled={selectedSlot === null || tdrRunning} <button
className={`btn-primary h-8 px-4 whitespace-nowrap ${ onClick={handleSetReference}
tdrRunning ? "opacity-90" : "" type="button"
}`} className="btn-primary h-8 px-3 font-medium"
> disabled={selectedSlot === null}
{tdrRunning >
? `TDR läuft... (${Math.min( TDR-Kurve als Referenz speichern
100, </button>
Math.round((tdrProgress / TDR_TOTAL_DURATION) * 100) )}
)}%)` <button
: "TDR-Messung starten"} onClick={handleStartTDR}
</button> type="button"
<div className="ml-auto flex-1 min-w-[14rem] max-w-[30rem]"> disabled={selectedSlot === null || tdrRunning}
<Listbox className={`btn-primary h-8 px-4 whitespace-nowrap ${
value={selectedId} tdrRunning ? "opacity-90" : ""
onChange={(id) => { }`}
setSelectedId(id);
if (id !== null) dispatch(getTDRChartDataByIdThunk(id));
}}
disabled={idsForSlot.length === 0}
> >
<div className="relative w-full"> {tdrRunning
<Listbox.Button className="dropdown-surface w-full flex items-center justify-between h-8 disabled:opacity-50 disabled:cursor-not-allowed"> ? `TDR läuft... (${Math.min(
<span className="dropdown-text-fix whitespace-nowrap overflow-hidden text-ellipsis pr-2"> 100,
{selectedId Math.round((tdrProgress / TDR_TOTAL_DURATION) * 100)
? (() => { )}%)`
const selected = idsForSlot.find( : "TDR-Messung starten"}
(e) => e.id === selectedId </button>
); <div className="ml-auto flex-1 min-w-[14rem] max-w-[30rem]">
return selected <Listbox
? `${new Date(selected.t).toLocaleString( value={selectedId}
"de-DE", onChange={(id) => {
{ setSelectedId(id);
day: "2-digit", if (id !== null) dispatch(getTDRChartDataByIdThunk(id));
month: "2-digit", }}
year: "numeric", disabled={idsForSlot.length === 0}
hour: "2-digit", >
minute: "2-digit", <div className="relative w-full">
second: "2-digit", <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">
)} Fehlerstelle: ${selected.d} m` {selectedId
: "Wähle Messung"; ? (() => {
})() const selected = idsForSlot.find(
: "Wähle Messung"} (e) => e.id === selectedId
</span> );
<i className="bi bi-chevron-down text-sm opacity-70" /> return selected
</Listbox.Button> ? `${new Date(selected.t).toLocaleString(
<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"> "de-DE",
{idsForSlot.map((entry) => { {
const dateLabel = new Date(entry.t).toLocaleString( day: "2-digit",
"de-DE", month: "2-digit",
{ year: "numeric",
day: "2-digit", hour: "2-digit",
month: "2-digit", minute: "2-digit",
year: "numeric", second: "2-digit",
hour: "2-digit", }
minute: "2-digit", )} Fehlerstelle: ${selected.d} m`
second: "2-digit", : "Wähle Messung";
} })()
); : "Wähle Messung"}
const fullText = `${dateLabel} Fehlerstelle: ${entry.d} m`; </span>
return ( <i className="bi bi-chevron-down text-sm opacity-70" />
<Listbox.Option </Listbox.Button>
key={entry.id} <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">
value={entry.id} {idsForSlot.map((entry) => {
title={fullText} const dateLabel = new Date(entry.t).toLocaleString(
className={({ selected, active }) => { "de-DE",
const base = {
"px-3 h-8 cursor-pointer rounded-sm m-0.5 flex items-center justify-start transition-colors text-[13px]"; day: "2-digit",
if (selected) month: "2-digit",
return `${base} dropdown-option-active font-medium`; year: "numeric",
if (active) return `${base} dropdown-option-hover`; hour: "2-digit",
return `${base}`; minute: "2-digit",
}} second: "2-digit",
> }
<span className="truncate w-full">{fullText}</span> );
</Listbox.Option> const fullText = `${dateLabel} Fehlerstelle: ${entry.d} m`;
); return (
})} <Listbox.Option
</Listbox.Options> key={entry.id}
</div> value={entry.id}
</Listbox> 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>
{/* Progress Overlay */}
{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>
</> <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>
)} )}
</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>
)}
</> </>
); );
}; };

View File

@@ -3,6 +3,7 @@
import React, { useEffect } from "react"; import React, { useEffect } from "react";
import ReactModal from "react-modal"; import ReactModal from "react-modal";
import TDRChart from "./TDRChart"; import TDRChart from "./TDRChart";
import TDRChartActionBar from "./TDRChartActionBar";
import { useSelector, useDispatch } from "react-redux"; import { useSelector, useDispatch } from "react-redux";
import { AppDispatch, RootState } from "@/redux/store"; import { AppDispatch, RootState } from "@/redux/store";
import { import {
@@ -166,6 +167,8 @@ const TDRChartView: React.FC<TDRChartViewProps> = ({
</div> </div>
</header> </header>
<div className="flex flex-col flex-1 p-3 gap-3"> <div className="flex flex-col flex-1 p-3 gap-3">
{/* Action Bar (wie bei ISO / Loop) */}
<TDRChartActionBar />
<div className="flex-1 relative"> <div className="flex-1 relative">
{chartTitle === "Messkurve" ? ( {chartTitle === "Messkurve" ? (
<TDRChart isFullScreen={isFullScreen} /> <TDRChart isFullScreen={isFullScreen} />

4
package-lock.json generated
View File

@@ -1,12 +1,12 @@
{ {
"name": "cpl-v4", "name": "cpl-v4",
"version": "1.6.886", "version": "1.6.887",
"lockfileVersion": 3, "lockfileVersion": 3,
"requires": true, "requires": true,
"packages": { "packages": {
"": { "": {
"name": "cpl-v4", "name": "cpl-v4",
"version": "1.6.886", "version": "1.6.887",
"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.886", "version": "1.6.887",
"private": true, "private": true,
"scripts": { "scripts": {
"dev": "next dev -p 3000", "dev": "next dev -p 3000",