feat: MUI test

This commit is contained in:
ISA
2025-09-09 08:33:35 +02:00
parent 001b237dd7
commit 8580032ff9
13 changed files with 1000 additions and 348 deletions

View File

@@ -41,7 +41,6 @@ export const useIsoChartLoader = () => {
)};${formatDate(bisDatum)};${slotNumber};${type};`;
}
console.log("API URL:", url);
return url;
};
@@ -143,19 +142,10 @@ export const useIsoDataLoader = () => {
const waitTime = Math.max(0, MIN_LOADING_TIME_MS - elapsedTime);
await new Promise((resolve) => setTimeout(resolve, waitTime));
console.log("▶️ Automatisches Laden - Isolationswiderstand-Daten für:");
console.log(" Slot:", slotNumber);
console.log(" Modus:", selectedMode);
console.log(" Von:", vonDatum);
console.log(" Bis:", bisDatum);
if (Array.isArray(jsonData) && jsonData.length > 0) {
dispatch(setIsoMeasurementCurveChartData(jsonData));
dispatch(setChartOpen(true));
} else {
console.log(
"⚠️ Keine Messdaten im gewählten Zeitraum gefunden (automatisches Laden)"
);
dispatch(setIsoMeasurementCurveChartData([]));
dispatch(setChartOpen(false));
}
@@ -170,8 +160,6 @@ export const useIsoDataLoader = () => {
};
//-----------------------------------------------------------------------------------IsoChartActionBar
// ...existing code...
const IsoChartActionBar = forwardRef((_props, ref) => {
IsoChartActionBar.displayName = "IsoChartActionBar";
const dispatch = useAppDispatch();
@@ -281,33 +269,36 @@ const IsoChartActionBar = forwardRef((_props, ref) => {
}
};
useImperativeHandle(ref, () => ({
handleFetchData,
}));
useImperativeHandle(ref, () => ({ handleFetchData }));
const hiddenWhenMeldungen = chartTitle !== "Messkurve";
return (
<div className="flex justify-between items-center p-2 bg-gray-100 rounded-lg space-x-2">
<div className="flex items-center">
<label className="text-sm font-semibold">
{slotNumber !== null ? slotNumber + 1 : "-"}
</label>
<div className="toolbar w-full justify-between flex-wrap">
<div className="flex items-center gap-2 pr-4">
<span className=" font-semibold uppercase tracking-wide text-muted">
</span>
<span className=" font-medium px-2 py-0.5 rounded bg-surface-alt border border-base min-w-[3rem] text-center">
{slotNumber !== null ? slotNumber + 1 : "-"}
</span>
</div>
<div className="flex items-center space-x-2">
{/* DateRangePicker für beide Ansichten sichtbar, da Meldungen auch datumsabhängig sind */}
<div className="flex items-center gap-2 flex-1 justify-end">
<div
style={{
visibility: chartTitle === "Messkurve" ? "visible" : "hidden",
}}
className={
hiddenWhenMeldungen
? "opacity-0 pointer-events-none"
: "opacity-100"
}
>
<DateRangePicker />
</div>
{/* DIA0-DIA2 Dropdown - Platz reservieren, aber ausblenden wenn Meldungen */}
<div
style={{
visibility: chartTitle === "Messkurve" ? "visible" : "hidden",
}}
className={`transition-opacity ${
hiddenWhenMeldungen
? "opacity-0 pointer-events-none"
: "opacity-100"
}`}
>
<Listbox
value={selectedMode}
@@ -317,39 +308,29 @@ const IsoChartActionBar = forwardRef((_props, ref) => {
}}
>
<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>
<Listbox.Button className="dropdown-surface w-full flex items-center justify-between">
<span className="dropdown-text-fix">
{
{
DIA0: "Alle Messwerte",
DIA1: "Stündliche Werte",
DIA2: "Tägliche Werte",
DIA1: "Stündlich",
DIA2: "Täglich",
}[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>
<i className="bi bi-chevron-down opacity-70" />
</Listbox.Button>
<Listbox.Options className="absolute z-50 mt-1 w-full border rounded bg-white shadow max-h-60 overflow-auto text-sm">
<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-4 py-1 cursor-pointer ${
`px-3 py-1.5 cursor-pointer rounded-sm m-0.5 ${
selected
? "bg-littwin-blue text-white"
? "dropdown-option-active"
: active
? "bg-gray-200"
? "dropdown-option-hover"
: ""
}`
}
@@ -357,9 +338,9 @@ const IsoChartActionBar = forwardRef((_props, ref) => {
{
{
DIA0: "Alle Messwerte",
DIA1: "Stündliche Werte",
DIA2: "Tägliche Werte",
}[mode]
DIA1: "Stündlich",
DIA2: "Täglich",
}[mode as "DIA0" | "DIA1" | "DIA2"]
}
</Listbox.Option>
))}
@@ -367,17 +348,12 @@ const IsoChartActionBar = forwardRef((_props, ref) => {
</div>
</Listbox>
</div>
{/* Dropdown für Auswahl zwischen "Messkurve" und "Meldungen" - immer anzeigen */}
{/* Dropdown für Auswahl zwischen "Messkurve" und "Meldungen" entfernt */}
{/* Daten laden Button lädt je nach Ansicht Messkurve oder Meldungen */}
<button
style={{
visibility: chartTitle === "Messkurve" ? "visible" : "hidden",
}}
onClick={handleFetchData}
className="px-4 py-1 bg-littwin-blue text-white rounded text-sm"
className={`btn-primary h-8 font-medium px-3 ${
hiddenWhenMeldungen ? "invisible" : "visible"
}`}
type="button"
>
Daten laden
</button>

View File

@@ -1,4 +1,4 @@
"use client"; // IsoChartView.tsx
"use client";
import React, { useEffect, useRef } from "react";
import { Listbox } from "@headlessui/react";
@@ -7,23 +7,18 @@ import IsoMeasurementChart from "./IsoMeasurementChart";
import IsoChartActionBar from "./IsoChartActionBar";
import Report from "./Report";
import { useSelector, useDispatch } from "react-redux";
import { AppDispatch } from "@/redux/store";
import { RootState } from "@/redux/store";
import { AppDispatch, RootState } from "@/redux/store";
import {
setChartOpen,
setFullScreen,
setSlotNumber,
setChartTitle,
} from "@/redux/slices/kabelueberwachungChartSlice";
import { resetBrushRange } from "@/redux/slices/brushSlice";
import {
setVonDatum,
setBisDatum,
setSelectedMode,
setSelectedSlotType,
} from "@/redux/slices/kabelueberwachungChartSlice";
import { resetBrushRange } from "@/redux/slices/brushSlice";
import { resetDateRange } from "@/redux/slices/dateRangePickerSlice";
interface IsoChartViewProps {
@@ -32,85 +27,59 @@ interface IsoChartViewProps {
slotIndex: number;
}
type ActionBarRefType = { handleFetchData: () => void };
const IsoChartView: React.FC<IsoChartViewProps> = ({
isOpen,
onClose,
slotIndex,
}) => {
const dispatch = useDispatch<AppDispatch>();
// removed unused loadData
const { isFullScreen, chartTitle } = useSelector(
(state: RootState) => state.kabelueberwachungChartSlice
);
// **Modal schließen + Redux-Status zurücksetzen**
const handleClose = () => {
const actionBarRef = useRef<ActionBarRefType>(null);
const initDates = () => {
const today = new Date();
const thirtyDaysAgo = new Date();
thirtyDaysAgo.setDate(today.getDate() - 30);
const toISO = (date: Date) => date.toLocaleDateString("sv-SE");
// Reset Datum
const toISO = (d: Date) => d.toLocaleDateString("sv-SE");
dispatch(setVonDatum(toISO(thirtyDaysAgo)));
dispatch(setBisDatum(toISO(today)));
};
// Reset DateRangePicker
const handleClose = () => {
initDates();
dispatch(resetDateRange());
// Reset Dropdowns
dispatch(setSelectedMode("DIA0")); // Reset to Alle Messwerte
dispatch(setSelectedMode("DIA0"));
dispatch(setSelectedSlotType("isolationswiderstand"));
dispatch(setChartTitle("Messkurve")); // Reset zu Messkurve
// Sonstiges Reset
dispatch(setChartTitle("Messkurve"));
dispatch(setChartOpen(false));
dispatch(setFullScreen(false));
dispatch(resetBrushRange());
onClose();
};
// **Vollbildmodus umschalten**
const toggleFullScreen = () => {
dispatch(setFullScreen(!isFullScreen));
};
const toggleFullScreen = () => dispatch(setFullScreen(!isFullScreen));
// Modal öffnen - ISO spezifische Einstellungen
type ActionBarRefType = { handleFetchData: () => void };
const actionBarRef = useRef<ActionBarRefType>(null);
useEffect(() => {
if (isOpen) {
dispatch(setSlotNumber(slotIndex));
// inline initDates to avoid extra dependency
const today = new Date();
const thirtyDaysAgo = new Date();
thirtyDaysAgo.setDate(today.getDate() - 30);
const toISO = (date: Date) => date.toLocaleDateString("sv-SE");
// Set slot number first
dispatch(setSlotNumber(slotIndex));
// Set dates
const toISO = (d: Date) => d.toLocaleDateString("sv-SE");
dispatch(setVonDatum(toISO(thirtyDaysAgo)));
dispatch(setBisDatum(toISO(today)));
// Set ISO specific settings
dispatch(setSelectedSlotType("isolationswiderstand"));
dispatch(setSelectedMode("DIA0")); // Set to Alle Messwerte on open
// Set default to Messkurve
dispatch(setSelectedMode("DIA0"));
dispatch(setChartTitle("Messkurve"));
// Automatisch Daten laden wie Button-Klick
const timer = setTimeout(() => {
actionBarRef.current?.handleFetchData();
}, 120);
// Cleanup timer
return () => clearTimeout(timer);
const t = setTimeout(() => actionBarRef.current?.handleFetchData(), 120);
return () => clearTimeout(t);
}
// eslint-disable-next-line react-hooks/exhaustive-deps
}, [isOpen, slotIndex, dispatch]);
return (
@@ -119,74 +88,55 @@ const IsoChartView: React.FC<IsoChartViewProps> = ({
onRequestClose={handleClose}
ariaHideApp={false}
style={{
overlay: { backgroundColor: "rgba(0, 0, 0, 0.5)" },
overlay: {
backgroundColor: "rgba(0,0,0,0.55)",
backdropFilter: "blur(2px)",
},
content: {
top: "50%",
left: "50%",
bottom: "auto",
marginRight: "-50%",
inset: "50% auto auto 50%",
transform: "translate(-50%, -50%)",
width: isFullScreen ? "90vw" : "70rem",
height: isFullScreen ? "90vh" : "35rem",
padding: "1rem",
transition: "all 0.3s ease-in-out",
width: isFullScreen ? "90vw" : "72rem",
height: isFullScreen ? "90vh" : "38rem",
padding: 0,
border: "1px solid var(--color-border)",
background: "var(--color-surface)",
borderRadius: "14px",
display: "flex",
flexDirection: "column",
overflow: "hidden",
},
}}
contentLabel="Isolationswiderstand"
>
{/* Action-Buttons */}
<div
style={{
position: "absolute",
top: "0.625rem",
right: "0.625rem",
display: "flex",
gap: "0.75rem",
}}
>
{/* Fullscreen-Button */}
<button
onClick={toggleFullScreen}
style={{
background: "transparent",
border: "none",
fontSize: "1.5rem",
cursor: "pointer",
}}
>
<i
className={
isFullScreen ? "bi bi-fullscreen-exit" : "bi bi-arrows-fullscreen"
}
></i>
</button>
{/* Schließen-Button */}
<button
onClick={handleClose}
style={{
background: "transparent",
border: "none",
fontSize: "1.5rem",
cursor: "pointer",
}}
>
<i className="bi bi-x-circle-fill"></i>
</button>
</div>
{/* Chart-Container */}
<div
style={{
flex: 1,
display: "flex",
flexDirection: "column",
height: "100%",
}}
>
<div className="flex justify-between items-center mb-2 pr-24">
<h3 className="text-lg font-semibold">Isolationswiderstand</h3>
<header className="modal-header relative pr-56">
<h3 className="text-sm font-semibold tracking-wide">
Isolationswiderstand
</h3>
<div className="absolute top-2 right-2 flex gap-2">
<button
onClick={toggleFullScreen}
className="icon-btn"
aria-label={isFullScreen ? "Vollbild verlassen" : "Vollbild"}
type="button"
>
<i
className={
isFullScreen
? "bi bi-fullscreen-exit"
: "bi bi-arrows-fullscreen"
}
/>
</button>
<button
onClick={handleClose}
className="icon-btn"
aria-label="Schließen"
type="button"
>
<i className="bi bi-x-lg" />
</button>
</div>
<div className="absolute top-2 right-28">
<Listbox
value={chartTitle}
onChange={(value: "Messkurve" | "Meldungen") =>
@@ -194,52 +144,36 @@ const IsoChartView: React.FC<IsoChartViewProps> = ({
}
>
<div className="relative w-40">
<Listbox.Button className="w-full border px-3 py-1 rounded text-left bg-white flex justify-between items-center text-sm">
<span>
{chartTitle === "Meldungen" ? "Meldungen" : "Messkurve"}
</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 className="dropdown-surface w-full flex items-center justify-between h-8">
<span className="dropdown-text-fix">{chartTitle}</span>
<i className="bi bi-chevron-down text-sm opacity-70" />
</Listbox.Button>
<Listbox.Options className="absolute z-50 mt-1 w-full border rounded bg-white shadow max-h-60 overflow-auto text-sm">
<Listbox.Options className="dropdown-options absolute z-50 mt-1 w-full max-h-60 overflow-auto text-sm">
{(["Messkurve", "Meldungen"] as const).map((option) => (
<Listbox.Option
key={option}
value={option}
className={({
selected,
active,
}: {
selected: boolean;
active: boolean;
}) =>
`px-4 py-1 cursor-pointer ${
className={({ selected, active }) =>
`px-3 py-1.5 cursor-pointer rounded-sm m-0.5 ${
selected
? "bg-littwin-blue text-white"
? "dropdown-option-active"
: active
? "bg-gray-200"
? "dropdown-option-hover"
: ""
}`
}
>
{option === "Meldungen" ? "Meldungen" : "Messkurve"}
{option}
</Listbox.Option>
))}
</Listbox.Options>
</div>
</Listbox>
</div>
</header>
<div className="flex flex-col flex-1 p-3 gap-3">
<IsoChartActionBar ref={actionBarRef} />
<div style={{ flex: 1, height: "90%" }}>
<div className="flex-1 relative">
{chartTitle === "Messkurve" ? (
<IsoMeasurementChart />
) : (

View File

@@ -224,43 +224,47 @@ const Report: React.FC<ReportProps> = ({ moduleType, autoLoad = true }) => {
gewählten Zeitraum gefunden.
</div>
) : (
<div className="flex-1 overflow-auto ">
<table className="min-w-full border text-sm">
<thead className="bg-gray-100 text-left sticky top-0 z-10">
<tr>
<th className="p-2 border">Prio</th>
<th className="p-2 border">Zeitstempel</th>
<th className="p-2 border">Quelle</th>
<th className="p-2 border">Meldung</th>
<th className="p-2 border">Status</th>
</tr>
</thead>
<tbody>
{filteredMessages.map((msg, index) => (
<tr key={index} className="hover:bg-gray-200">
<td className="border p-2">
<div
className="w-4 h-4 rounded"
style={{ backgroundColor: msg.c }}
></div>
</td>
<td className="border p-2">
{new Date(msg.t).toLocaleString("de-DE", {
day: "2-digit",
month: "2-digit",
year: "numeric",
hour: "2-digit",
minute: "2-digit",
second: "2-digit",
})}
</td>
<td className="border p-2">{msg.i}</td>
<td className="border p-2">{msg.m}</td>
<td className="border p-2">{msg.v}</td>
<div className="flex-1 overflow-auto table-scroll-region">
<div className="data-table-wrapper">
<table className="data-table">
<thead>
<tr>
<th style={{ width: "60px" }}>Prio</th>
<th style={{ minWidth: "180px" }}>Zeitstempel</th>
<th style={{ minWidth: "140px" }}>Quelle</th>
<th style={{ minWidth: "260px" }}>Meldung</th>
<th style={{ minWidth: "120px" }}>Status</th>
</tr>
))}
</tbody>
</table>
</thead>
<tbody>
{filteredMessages.map((msg, index) => (
<tr key={index}>
<td>
<div
className="prio-dot"
style={{ backgroundColor: msg.c }}
/>
</td>
<td>
{new Date(msg.t).toLocaleString("de-DE", {
day: "2-digit",
month: "2-digit",
year: "numeric",
hour: "2-digit",
minute: "2-digit",
second: "2-digit",
})}
</td>
<td>{msg.i}</td>
<td className="truncate max-w-[22ch]" title={msg.m}>
{msg.m}
</td>
<td>{msg.v}</td>
</tr>
))}
</tbody>
</table>
</div>
</div>
)}

View File

@@ -41,68 +41,71 @@ export default function KueModal({ showModal, onClose, slot }: KueModalProps) {
window.kabelModalOpen = showModal;
}
}, [showModal]);
//-----------------------------------------------------
//------------------------------------------------------
return (
<ReactModal
isOpen={showModal}
onRequestClose={onClose}
shouldCloseOnOverlayClick
ariaHideApp={false}
style={{
overlay: {
backgroundColor: "rgba(0, 0, 0, 0.5)",
backgroundColor: "rgba(0,0,0,0.55)",
zIndex: 100,
backdropFilter: "blur(2px)",
},
content: {
top: "50%",
left: "50%",
inset: "50% auto auto 50%",
transform: "translate(-50%, -50%)",
width: "90%",
maxWidth: "850px",
padding: "0px",
border: "none",
borderRadius: "8px",
position: "relative",
bottom: "auto",
right: "auto",
width: "min(900px,92vw)",
maxHeight: "80vh",
padding: 0,
border: "1px solid var(--color-border)",
background: "var(--color-surface)",
borderRadius: "12px",
overflow: "hidden",
display: "flex",
flexDirection: "column",
},
}}
contentLabel={`Einstellungen KÜ ${slot + 1}`}
>
<div className="p-2 flex justify-between items-center rounded-t-md bg-surface-alt border-b border-base">
<h2 className="text-base font-bold text-fg">
<div className="modal-header">
<h2 className="text-sm font-semibold tracking-wide text-fg">
Einstellungen {slot + 1}
</h2>
<button
onClick={onClose}
className="text-2xl text-fg-muted hover:text-fg transition-colors focus:outline-none focus:ring-2 focus:ring-accent/50 rounded"
className="icon-btn"
aria-label="Modal schließen"
type="button"
>
<i className="bi bi-x-circle-fill"></i>
<i className="bi bi-x-lg text-base" />
</button>
</div>
<div className="flex justify-start bg-surface-alt space-x-2 p-2 border-b border-base">
<div className="flex justify-start bg-surface-alt px-3 pt-2 gap-2 border-b border-base">
{[
{ label: "Allgemein", key: "kue" as const },
{ label: "TDR ", key: "tdr" as const },
{ label: "TDR", key: "tdr" as const },
{ label: "KVz", key: "kvz" as const },
{ label: "Knotenpunkte", key: "knoten" as const },
].map(({ label, key }) => (
<button
key={key}
onClick={() => setActiveTab(key)}
className={`px-4 py-1 rounded-t font-semibold text-sm transition-colors focus:outline-none focus:ring-2 focus:ring-accent/40 ${
activeTab === key
? "bg-surface text-accent shadow-sm"
: "text-fg-muted hover:text-fg"
}`}
>
{label}
</button>
))}
].map(({ label, key }) => {
const isActive = activeTab === key;
return (
<button
key={key}
type="button"
onClick={() => setActiveTab(key)}
className={`tab-btn ${isActive ? "tab-btn-active" : ""}`}
aria-current={isActive ? "page" : undefined}
>
{label}
</button>
);
})}
</div>
<div className="p-4 bg-surface rounded-b-md h-[20rem] laptop:h-[24rem] 2xl:h-[30rem] overflow-y-auto text-fg">
<div className="modal-body-scroll px-5 py-4 flex-1 h-[20rem] laptop:h-[24rem] 2xl:h-[30rem] text-fg">
{activeTab === "kue" && (
<KueEinstellung
slot={slot}