Compare commits
32 Commits
feat/scrol
...
develop
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
3edb8a053c | ||
|
|
aedc7ccae5 | ||
|
|
bdaf0ec263 | ||
|
|
538f9ca487 | ||
|
|
5ef7e648eb | ||
|
|
74880d9ccc | ||
|
|
7f035f0c18 | ||
|
|
7fe04f55fe | ||
|
|
2ceebea533 | ||
|
|
95c884bc07 | ||
|
|
05b416855b | ||
|
|
b4dd42c8a5 | ||
|
|
41910e450e | ||
|
|
f2a5f2083a | ||
|
|
92b712d7ce | ||
|
|
be9954ac29 | ||
|
|
f25063074d | ||
|
|
9192111b12 | ||
|
|
6f88a11771 | ||
|
|
4c45c3b9ca | ||
|
|
484902b788 | ||
|
|
3266e8b2d5 | ||
|
|
77f14313ae | ||
|
|
f43ddccc46 | ||
|
|
28612f9cd0 | ||
|
|
d6703c8870 | ||
|
|
18c9c886ec | ||
|
|
4c6fe0db03 | ||
|
|
6cb753c040 | ||
|
|
52551b3243 | ||
|
|
f7d1a36e0f | ||
|
|
8580032ff9 |
@@ -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.879
|
NEXT_PUBLIC_APP_VERSION=1.6.913
|
||||||
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)
|
||||||
|
|
||||||
|
|||||||
@@ -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.879
|
NEXT_PUBLIC_APP_VERSION=1.6.913
|
||||||
NEXT_PUBLIC_CPL_MODE=production
|
NEXT_PUBLIC_CPL_MODE=production
|
||||||
721
CHANGELOG.md
721
CHANGELOG.md
File diff suppressed because it is too large
Load Diff
@@ -168,11 +168,11 @@ Beispielaufruf im DEV-Modus (über UI gesteuert, nicht manuell notwendig):
|
|||||||
### 🔌 System
|
### 🔌 System
|
||||||
|
|
||||||
- Live-Anzeige von:
|
- Live-Anzeige von:
|
||||||
- +5V, +15V, -15V, -98V Spannungen
|
- +5V, +15V, -15V, -96V Spannungen
|
||||||
- CPU- und ADC-Temperaturen
|
- CPU- und ADC-Temperaturen
|
||||||
- Verlaufskurven über Zeit (Chart.js)
|
- Verlaufskurven über Zeit (Chart.js)
|
||||||
- Spannungen und Temperaturen werden jetzt in zwei separaten Charts nebeneinander dargestellt
|
- Spannungen und Temperaturen werden jetzt in zwei separaten Charts nebeneinander dargestellt
|
||||||
- Spannungswerte (+5V, +15V, -15V, -98V) werden mit zwei Nachkommastellen angezeigt
|
- Spannungswerte (+5V, +15V, -15V, -96V) werden mit zwei Nachkommastellen angezeigt
|
||||||
|
|
||||||
### ⚙️ Einstellungen
|
### ⚙️ Einstellungen
|
||||||
|
|
||||||
|
|||||||
@@ -77,6 +77,8 @@ const DateRangePicker: React.FC<DateRangePickerProps> = ({
|
|||||||
minDate={sixMonthsAgo}
|
minDate={sixMonthsAgo}
|
||||||
maxDate={today}
|
maxDate={today}
|
||||||
dateFormat="dd.MM.yyyy"
|
dateFormat="dd.MM.yyyy"
|
||||||
|
portalId="root-portal"
|
||||||
|
popperClassName="custom-datepicker-popper"
|
||||||
className={`border px-2 py-1 rounded ${inputWidthClass} ${
|
className={`border px-2 py-1 rounded ${inputWidthClass} ${
|
||||||
compact ? "text-xs" : "text-sm"
|
compact ? "text-xs" : "text-sm"
|
||||||
}`}
|
}`}
|
||||||
@@ -107,6 +109,8 @@ const DateRangePicker: React.FC<DateRangePickerProps> = ({
|
|||||||
minDate={sixMonthsAgo}
|
minDate={sixMonthsAgo}
|
||||||
maxDate={today}
|
maxDate={today}
|
||||||
dateFormat="dd.MM.yyyy"
|
dateFormat="dd.MM.yyyy"
|
||||||
|
portalId="root-portal"
|
||||||
|
popperClassName="custom-datepicker-popper"
|
||||||
className={`border px-2 py-1 rounded ${inputWidthClass} ${
|
className={`border px-2 py-1 rounded ${inputWidthClass} ${
|
||||||
compact ? "text-xs" : "text-sm"
|
compact ? "text-xs" : "text-sm"
|
||||||
}`}
|
}`}
|
||||||
|
|||||||
@@ -145,13 +145,13 @@ function Header() {
|
|||||||
}, []);
|
}, []);
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<header className="bg-[var(--color-surface)] dark:bg-[var(--color-surface)]/90 backdrop-blur flex justify-between items-center w-full h-[13vh] laptop:h-[10vh] relative text-[var(--color-fg)] dark:text-[var(--color-fg)] shadow-sm border-b border-[var(--color-border)]">
|
<header className="bg-[var(--color-surface-alt)] dark:bg-[var(--color-surface-alt)] flex justify-between items-center w-full h-[13vh] laptop:h-[10vh] relative text-[var(--color-fg)] border-b border-base theme-transition">
|
||||||
<div
|
<div
|
||||||
className="absolute transform -translate-y-1/2
|
className="absolute transform -translate-y-1/2
|
||||||
left-[8%] sm:left-[8%] md:left-[8%] lg:left-[8%] xl:left-[6%] 2xl:left-[2%] laptop:left-[4%] laptop:
|
left-[8%] sm:left-[8%] md:left-[8%] lg:left-[8%] xl:left-[6%] 2xl:left-[2%] laptop:left-[4%] laptop:
|
||||||
top-[90%] sm:top-[90%] md:top-[90%] lg:top-[90%] xl:top-[90%]"
|
top-[90%] sm:top-[90%] md:top-[90%] lg:top-[90%] xl:top-[90%]"
|
||||||
style={{
|
style={{
|
||||||
height: "10vh", // Dynamische Höhe des Containers
|
height: "12vh", // Erhöhte Höhe des Containers für größeres Logo
|
||||||
width: "auto",
|
width: "auto",
|
||||||
aspectRatio: "1", // Beibehaltung des Seitenverhältnisses
|
aspectRatio: "1", // Beibehaltung des Seitenverhältnisses
|
||||||
}}
|
}}
|
||||||
@@ -160,7 +160,7 @@ function Header() {
|
|||||||
src="/images/Logo.png"
|
src="/images/Logo.png"
|
||||||
alt="Logo"
|
alt="Logo"
|
||||||
fill
|
fill
|
||||||
sizes="(max-width: 640px) 7vh, (max-width: 1024px) 8vh, (max-width: 1280px) 9vh, 10vh"
|
sizes="(max-width: 640px) 12vh, (max-width: 1024px) 14vh, (max-width: 1280px) 16vh, 18vh"
|
||||||
className="object-contain"
|
className="object-contain"
|
||||||
priority={false}
|
priority={false}
|
||||||
/>
|
/>
|
||||||
|
|||||||
@@ -367,9 +367,9 @@ export default function AnalogInputsChart({
|
|||||||
<div
|
<div
|
||||||
className={`flex flex-col gap-2 h-full ${loading ? "cursor-wait" : ""}`}
|
className={`flex flex-col gap-2 h-full ${loading ? "cursor-wait" : ""}`}
|
||||||
>
|
>
|
||||||
<div className="flex justify-between items-center p-2 bg-gray-100 rounded-lg space-x-2">
|
<div className="flex justify-between items-center p-2 rounded-lg space-x-2 bg-[var(--color-surface-alt)] border border-base">
|
||||||
<div className="flex justify-start">
|
<div className="flex justify-start">
|
||||||
<Dialog.Title className="text-lg font-semibold text-gray-700">
|
<Dialog.Title className="text-lg font-semibold text-fg">
|
||||||
Eingang {selectedId ?? "–"}
|
Eingang {selectedId ?? "–"}
|
||||||
</Dialog.Title>
|
</Dialog.Title>
|
||||||
</div>
|
</div>
|
||||||
@@ -385,7 +385,7 @@ export default function AnalogInputsChart({
|
|||||||
{/* ✅ Zeitraum-Auswahl (Listbox nur lokal) */}
|
{/* ✅ Zeitraum-Auswahl (Listbox nur lokal) */}
|
||||||
<Listbox value={localZeitraum} onChange={setLocalZeitraum}>
|
<Listbox value={localZeitraum} onChange={setLocalZeitraum}>
|
||||||
<div className="relative w-48">
|
<div className="relative w-48">
|
||||||
<Listbox.Button className="w-full border px-3 py-1 rounded bg-white flex justify-between items-center text-sm">
|
<Listbox.Button className="w-full border border-base px-3 py-1 rounded bg-[var(--color-surface)] text-fg flex justify-between items-center text-sm">
|
||||||
<span>
|
<span>
|
||||||
{localZeitraum === "DIA0"
|
{localZeitraum === "DIA0"
|
||||||
? "Alle Messwerte"
|
? "Alle Messwerte"
|
||||||
@@ -393,14 +393,14 @@ export default function AnalogInputsChart({
|
|||||||
? "Stündlich"
|
? "Stündlich"
|
||||||
: "Täglich"}
|
: "Täglich"}
|
||||||
</span>
|
</span>
|
||||||
<i className="bi bi-chevron-down text-gray-400" />
|
<i className="bi bi-chevron-down text-[var(--color-muted)]" />
|
||||||
</Listbox.Button>
|
</Listbox.Button>
|
||||||
<Listbox.Options className="absolute z-10 mt-1 w-full border bg-white shadow rounded text-sm">
|
<Listbox.Options className="absolute z-10 mt-1 w-full border border-base bg-[var(--color-surface)] shadow rounded text-sm">
|
||||||
{["DIA0", "DIA1", "DIA2"].map((option) => (
|
{["DIA0", "DIA1", "DIA2"].map((option) => (
|
||||||
<Listbox.Option
|
<Listbox.Option
|
||||||
key={option}
|
key={option}
|
||||||
value={option}
|
value={option}
|
||||||
className="px-4 py-1 cursor-pointer hover:bg-gray-200"
|
className="px-4 py-1 cursor-pointer hover:bg-[var(--color-surface-alt)] text-fg"
|
||||||
>
|
>
|
||||||
{option === "DIA0"
|
{option === "DIA0"
|
||||||
? "Alle Messwerte"
|
? "Alle Messwerte"
|
||||||
@@ -416,7 +416,7 @@ export default function AnalogInputsChart({
|
|||||||
{/* ✅ Button: lädt die Daten & aktualisiert Redux */}
|
{/* ✅ Button: lädt die Daten & aktualisiert Redux */}
|
||||||
<button
|
<button
|
||||||
onClick={handleFetchData}
|
onClick={handleFetchData}
|
||||||
className="px-4 py-1 bg-littwin-blue text-white rounded text-sm"
|
className="btn-primary px-4 py-1 rounded text-sm"
|
||||||
>
|
>
|
||||||
Daten laden
|
Daten laden
|
||||||
</button>
|
</button>
|
||||||
@@ -427,11 +427,9 @@ export default function AnalogInputsChart({
|
|||||||
{/* Chart-Anzeige */}
|
{/* Chart-Anzeige */}
|
||||||
<div className="flex-1 min-h-0 w-full">
|
<div className="flex-1 min-h-0 w-full">
|
||||||
{!selectedAnalogInput?.id ? (
|
{!selectedAnalogInput?.id ? (
|
||||||
<div className="flex items-center justify-center h-full text-gray-500 text-lg gap-2">
|
<div className="flex items-center justify-center h-full text-fg-secondary text-lg gap-2">
|
||||||
<i className="bi bi-info-circle text-2xl mr-2" />
|
<i className="bi bi-info-circle text-2xl mr-2" />
|
||||||
<span>
|
<span>Bitte Eingang auswählen</span>
|
||||||
Bitte wählen Sie einen Eingang aus, um die Messkurve anzuzeigen
|
|
||||||
</span>
|
|
||||||
</div>
|
</div>
|
||||||
) : (
|
) : (
|
||||||
<Line
|
<Line
|
||||||
|
|||||||
@@ -35,82 +35,50 @@ export default function AnalogInputsChartModal({
|
|||||||
<div className="fixed inset-0 bg-black/50" aria-hidden="true" />
|
<div className="fixed inset-0 bg-black/50" aria-hidden="true" />
|
||||||
{/* Centered panel */}
|
{/* Centered panel */}
|
||||||
<div className="fixed inset-0 flex items-center justify-center p-4">
|
<div className="fixed inset-0 flex items-center justify-center p-4">
|
||||||
<Dialog.Panel className="relative">
|
<Dialog.Panel className="relative outline-none">
|
||||||
<div
|
<div
|
||||||
className="bg-white rounded-xl shadow-xl border border-gray-200"
|
className={`rounded-xl shadow-xl border border-base bg-[var(--color-surface)] text-fg flex flex-col transition-all duration-300 overflow-hidden`}
|
||||||
style={{
|
style={{
|
||||||
width: isFullscreen ? "90vw" : "70rem",
|
width: isFullscreen ? "90vw" : "70rem",
|
||||||
height: isFullscreen ? "90vh" : "35rem",
|
height: isFullscreen ? "90vh" : "38rem",
|
||||||
padding: "1rem",
|
|
||||||
transition: "all 0.3s ease-in-out",
|
|
||||||
display: "flex",
|
|
||||||
flexDirection: "column",
|
|
||||||
}}
|
}}
|
||||||
>
|
>
|
||||||
{/* Controls top-right (fullscreen + close) */}
|
{/* Header */}
|
||||||
<div
|
<header className="flex items-center justify-between px-6 py-4 border-b border-base select-none">
|
||||||
style={{
|
<h2 className="text-base font-bold">
|
||||||
position: "absolute",
|
|
||||||
top: "0.625rem",
|
|
||||||
right: "0.625rem",
|
|
||||||
display: "flex",
|
|
||||||
gap: "0.75rem",
|
|
||||||
}}
|
|
||||||
>
|
|
||||||
<button
|
|
||||||
onClick={() => setIsFullscreen((v) => !v)}
|
|
||||||
style={{
|
|
||||||
background: "transparent",
|
|
||||||
border: "none",
|
|
||||||
fontSize: "1.5rem",
|
|
||||||
cursor: "pointer",
|
|
||||||
}}
|
|
||||||
title={isFullscreen ? "Exit fullscreen" : "Fullscreen"}
|
|
||||||
aria-label={isFullscreen ? "Exit fullscreen" : "Fullscreen"}
|
|
||||||
>
|
|
||||||
<i
|
|
||||||
className={
|
|
||||||
isFullscreen
|
|
||||||
? "bi bi-fullscreen-exit"
|
|
||||||
: "bi bi-arrows-fullscreen"
|
|
||||||
}
|
|
||||||
></i>
|
|
||||||
</button>
|
|
||||||
<button
|
|
||||||
onClick={() => dispatch(setIsChartModalOpen(false))}
|
|
||||||
style={{
|
|
||||||
background: "transparent",
|
|
||||||
border: "none",
|
|
||||||
fontSize: "1.5rem",
|
|
||||||
cursor: "pointer",
|
|
||||||
}}
|
|
||||||
title="Schließen"
|
|
||||||
aria-label="Modal schließen"
|
|
||||||
>
|
|
||||||
<i className="bi bi-x-circle-fill"></i>
|
|
||||||
</button>
|
|
||||||
</div>
|
|
||||||
|
|
||||||
{/* Title row (align like IsoChartView) */}
|
|
||||||
<div className="flex justify-between items-center mb-2 pr-24">
|
|
||||||
<Dialog.Title className="text-lg font-semibold text-gray-700">
|
|
||||||
Messkurve Messwerteingang {selectedId ?? "–"}
|
Messkurve Messwerteingang {selectedId ?? "–"}
|
||||||
</Dialog.Title>
|
</h2>
|
||||||
</div>
|
<div className="flex items-center gap-3">
|
||||||
|
<button
|
||||||
{/* Chart container (structure similar to IsoChartView) */}
|
onClick={() => setIsFullscreen((v) => !v)}
|
||||||
<div
|
className="icon-btn text-xl"
|
||||||
style={{
|
aria-label={isFullscreen ? "Vollbild verlassen" : "Vollbild"}
|
||||||
flex: 1,
|
type="button"
|
||||||
display: "flex",
|
title={isFullscreen ? "Vollbild verlassen" : "Vollbild"}
|
||||||
flexDirection: "column",
|
>
|
||||||
height: "90%",
|
<i
|
||||||
}}
|
className={
|
||||||
>
|
isFullscreen
|
||||||
{/* Optional: place an action bar here if needed */}
|
? "bi bi-fullscreen-exit"
|
||||||
<div style={{ flex: 1, height: "90%" }}>
|
: "bi bi-arrows-fullscreen"
|
||||||
<AnalogInputsChart loading={loading} setLoading={setLoading} />
|
}
|
||||||
|
/>
|
||||||
|
</button>
|
||||||
|
<button
|
||||||
|
onClick={() => dispatch(setIsChartModalOpen(false))}
|
||||||
|
className="icon-btn text-2xl"
|
||||||
|
aria-label="Modal schließen"
|
||||||
|
type="button"
|
||||||
|
title="Schließen"
|
||||||
|
>
|
||||||
|
<i className="bi bi-x-circle-fill" />
|
||||||
|
</button>
|
||||||
</div>
|
</div>
|
||||||
|
</header>
|
||||||
|
|
||||||
|
{/* Body */}
|
||||||
|
<div className="flex-1 min-h-0 px-4 pt-3 pb-4 bg-[var(--color-surface)]">
|
||||||
|
<AnalogInputsChart loading={loading} setLoading={setLoading} />
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
</Dialog.Panel>
|
</Dialog.Panel>
|
||||||
|
|||||||
@@ -108,125 +108,118 @@ export default function AnalogInputsSettingsModal() {
|
|||||||
|
|
||||||
return (
|
return (
|
||||||
<div className="fixed top-0 left-0 w-full h-full bg-black bg-opacity-50 flex justify-center items-center z-50">
|
<div className="fixed top-0 left-0 w-full h-full bg-black bg-opacity-50 flex justify-center items-center z-50">
|
||||||
<div className="bg-white rounded-lg shadow-lg p-6 w-1/2 max-w-lg">
|
<div className="bg-[var(--color-surface)] border border-base rounded-xl shadow-xl w-[32rem] max-w-full p-0 flex flex-col">
|
||||||
<div className="mb-4 border-b pb-2 flex justify-between items-center">
|
<header className="modal-header flex items-center justify-between px-6 py-4 border-b border-base">
|
||||||
<h2 className="text-base font-bold">
|
<h2 className="text-base font-bold text-fg">
|
||||||
Einstellungen Messwerteingang {selectedInput.id}
|
Einstellungen Messwerteingang {selectedInput.id}
|
||||||
</h2>
|
</h2>
|
||||||
<button
|
<button
|
||||||
onClick={() => dispatch(setIsSettingsModalOpen(false))}
|
onClick={() => dispatch(setIsSettingsModalOpen(false))}
|
||||||
className="text-2xl hover:text-gray-400"
|
className="icon-btn text-2xl"
|
||||||
aria-label="Modal schließen"
|
aria-label="Modal schließen"
|
||||||
|
type="button"
|
||||||
>
|
>
|
||||||
<i className="bi bi-x-circle-fill"></i>
|
<i className="bi bi-x-circle-fill" />
|
||||||
</button>
|
</button>
|
||||||
</div>
|
</header>
|
||||||
|
<div className="modal-body-scroll px-6 py-5 flex-1 text-fg">
|
||||||
{/* Bezeichnung */}
|
<div className="grid grid-cols-2 gap-x-4 gap-y-3">
|
||||||
<div className="grid grid-cols-2 gap-x-4 gap-y-3">
|
<span className="font-normal text-fg-secondary">Bezeichnung:</span>
|
||||||
<span className="font-normal">Bezeichnung:</span>
|
<input
|
||||||
<input
|
type="text"
|
||||||
type="text"
|
className="border border-base rounded px-2 py-1 w-full bg-[var(--color-surface-alt)] text-fg"
|
||||||
className="w-full border rounded px-3 py-1 mb-4"
|
value={label}
|
||||||
value={label}
|
onChange={(e) => setLabel(e.target.value)}
|
||||||
onChange={(e) => setLabel(e.target.value)}
|
/>
|
||||||
/>
|
</div>
|
||||||
</div>
|
<div className="grid grid-cols-2 gap-x-4 gap-y-3 mb-4">
|
||||||
|
<span className="font-normal text-fg-secondary">Offset:</span>
|
||||||
{/* Offset */}
|
|
||||||
<div className="grid grid-cols-2 gap-x-4 gap-y-3 mb-4">
|
|
||||||
<span className="font-normal">Offset:</span>
|
|
||||||
<input
|
|
||||||
type="number"
|
|
||||||
step="0.001"
|
|
||||||
className="border border-gray-300 rounded px-2 py-1 w-full text-right"
|
|
||||||
value={offset}
|
|
||||||
onChange={(e) => setOffset(e.target.value)}
|
|
||||||
/>
|
|
||||||
</div>
|
|
||||||
|
|
||||||
{/* Faktor */}
|
|
||||||
<div className="grid grid-cols-2 gap-x-4 gap-y-3 mb-4">
|
|
||||||
<span className="font-normal">Faktor:</span>
|
|
||||||
<input
|
|
||||||
type="number"
|
|
||||||
step="0.001"
|
|
||||||
className="border border-gray-300 rounded px-2 py-1 w-full text-right"
|
|
||||||
value={factor}
|
|
||||||
onChange={(e) => setFactor(e.target.value)}
|
|
||||||
/>
|
|
||||||
</div>
|
|
||||||
|
|
||||||
{/* Einheit */}
|
|
||||||
<div className="grid grid-cols-2 gap-x-4 gap-y-3 mb-4">
|
|
||||||
<span className="font-normal">Einheit:</span>
|
|
||||||
<Listbox value={unit} onChange={setUnit}>
|
|
||||||
<div className="relative w-full">
|
|
||||||
<Listbox.Button className="w-full border px-3 py-1 rounded text-left bg-white flex justify-between items-center text-sm text-gray-900 font-sans">
|
|
||||||
<span>{unit}</span>
|
|
||||||
<svg
|
|
||||||
className="w-5 h-5 text-gray-400"
|
|
||||||
xmlns="http://www.w3.org/2000/svg"
|
|
||||||
viewBox="0 0 20 20"
|
|
||||||
fill="currentColor"
|
|
||||||
aria-hidden="true"
|
|
||||||
>
|
|
||||||
<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 text-gray-900 font-sans">
|
|
||||||
{unitOptions.map((opt) => (
|
|
||||||
<Listbox.Option
|
|
||||||
key={opt}
|
|
||||||
value={opt}
|
|
||||||
className={({ selected, active }) =>
|
|
||||||
`px-4 py-1 cursor-pointer ${
|
|
||||||
selected
|
|
||||||
? "bg-littwin-blue text-white font-medium"
|
|
||||||
: active
|
|
||||||
? "bg-gray-200"
|
|
||||||
: "text-gray-900"
|
|
||||||
}`
|
|
||||||
}
|
|
||||||
>
|
|
||||||
{opt}
|
|
||||||
</Listbox.Option>
|
|
||||||
))}
|
|
||||||
</Listbox.Options>
|
|
||||||
</div>
|
|
||||||
</Listbox>
|
|
||||||
</div>
|
|
||||||
|
|
||||||
{/* Speicherintervall */}
|
|
||||||
<div className="grid grid-cols-2 gap-x-4 gap-y-3">
|
|
||||||
<span className="font-normal">Speicherintervall:</span>
|
|
||||||
<div className="relative w-full">
|
|
||||||
<input
|
<input
|
||||||
type="number"
|
type="number"
|
||||||
className="border rounded px-2 py-1 pr-20 w-full text-right"
|
step="0.001"
|
||||||
value={loggerInterval}
|
className="border border-base rounded px-2 py-1 w-full text-right bg-[var(--color-surface-alt)] text-fg"
|
||||||
onChange={(e) => setLoggerInterval(e.target.value)}
|
value={offset}
|
||||||
|
onChange={(e) => setOffset(e.target.value)}
|
||||||
/>
|
/>
|
||||||
<span className="absolute right-2 top-1/2 transform -translate-y-1/2 text-gray-500 text-sm">
|
</div>
|
||||||
Minuten
|
<div className="grid grid-cols-2 gap-x-4 gap-y-3 mb-4">
|
||||||
|
<span className="font-normal text-fg-secondary">Faktor:</span>
|
||||||
|
<input
|
||||||
|
type="number"
|
||||||
|
step="0.001"
|
||||||
|
className="border border-base rounded px-2 py-1 w-full text-right bg-[var(--color-surface-alt)] text-fg"
|
||||||
|
value={factor}
|
||||||
|
onChange={(e) => setFactor(e.target.value)}
|
||||||
|
/>
|
||||||
|
</div>
|
||||||
|
<div className="grid grid-cols-2 gap-x-4 gap-y-3 mb-4">
|
||||||
|
<span className="font-normal text-fg-secondary">Einheit:</span>
|
||||||
|
<Listbox value={unit} onChange={setUnit}>
|
||||||
|
<div className="relative w-full">
|
||||||
|
<Listbox.Button className="w-full border border-base px-2 py-1 rounded text-left bg-[var(--color-surface-alt)] text-fg flex justify-between items-center text-sm font-sans">
|
||||||
|
<span>{unit}</span>
|
||||||
|
<svg
|
||||||
|
className="w-5 h-5 text-muted"
|
||||||
|
xmlns="http://www.w3.org/2000/svg"
|
||||||
|
viewBox="0 0 20 20"
|
||||||
|
fill="currentColor"
|
||||||
|
aria-hidden="true"
|
||||||
|
>
|
||||||
|
<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 border-base rounded bg-[var(--color-surface-alt)] shadow max-h-60 overflow-auto text-sm text-fg font-sans">
|
||||||
|
{unitOptions.map((opt) => (
|
||||||
|
<Listbox.Option
|
||||||
|
key={opt}
|
||||||
|
value={opt}
|
||||||
|
className={({ selected, active }) =>
|
||||||
|
`px-4 py-1 cursor-pointer ${
|
||||||
|
selected
|
||||||
|
? "bg-littwin-blue text-white font-medium"
|
||||||
|
: active
|
||||||
|
? "bg-base-muted"
|
||||||
|
: "text-fg"
|
||||||
|
}`
|
||||||
|
}
|
||||||
|
>
|
||||||
|
{opt}
|
||||||
|
</Listbox.Option>
|
||||||
|
))}
|
||||||
|
</Listbox.Options>
|
||||||
|
</div>
|
||||||
|
</Listbox>
|
||||||
|
</div>
|
||||||
|
<div className="grid grid-cols-2 gap-x-4 gap-y-3">
|
||||||
|
<span className="font-normal text-fg-secondary">
|
||||||
|
Speicherintervall:
|
||||||
</span>
|
</span>
|
||||||
|
<div className="relative w-full">
|
||||||
|
<input
|
||||||
|
type="number"
|
||||||
|
className="border border-base rounded px-2 py-1 pr-20 w-full text-right bg-[var(--color-surface-alt)] text-fg"
|
||||||
|
value={loggerInterval}
|
||||||
|
onChange={(e) => setLoggerInterval(e.target.value)}
|
||||||
|
/>
|
||||||
|
<span className="absolute right-2 top-1/2 transform -translate-y-1/2 text-muted text-sm">
|
||||||
|
Minuten
|
||||||
|
</span>
|
||||||
|
</div>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
|
<footer className="px-6 py-4 border-t border-base flex justify-end">
|
||||||
{/* Speichern-Button */}
|
|
||||||
<div className="flex justify-end gap-2 mt-6">
|
|
||||||
<button
|
<button
|
||||||
onClick={handleSave}
|
onClick={handleSave}
|
||||||
disabled={isSaving}
|
disabled={isSaving}
|
||||||
className="bg-littwin-blue text-white px-4 py-2 rounded flex items-center"
|
className="btn-primary px-4 py-2 rounded flex items-center"
|
||||||
>
|
>
|
||||||
{isSaving ? "Speichern..." : "Speichern"}
|
{isSaving ? "Speichern..." : "Speichern"}
|
||||||
</button>
|
</button>
|
||||||
</div>
|
</footer>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
);
|
);
|
||||||
|
|||||||
@@ -182,132 +182,133 @@ export default function InputModal({
|
|||||||
|
|
||||||
return (
|
return (
|
||||||
<div className="fixed top-0 left-0 w-full h-full bg-black bg-opacity-50 flex justify-center items-center z-50">
|
<div className="fixed top-0 left-0 w-full h-full bg-black bg-opacity-50 flex justify-center items-center z-50">
|
||||||
<div className="bg-white rounded-lg shadow-lg p-6 w-1/2 max-w-lg">
|
<div className="bg-[var(--color-surface)] border border-base rounded-xl shadow-xl w-[32rem] max-w-full p-0 flex flex-col">
|
||||||
<div className="mb-4 border-b pb-2 flex justify-between items-center">
|
<header className="modal-header flex items-center justify-between px-6 py-4 border-b border-base">
|
||||||
<h2 className="text-base font-bold">
|
<h2 className="text-base font-bold text-fg">
|
||||||
Einstellungen Meldungseingang {selectedInput.id}
|
Einstellungen Meldungseingang {selectedInput.id}
|
||||||
</h2>
|
</h2>
|
||||||
<button
|
<button
|
||||||
onClick={handleClose}
|
onClick={handleClose}
|
||||||
className="text-2xl hover:text-gray-400"
|
className="icon-btn text-2xl"
|
||||||
aria-label="Modal schließen"
|
aria-label="Modal schließen"
|
||||||
|
type="button"
|
||||||
>
|
>
|
||||||
<i className="bi bi-x-circle-fill"></i>
|
<i className="bi bi-x-circle-fill" />
|
||||||
</button>
|
</button>
|
||||||
</div>
|
</header>
|
||||||
|
<div className="modal-body-scroll px-6 py-5 flex-1 text-fg">
|
||||||
<div className="grid grid-cols-2 gap-x-4 gap-y-3">
|
<div className="grid grid-cols-2 gap-x-4 gap-y-3">
|
||||||
<div>
|
<div>
|
||||||
<span className="font-normal">Bezeichnung:</span>
|
<span className="font-normal text-fg-secondary">
|
||||||
</div>
|
Bezeichnung:
|
||||||
<div>
|
</span>
|
||||||
<input
|
</div>
|
||||||
type="text"
|
<div>
|
||||||
value={label}
|
<input
|
||||||
onChange={(e) => setLabel(e.target.value)}
|
type="text"
|
||||||
className="border border-gray-300 rounded px-2 py-1 w-full"
|
value={label}
|
||||||
maxLength={32}
|
onChange={(e) => setLabel(e.target.value)}
|
||||||
/>
|
className="border border-base rounded px-2 py-1 w-full bg-[var(--color-surface-alt)] text-fg"
|
||||||
</div>
|
maxLength={32}
|
||||||
|
|
||||||
<div>
|
|
||||||
<span className="font-normal">Invertierung:</span>
|
|
||||||
</div>
|
|
||||||
<div className="flex items-center gap-2">
|
|
||||||
<button
|
|
||||||
type="button"
|
|
||||||
role="switch"
|
|
||||||
aria-checked={invertiert}
|
|
||||||
onClick={() => setInvertiert(!invertiert)}
|
|
||||||
className={`relative inline-flex h-6 w-11 items-center rounded-full transition-colors duration-200 ${
|
|
||||||
invertiert ? "bg-littwin-blue" : "bg-gray-300"
|
|
||||||
}`}
|
|
||||||
>
|
|
||||||
<span
|
|
||||||
className={`inline-block h-4 w-4 transform rounded-full bg-white transition-transform duration-200 ${
|
|
||||||
invertiert ? "translate-x-6" : "translate-x-1"
|
|
||||||
}`}
|
|
||||||
/>
|
/>
|
||||||
</button>
|
</div>
|
||||||
<span>{invertiert ? "Ein" : "Aus"}</span>
|
<div>
|
||||||
</div>
|
<span className="font-normal text-fg-secondary">
|
||||||
|
Invertierung:
|
||||||
<div>
|
</span>
|
||||||
<span className="font-normal">Filterzeit:</span>
|
</div>
|
||||||
</div>
|
<div className="flex items-center gap-2">
|
||||||
<div className="relative">
|
<button
|
||||||
<input
|
type="button"
|
||||||
type="number"
|
role="switch"
|
||||||
min={0}
|
aria-checked={invertiert}
|
||||||
max={2000}
|
onClick={() => setInvertiert(!invertiert)}
|
||||||
value={timeFilter}
|
className={`relative inline-flex h-6 w-11 items-center rounded-full border border-base transition-colors duration-200 ${
|
||||||
onChange={(e) => {
|
invertiert ? "bg-littwin-blue" : "bg-base-muted"
|
||||||
const val = Number(e.target.value);
|
|
||||||
if (val <= 2000) {
|
|
||||||
setTimeFilter(val);
|
|
||||||
}
|
|
||||||
}}
|
|
||||||
className="border border-gray-300 rounded px-2 py-1 pr-10 w-full text-right"
|
|
||||||
title="Maximal 2000 ms erlaubt"
|
|
||||||
/>
|
|
||||||
|
|
||||||
<span className="absolute right-2 top-1/2 transform -translate-y-1/2 text-gray-500 text-sm">
|
|
||||||
ms
|
|
||||||
</span>
|
|
||||||
</div>
|
|
||||||
|
|
||||||
<div>
|
|
||||||
<span className="font-normal">Gewichtung:</span>
|
|
||||||
</div>
|
|
||||||
<div>
|
|
||||||
<input
|
|
||||||
type="number"
|
|
||||||
min={0}
|
|
||||||
max={1000}
|
|
||||||
value={weighting}
|
|
||||||
onChange={(e) => {
|
|
||||||
const val = Number(e.target.value);
|
|
||||||
if (val <= 1000) {
|
|
||||||
setWeighting(val);
|
|
||||||
}
|
|
||||||
}}
|
|
||||||
className="border border-gray-300 rounded px-2 py-1 w-full text-right"
|
|
||||||
title="Maximal 1000 erlaubt"
|
|
||||||
/>
|
|
||||||
</div>
|
|
||||||
|
|
||||||
<div className="relative group inline-block">
|
|
||||||
<span className="font-normal">Out of Service:</span>
|
|
||||||
</div>
|
|
||||||
|
|
||||||
<div className="flex items-center gap-2">
|
|
||||||
<button
|
|
||||||
type="button"
|
|
||||||
role="switch"
|
|
||||||
aria-checked={!!eingangOffline}
|
|
||||||
onClick={() => setEingangOffline(eingangOffline ? 0 : 1)}
|
|
||||||
className={`relative inline-flex h-6 w-11 items-center rounded-full transition-colors duration-200 ${
|
|
||||||
eingangOffline ? "bg-littwin-blue" : "bg-gray-300"
|
|
||||||
}`}
|
|
||||||
>
|
|
||||||
<span
|
|
||||||
className={`inline-block h-4 w-4 transform rounded-full bg-white transition-transform duration-200 ${
|
|
||||||
eingangOffline ? "translate-x-6" : "translate-x-1"
|
|
||||||
}`}
|
}`}
|
||||||
|
>
|
||||||
|
<span
|
||||||
|
className={`absolute left-1 top-1/2 -translate-y-1/2 h-4 w-4 rounded-full bg-white shadow transition-transform duration-200 ${
|
||||||
|
invertiert ? "translate-x-5" : "translate-x-0"
|
||||||
|
}`}
|
||||||
|
/>
|
||||||
|
</button>
|
||||||
|
<span className="text-fg">{invertiert ? "Ein" : "Aus"}</span>
|
||||||
|
</div>
|
||||||
|
<div>
|
||||||
|
<span className="font-normal text-fg-secondary">Filterzeit:</span>
|
||||||
|
</div>
|
||||||
|
<div className="relative">
|
||||||
|
<input
|
||||||
|
type="number"
|
||||||
|
min={0}
|
||||||
|
max={2000}
|
||||||
|
value={timeFilter}
|
||||||
|
onChange={(e) => {
|
||||||
|
const val = Number(e.target.value);
|
||||||
|
if (val <= 2000) {
|
||||||
|
setTimeFilter(val);
|
||||||
|
}
|
||||||
|
}}
|
||||||
|
className="border border-base rounded px-2 py-1 pr-10 w-full text-right bg-[var(--color-surface-alt)] text-fg"
|
||||||
|
title="Maximal 2000 ms erlaubt"
|
||||||
/>
|
/>
|
||||||
</button>
|
<span className="absolute right-2 top-1/2 transform -translate-y-1/2 text-muted text-sm">
|
||||||
<span>{eingangOffline ? "Ein" : "Aus"}</span>
|
ms
|
||||||
|
</span>
|
||||||
|
</div>
|
||||||
|
<div>
|
||||||
|
<span className="font-normal text-fg-secondary">Gewichtung:</span>
|
||||||
|
</div>
|
||||||
|
<div>
|
||||||
|
<input
|
||||||
|
type="number"
|
||||||
|
min={0}
|
||||||
|
max={1000}
|
||||||
|
value={weighting}
|
||||||
|
onChange={(e) => {
|
||||||
|
const val = Number(e.target.value);
|
||||||
|
if (val <= 1000) {
|
||||||
|
setWeighting(val);
|
||||||
|
}
|
||||||
|
}}
|
||||||
|
className="border border-base rounded px-2 py-1 w-full text-right bg-[var(--color-surface-alt)] text-fg"
|
||||||
|
title="Maximal 1000 erlaubt"
|
||||||
|
/>
|
||||||
|
</div>
|
||||||
|
<div className="relative group inline-block">
|
||||||
|
<span className="font-normal text-fg-secondary">
|
||||||
|
Out of Service:
|
||||||
|
</span>
|
||||||
|
</div>
|
||||||
|
<div className="flex items-center gap-2">
|
||||||
|
<button
|
||||||
|
type="button"
|
||||||
|
role="switch"
|
||||||
|
aria-checked={!!eingangOffline}
|
||||||
|
onClick={() => setEingangOffline(eingangOffline ? 0 : 1)}
|
||||||
|
className={`relative inline-flex h-6 w-11 items-center rounded-full border border-base transition-colors duration-200 ${
|
||||||
|
eingangOffline ? "bg-littwin-blue" : "bg-base-muted"
|
||||||
|
}`}
|
||||||
|
>
|
||||||
|
<span
|
||||||
|
className={`absolute left-1 top-1/2 -translate-y-1/2 h-4 w-4 rounded-full bg-white shadow transition-transform duration-200 ${
|
||||||
|
eingangOffline ? "translate-x-5" : "translate-x-0"
|
||||||
|
}`}
|
||||||
|
/>
|
||||||
|
</button>
|
||||||
|
<span className="text-fg">{eingangOffline ? "Ein" : "Aus"}</span>
|
||||||
|
</div>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
|
<footer className="px-6 py-4 border-t border-base flex justify-end">
|
||||||
<div className="mt-6 flex justify-end gap-2">
|
|
||||||
<button
|
<button
|
||||||
onClick={handleSpeichern}
|
onClick={handleSpeichern}
|
||||||
className="bg-littwin-blue text-white px-4 py-2 rounded flex items-center"
|
className="btn-primary px-4 py-2 rounded flex items-center"
|
||||||
>
|
>
|
||||||
Speichern
|
Speichern
|
||||||
</button>
|
</button>
|
||||||
</div>
|
</footer>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
);
|
);
|
||||||
|
|||||||
@@ -94,44 +94,48 @@ export default function DigitalOutputsModal({
|
|||||||
|
|
||||||
return (
|
return (
|
||||||
<div className="fixed top-0 left-0 w-full h-full bg-black bg-opacity-50 flex justify-center items-center z-50">
|
<div className="fixed top-0 left-0 w-full h-full bg-black bg-opacity-50 flex justify-center items-center z-50">
|
||||||
<div className="bg-white rounded-lg shadow-lg p-6 w-1/2 max-w-lg">
|
<div className="bg-[var(--color-surface)] border border-base rounded-xl shadow-xl w-[32rem] max-w-full p-0 flex flex-col">
|
||||||
<div className="mb-4 border-b pb-2 flex justify-between items-center">
|
<header className="modal-header flex items-center justify-between px-6 py-4 border-b border-base">
|
||||||
<h2 className="text-base font-bold">
|
<h2 className="text-base font-bold text-fg">
|
||||||
Einstellungen Schaltausgang {selectedOutput.id}
|
Einstellungen Schaltausgang {selectedOutput.id}
|
||||||
</h2>
|
</h2>
|
||||||
<button
|
<button
|
||||||
onClick={closeOutputModal}
|
onClick={closeOutputModal}
|
||||||
className="text-2xl hover:text-gray-400"
|
className="icon-btn text-2xl"
|
||||||
aria-label="Modal schließen"
|
aria-label="Modal schließen"
|
||||||
|
type="button"
|
||||||
>
|
>
|
||||||
<i className="bi bi-x-circle-fill"></i>
|
<i className="bi bi-x-circle-fill" />
|
||||||
</button>
|
</button>
|
||||||
</div>
|
</header>
|
||||||
|
<div className="modal-body-scroll px-6 py-5 flex-1 text-fg">
|
||||||
<div className="grid grid-cols-2 gap-x-4 gap-y-3">
|
<div className="grid grid-cols-2 gap-x-4 gap-y-3">
|
||||||
<div>
|
<div>
|
||||||
<span className="font-normal">Bezeichnung:</span>
|
<span className="font-normal text-fg-secondary">
|
||||||
|
Bezeichnung:
|
||||||
|
</span>
|
||||||
|
</div>
|
||||||
|
<div>
|
||||||
|
<input
|
||||||
|
type="text"
|
||||||
|
value={label}
|
||||||
|
onChange={(e) => setLabel(e.target.value)}
|
||||||
|
className="border border-base rounded px-2 py-1 w-full bg-[var(--color-surface-alt)] text-fg"
|
||||||
|
placeholder="z. B. Licht Relais 1"
|
||||||
|
/>
|
||||||
|
</div>
|
||||||
</div>
|
</div>
|
||||||
<input
|
{errorMsg && <p className="text-red-600 text-sm mb-2">{errorMsg}</p>}
|
||||||
type="text"
|
|
||||||
value={label}
|
|
||||||
onChange={(e) => setLabel(e.target.value)}
|
|
||||||
className="w-full border border-gray-300 rounded px-3 py-2"
|
|
||||||
placeholder="z. B. Licht Relais 1"
|
|
||||||
/>
|
|
||||||
</div>
|
</div>
|
||||||
|
<footer className="px-6 py-4 border-t border-base flex justify-end">
|
||||||
{errorMsg && <p className="text-red-600 text-sm mb-2">{errorMsg}</p>}
|
|
||||||
|
|
||||||
<div className="flex justify-end gap-2 mt-6">
|
|
||||||
<button
|
<button
|
||||||
onClick={handleSave}
|
onClick={handleSave}
|
||||||
disabled={isSaving}
|
disabled={isSaving}
|
||||||
className="bg-littwin-blue text-white px-4 py-2 rounded flex items-center"
|
className="btn-primary px-4 py-2 rounded flex items-center"
|
||||||
>
|
>
|
||||||
{isSaving ? "Speichern..." : "Speichern"}
|
{isSaving ? "Speichern..." : "Speichern"}
|
||||||
</button>
|
</button>
|
||||||
</div>
|
</footer>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
);
|
);
|
||||||
|
|||||||
@@ -108,15 +108,21 @@ export default function DigitalOutputsWidget({
|
|||||||
{output.label}
|
{output.label}
|
||||||
</td>
|
</td>
|
||||||
<td className="px-1 py-1 bg-[var(--color-surface)] text-[var(--color-fg)]">
|
<td className="px-1 py-1 bg-[var(--color-surface)] text-[var(--color-fg)]">
|
||||||
<Icon
|
<button
|
||||||
icon={switchIcon}
|
type="button"
|
||||||
className={`cursor-pointer text-base transition ${
|
role="switch"
|
||||||
output.status
|
aria-checked={output.status}
|
||||||
? "text-accent"
|
|
||||||
: "text-[var(--color-muted)] scale-x-[-1]"
|
|
||||||
} hover:text-accent`}
|
|
||||||
onClick={() => handleToggle(output.id)}
|
onClick={() => handleToggle(output.id)}
|
||||||
/>
|
className={`relative inline-flex h-4 w-7 items-center rounded-full border border-base transition-colors duration-200 ${
|
||||||
|
output.status ? "bg-littwin-blue" : "bg-base-muted"
|
||||||
|
}`}
|
||||||
|
>
|
||||||
|
<span
|
||||||
|
className={`absolute left-0.5 top-1/2 -translate-y-1/2 h-3 w-3 rounded-full bg-white shadow transition-transform duration-200 ${
|
||||||
|
output.status ? "translate-x-3.5" : "translate-x-0"
|
||||||
|
}`}
|
||||||
|
/>
|
||||||
|
</button>
|
||||||
</td>
|
</td>
|
||||||
<td className="px-1 py-1 bg-[var(--color-surface)] text-[var(--color-fg)]">
|
<td className="px-1 py-1 bg-[var(--color-surface)] text-[var(--color-fg)]">
|
||||||
<Icon
|
<Icon
|
||||||
|
|||||||
@@ -41,7 +41,6 @@ export const useIsoChartLoader = () => {
|
|||||||
)};${formatDate(bisDatum)};${slotNumber};${type};`;
|
)};${formatDate(bisDatum)};${slotNumber};${type};`;
|
||||||
}
|
}
|
||||||
|
|
||||||
console.log("API URL:", url);
|
|
||||||
return url;
|
return url;
|
||||||
};
|
};
|
||||||
|
|
||||||
@@ -143,19 +142,10 @@ export const useIsoDataLoader = () => {
|
|||||||
const waitTime = Math.max(0, MIN_LOADING_TIME_MS - elapsedTime);
|
const waitTime = Math.max(0, MIN_LOADING_TIME_MS - elapsedTime);
|
||||||
await new Promise((resolve) => setTimeout(resolve, waitTime));
|
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) {
|
if (Array.isArray(jsonData) && jsonData.length > 0) {
|
||||||
dispatch(setIsoMeasurementCurveChartData(jsonData));
|
dispatch(setIsoMeasurementCurveChartData(jsonData));
|
||||||
dispatch(setChartOpen(true));
|
dispatch(setChartOpen(true));
|
||||||
} else {
|
} else {
|
||||||
console.log(
|
|
||||||
"⚠️ Keine Messdaten im gewählten Zeitraum gefunden (automatisches Laden)"
|
|
||||||
);
|
|
||||||
dispatch(setIsoMeasurementCurveChartData([]));
|
dispatch(setIsoMeasurementCurveChartData([]));
|
||||||
dispatch(setChartOpen(false));
|
dispatch(setChartOpen(false));
|
||||||
}
|
}
|
||||||
@@ -170,8 +160,6 @@ export const useIsoDataLoader = () => {
|
|||||||
};
|
};
|
||||||
|
|
||||||
//-----------------------------------------------------------------------------------IsoChartActionBar
|
//-----------------------------------------------------------------------------------IsoChartActionBar
|
||||||
// ...existing code...
|
|
||||||
|
|
||||||
const IsoChartActionBar = forwardRef((_props, ref) => {
|
const IsoChartActionBar = forwardRef((_props, ref) => {
|
||||||
IsoChartActionBar.displayName = "IsoChartActionBar";
|
IsoChartActionBar.displayName = "IsoChartActionBar";
|
||||||
const dispatch = useAppDispatch();
|
const dispatch = useAppDispatch();
|
||||||
@@ -281,106 +269,90 @@ const IsoChartActionBar = forwardRef((_props, ref) => {
|
|||||||
}
|
}
|
||||||
};
|
};
|
||||||
|
|
||||||
useImperativeHandle(ref, () => ({
|
useImperativeHandle(ref, () => ({ handleFetchData }));
|
||||||
handleFetchData,
|
|
||||||
}));
|
const isMeldungen = chartTitle === "Meldungen";
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<div className="flex justify-between items-center p-2 bg-gray-100 rounded-lg space-x-2">
|
<div className="toolbar w-full justify-between flex-wrap">
|
||||||
<div className="flex items-center">
|
<div className="flex items-center gap-2 pr-4">
|
||||||
<label className="text-sm font-semibold">
|
<span className=" font-semibold uppercase tracking-wide text-muted">
|
||||||
KÜ {slotNumber !== null ? slotNumber + 1 : "-"}
|
KÜ
|
||||||
</label>
|
</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>
|
||||||
|
<div className="flex items-center gap-3 flex-1 justify-end">
|
||||||
<div className="flex items-center space-x-2">
|
{/* Always show date range; requirement: in Meldungen only Von/Bis + Anzeigen */}
|
||||||
{/* DateRangePicker – für beide Ansichten sichtbar, da Meldungen auch datumsabhängig sind */}
|
<DateRangePicker />
|
||||||
<div
|
{!isMeldungen && (
|
||||||
style={{
|
<>
|
||||||
visibility: chartTitle === "Messkurve" ? "visible" : "hidden",
|
<Listbox
|
||||||
}}
|
value={selectedMode}
|
||||||
>
|
onChange={(value) => {
|
||||||
<DateRangePicker />
|
dispatch(setSelectedMode(value));
|
||||||
</div>
|
dispatch(setBrushRange({ startIndex: 0, endIndex: 0 }));
|
||||||
|
}}
|
||||||
{/* DIA0-DIA2 Dropdown - Platz reservieren, aber ausblenden wenn Meldungen */}
|
>
|
||||||
<div
|
<div className="relative w-48">
|
||||||
style={{
|
<Listbox.Button className="dropdown-surface w-full flex items-center justify-between">
|
||||||
visibility: chartTitle === "Messkurve" ? "visible" : "hidden",
|
<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="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",
|
DIA0: "Alle Messwerte",
|
||||||
DIA1: "Stündliche Werte",
|
DIA1: "Stündlich",
|
||||||
DIA2: "Tägliche Werte",
|
DIA2: "Täglich",
|
||||||
}[mode]
|
}[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
|
||||||
|
key={mode}
|
||||||
{/* Dropdown für Auswahl zwischen "Messkurve" und "Meldungen" - immer anzeigen */}
|
value={mode}
|
||||||
{/* Dropdown für Auswahl zwischen "Messkurve" und "Meldungen" entfernt */}
|
className={({ selected, active }) =>
|
||||||
|
`px-3 py-1.5 cursor-pointer rounded-sm m-0.5 ${
|
||||||
{/* Daten laden Button – lädt je nach Ansicht Messkurve oder Meldungen */}
|
selected
|
||||||
<button
|
? "dropdown-option-active"
|
||||||
style={{
|
: active
|
||||||
visibility: chartTitle === "Messkurve" ? "visible" : "hidden",
|
? "dropdown-option-hover"
|
||||||
}}
|
: ""
|
||||||
onClick={handleFetchData}
|
}`
|
||||||
className="px-4 py-1 bg-littwin-blue text-white rounded text-sm"
|
}
|
||||||
>
|
>
|
||||||
Daten laden
|
{
|
||||||
</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>
|
||||||
);
|
);
|
||||||
|
|||||||
@@ -1,4 +1,4 @@
|
|||||||
"use client"; // IsoChartView.tsx
|
"use client";
|
||||||
|
|
||||||
import React, { useEffect, useRef } from "react";
|
import React, { useEffect, useRef } from "react";
|
||||||
import { Listbox } from "@headlessui/react";
|
import { Listbox } from "@headlessui/react";
|
||||||
@@ -7,23 +7,18 @@ import IsoMeasurementChart from "./IsoMeasurementChart";
|
|||||||
import IsoChartActionBar from "./IsoChartActionBar";
|
import IsoChartActionBar from "./IsoChartActionBar";
|
||||||
import Report from "./Report";
|
import Report from "./Report";
|
||||||
import { useSelector, useDispatch } from "react-redux";
|
import { useSelector, useDispatch } from "react-redux";
|
||||||
import { AppDispatch } from "@/redux/store";
|
import { AppDispatch, RootState } from "@/redux/store";
|
||||||
import { RootState } from "@/redux/store";
|
|
||||||
import {
|
import {
|
||||||
setChartOpen,
|
setChartOpen,
|
||||||
setFullScreen,
|
setFullScreen,
|
||||||
setSlotNumber,
|
setSlotNumber,
|
||||||
setChartTitle,
|
setChartTitle,
|
||||||
} from "@/redux/slices/kabelueberwachungChartSlice";
|
|
||||||
|
|
||||||
import { resetBrushRange } from "@/redux/slices/brushSlice";
|
|
||||||
|
|
||||||
import {
|
|
||||||
setVonDatum,
|
setVonDatum,
|
||||||
setBisDatum,
|
setBisDatum,
|
||||||
setSelectedMode,
|
setSelectedMode,
|
||||||
setSelectedSlotType,
|
setSelectedSlotType,
|
||||||
} from "@/redux/slices/kabelueberwachungChartSlice";
|
} from "@/redux/slices/kabelueberwachungChartSlice";
|
||||||
|
import { resetBrushRange } from "@/redux/slices/brushSlice";
|
||||||
import { resetDateRange } from "@/redux/slices/dateRangePickerSlice";
|
import { resetDateRange } from "@/redux/slices/dateRangePickerSlice";
|
||||||
|
|
||||||
interface IsoChartViewProps {
|
interface IsoChartViewProps {
|
||||||
@@ -32,85 +27,59 @@ interface IsoChartViewProps {
|
|||||||
slotIndex: number;
|
slotIndex: number;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
type ActionBarRefType = { handleFetchData: () => void };
|
||||||
|
|
||||||
const IsoChartView: React.FC<IsoChartViewProps> = ({
|
const IsoChartView: React.FC<IsoChartViewProps> = ({
|
||||||
isOpen,
|
isOpen,
|
||||||
onClose,
|
onClose,
|
||||||
slotIndex,
|
slotIndex,
|
||||||
}) => {
|
}) => {
|
||||||
const dispatch = useDispatch<AppDispatch>();
|
const dispatch = useDispatch<AppDispatch>();
|
||||||
// removed unused loadData
|
|
||||||
|
|
||||||
const { isFullScreen, chartTitle } = useSelector(
|
const { isFullScreen, chartTitle } = useSelector(
|
||||||
(state: RootState) => state.kabelueberwachungChartSlice
|
(state: RootState) => state.kabelueberwachungChartSlice
|
||||||
);
|
);
|
||||||
|
|
||||||
// **Modal schließen + Redux-Status zurücksetzen**
|
const actionBarRef = useRef<ActionBarRefType>(null);
|
||||||
const handleClose = () => {
|
|
||||||
|
const initDates = () => {
|
||||||
const today = new Date();
|
const today = new Date();
|
||||||
const thirtyDaysAgo = new Date();
|
const thirtyDaysAgo = new Date();
|
||||||
thirtyDaysAgo.setDate(today.getDate() - 30);
|
thirtyDaysAgo.setDate(today.getDate() - 30);
|
||||||
|
const toISO = (d: Date) => d.toLocaleDateString("sv-SE");
|
||||||
const toISO = (date: Date) => date.toLocaleDateString("sv-SE");
|
|
||||||
|
|
||||||
// Reset Datum
|
|
||||||
dispatch(setVonDatum(toISO(thirtyDaysAgo)));
|
dispatch(setVonDatum(toISO(thirtyDaysAgo)));
|
||||||
dispatch(setBisDatum(toISO(today)));
|
dispatch(setBisDatum(toISO(today)));
|
||||||
|
};
|
||||||
|
|
||||||
// Reset DateRangePicker
|
const handleClose = () => {
|
||||||
|
initDates();
|
||||||
dispatch(resetDateRange());
|
dispatch(resetDateRange());
|
||||||
|
dispatch(setSelectedMode("DIA0"));
|
||||||
// Reset Dropdowns
|
|
||||||
dispatch(setSelectedMode("DIA0")); // Reset to Alle Messwerte
|
|
||||||
dispatch(setSelectedSlotType("isolationswiderstand"));
|
dispatch(setSelectedSlotType("isolationswiderstand"));
|
||||||
dispatch(setChartTitle("Messkurve")); // Reset zu Messkurve
|
dispatch(setChartTitle("Messkurve"));
|
||||||
|
|
||||||
// Sonstiges Reset
|
|
||||||
dispatch(setChartOpen(false));
|
dispatch(setChartOpen(false));
|
||||||
dispatch(setFullScreen(false));
|
dispatch(setFullScreen(false));
|
||||||
dispatch(resetBrushRange());
|
dispatch(resetBrushRange());
|
||||||
|
|
||||||
onClose();
|
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(() => {
|
useEffect(() => {
|
||||||
if (isOpen) {
|
if (isOpen) {
|
||||||
|
dispatch(setSlotNumber(slotIndex));
|
||||||
|
// inline initDates to avoid extra dependency
|
||||||
const today = new Date();
|
const today = new Date();
|
||||||
const thirtyDaysAgo = new Date();
|
const thirtyDaysAgo = new Date();
|
||||||
thirtyDaysAgo.setDate(today.getDate() - 30);
|
thirtyDaysAgo.setDate(today.getDate() - 30);
|
||||||
|
const toISO = (d: Date) => d.toLocaleDateString("sv-SE");
|
||||||
const toISO = (date: Date) => date.toLocaleDateString("sv-SE");
|
|
||||||
|
|
||||||
// Set slot number first
|
|
||||||
dispatch(setSlotNumber(slotIndex));
|
|
||||||
|
|
||||||
// Set dates
|
|
||||||
dispatch(setVonDatum(toISO(thirtyDaysAgo)));
|
dispatch(setVonDatum(toISO(thirtyDaysAgo)));
|
||||||
dispatch(setBisDatum(toISO(today)));
|
dispatch(setBisDatum(toISO(today)));
|
||||||
|
|
||||||
// Set ISO specific settings
|
|
||||||
dispatch(setSelectedSlotType("isolationswiderstand"));
|
dispatch(setSelectedSlotType("isolationswiderstand"));
|
||||||
dispatch(setSelectedMode("DIA0")); // Set to Alle Messwerte on open
|
dispatch(setSelectedMode("DIA0"));
|
||||||
|
|
||||||
// Set default to Messkurve
|
|
||||||
dispatch(setChartTitle("Messkurve"));
|
dispatch(setChartTitle("Messkurve"));
|
||||||
|
const t = setTimeout(() => actionBarRef.current?.handleFetchData(), 120);
|
||||||
// Automatisch Daten laden wie Button-Klick
|
return () => clearTimeout(t);
|
||||||
const timer = setTimeout(() => {
|
|
||||||
actionBarRef.current?.handleFetchData();
|
|
||||||
}, 120);
|
|
||||||
|
|
||||||
// Cleanup timer
|
|
||||||
return () => clearTimeout(timer);
|
|
||||||
}
|
}
|
||||||
// eslint-disable-next-line react-hooks/exhaustive-deps
|
|
||||||
}, [isOpen, slotIndex, dispatch]);
|
}, [isOpen, slotIndex, dispatch]);
|
||||||
|
|
||||||
return (
|
return (
|
||||||
@@ -119,74 +88,63 @@ const IsoChartView: React.FC<IsoChartViewProps> = ({
|
|||||||
onRequestClose={handleClose}
|
onRequestClose={handleClose}
|
||||||
ariaHideApp={false}
|
ariaHideApp={false}
|
||||||
style={{
|
style={{
|
||||||
overlay: { backgroundColor: "rgba(0, 0, 0, 0.5)" },
|
overlay: {
|
||||||
|
backgroundColor: "rgba(0,0,0,0.55)",
|
||||||
|
backdropFilter: "blur(2px)",
|
||||||
|
},
|
||||||
content: {
|
content: {
|
||||||
top: "50%",
|
inset: "50% auto auto 50%",
|
||||||
left: "50%",
|
|
||||||
bottom: "auto",
|
|
||||||
marginRight: "-50%",
|
|
||||||
transform: "translate(-50%, -50%)",
|
transform: "translate(-50%, -50%)",
|
||||||
width: isFullScreen ? "90vw" : "70rem",
|
width: isFullScreen ? "90vw" : "72rem",
|
||||||
height: isFullScreen ? "90vh" : "35rem",
|
height: isFullScreen ? "90vh" : "38rem",
|
||||||
padding: "1rem",
|
padding: 0,
|
||||||
transition: "all 0.3s ease-in-out",
|
border: "1px solid var(--color-border)",
|
||||||
|
background: "var(--color-surface)",
|
||||||
|
borderRadius: "14px",
|
||||||
display: "flex",
|
display: "flex",
|
||||||
flexDirection: "column",
|
flexDirection: "column",
|
||||||
|
overflow: "hidden",
|
||||||
},
|
},
|
||||||
}}
|
}}
|
||||||
|
contentLabel="Isolationswiderstand"
|
||||||
>
|
>
|
||||||
{/* Action-Buttons */}
|
<header className="modal-header relative pr-56">
|
||||||
<div
|
<h3 className="text-sm font-semibold tracking-wide">
|
||||||
style={{
|
Isolationswiderstand
|
||||||
position: "absolute",
|
</h3>
|
||||||
top: "0.625rem",
|
<div className="absolute top-2 right-2 flex gap-2">
|
||||||
right: "0.625rem",
|
<button
|
||||||
display: "flex",
|
onClick={toggleFullScreen}
|
||||||
gap: "0.75rem",
|
className="icon-btn"
|
||||||
}}
|
aria-label={isFullScreen ? "Vollbild verlassen" : "Vollbild"}
|
||||||
>
|
type="button"
|
||||||
{/* Fullscreen-Button */}
|
>
|
||||||
<button
|
<i
|
||||||
onClick={toggleFullScreen}
|
className={
|
||||||
style={{
|
isFullScreen
|
||||||
background: "transparent",
|
? "bi bi-fullscreen-exit"
|
||||||
border: "none",
|
: "bi bi-arrows-fullscreen"
|
||||||
fontSize: "1.5rem",
|
}
|
||||||
cursor: "pointer",
|
/>
|
||||||
}}
|
</button>
|
||||||
>
|
<button
|
||||||
<i
|
onClick={handleClose}
|
||||||
className={
|
className="icon-btn"
|
||||||
isFullScreen ? "bi bi-fullscreen-exit" : "bi bi-arrows-fullscreen"
|
aria-label="Schließen"
|
||||||
}
|
type="button"
|
||||||
></i>
|
>
|
||||||
</button>
|
<i
|
||||||
|
style={{
|
||||||
{/* Schließen-Button */}
|
background: "transparent",
|
||||||
<button
|
border: "none",
|
||||||
onClick={handleClose}
|
fontSize: "1.5rem",
|
||||||
style={{
|
cursor: "pointer",
|
||||||
background: "transparent",
|
}}
|
||||||
border: "none",
|
className="bi bi-x-circle-fill"
|
||||||
fontSize: "1.5rem",
|
/>
|
||||||
cursor: "pointer",
|
</button>
|
||||||
}}
|
</div>
|
||||||
>
|
<div className="absolute top-2 right-28">
|
||||||
<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>
|
|
||||||
<Listbox
|
<Listbox
|
||||||
value={chartTitle}
|
value={chartTitle}
|
||||||
onChange={(value: "Messkurve" | "Meldungen") =>
|
onChange={(value: "Messkurve" | "Meldungen") =>
|
||||||
@@ -194,52 +152,36 @@ const IsoChartView: React.FC<IsoChartViewProps> = ({
|
|||||||
}
|
}
|
||||||
>
|
>
|
||||||
<div className="relative w-40">
|
<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">
|
<Listbox.Button className="dropdown-surface w-full flex items-center justify-between h-8">
|
||||||
<span>
|
<span className="dropdown-text-fix">{chartTitle}</span>
|
||||||
{chartTitle === "Meldungen" ? "Meldungen" : "Messkurve"}
|
<i className="bi bi-chevron-down text-sm opacity-70" />
|
||||||
</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.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) => (
|
{(["Messkurve", "Meldungen"] as const).map((option) => (
|
||||||
<Listbox.Option
|
<Listbox.Option
|
||||||
key={option}
|
key={option}
|
||||||
value={option}
|
value={option}
|
||||||
className={({
|
className={({ selected, active }) =>
|
||||||
selected,
|
`px-3 py-1.5 cursor-pointer rounded-sm m-0.5 ${
|
||||||
active,
|
|
||||||
}: {
|
|
||||||
selected: boolean;
|
|
||||||
active: boolean;
|
|
||||||
}) =>
|
|
||||||
`px-4 py-1 cursor-pointer ${
|
|
||||||
selected
|
selected
|
||||||
? "bg-littwin-blue text-white"
|
? "dropdown-option-active"
|
||||||
: active
|
: active
|
||||||
? "bg-gray-200"
|
? "dropdown-option-hover"
|
||||||
: ""
|
: ""
|
||||||
}`
|
}`
|
||||||
}
|
}
|
||||||
>
|
>
|
||||||
{option === "Meldungen" ? "Meldungen" : "Messkurve"}
|
{option}
|
||||||
</Listbox.Option>
|
</Listbox.Option>
|
||||||
))}
|
))}
|
||||||
</Listbox.Options>
|
</Listbox.Options>
|
||||||
</div>
|
</div>
|
||||||
</Listbox>
|
</Listbox>
|
||||||
</div>
|
</div>
|
||||||
|
</header>
|
||||||
|
<div className="flex flex-col flex-1 p-3 gap-3">
|
||||||
<IsoChartActionBar ref={actionBarRef} />
|
<IsoChartActionBar ref={actionBarRef} />
|
||||||
<div style={{ flex: 1, height: "90%" }}>
|
<div className="flex-1 relative">
|
||||||
{chartTitle === "Messkurve" ? (
|
{chartTitle === "Messkurve" ? (
|
||||||
<IsoMeasurementChart />
|
<IsoMeasurementChart />
|
||||||
) : (
|
) : (
|
||||||
|
|||||||
@@ -224,43 +224,47 @@ const Report: React.FC<ReportProps> = ({ moduleType, autoLoad = true }) => {
|
|||||||
gewählten Zeitraum gefunden.
|
gewählten Zeitraum gefunden.
|
||||||
</div>
|
</div>
|
||||||
) : (
|
) : (
|
||||||
<div className="flex-1 overflow-auto ">
|
<div className="flex-1 overflow-auto table-scroll-region">
|
||||||
<table className="min-w-full border text-sm">
|
<div className="data-table-wrapper">
|
||||||
<thead className="bg-gray-100 text-left sticky top-0 z-10">
|
<table className="data-table">
|
||||||
<tr>
|
<thead>
|
||||||
<th className="p-2 border">Prio</th>
|
<tr>
|
||||||
<th className="p-2 border">Zeitstempel</th>
|
<th style={{ width: "60px" }}>Prio</th>
|
||||||
<th className="p-2 border">Quelle</th>
|
<th style={{ minWidth: "180px" }}>Zeitstempel</th>
|
||||||
<th className="p-2 border">Meldung</th>
|
<th style={{ minWidth: "140px" }}>Quelle</th>
|
||||||
<th className="p-2 border">Status</th>
|
<th style={{ minWidth: "260px" }}>Meldung</th>
|
||||||
</tr>
|
<th style={{ minWidth: "120px" }}>Status</th>
|
||||||
</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>
|
|
||||||
</tr>
|
</tr>
|
||||||
))}
|
</thead>
|
||||||
</tbody>
|
<tbody>
|
||||||
</table>
|
{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>
|
</div>
|
||||||
)}
|
)}
|
||||||
|
|
||||||
|
|||||||
@@ -1,6 +1,9 @@
|
|||||||
"use client"; // KVZChartView.tsx
|
"use client"; // KVZChartView.tsx
|
||||||
|
|
||||||
import React, { useEffect } from "react";
|
import React, { useEffect } from "react";
|
||||||
|
import DateRangePicker from "@/components/common/DateRangePicker";
|
||||||
|
import { getMessagesThunk } from "@/redux/thunks/getMessagesThunk";
|
||||||
|
import { setLoading } from "@/redux/slices/kabelueberwachungChartSlice";
|
||||||
import ReactModal from "react-modal";
|
import ReactModal from "react-modal";
|
||||||
import { useDispatch, useSelector } from "react-redux";
|
import { useDispatch, useSelector } from "react-redux";
|
||||||
import { AppDispatch, RootState } from "@/redux/store";
|
import { AppDispatch, RootState } from "@/redux/store";
|
||||||
@@ -31,27 +34,31 @@ const KVZChartView: React.FC<KVZChartViewProps> = ({
|
|||||||
slotIndex,
|
slotIndex,
|
||||||
}) => {
|
}) => {
|
||||||
const dispatch = useDispatch<AppDispatch>();
|
const dispatch = useDispatch<AppDispatch>();
|
||||||
const isFullScreen = useSelector(
|
const { isFullScreen, slotNumber, vonDatum, bisDatum } = useSelector(
|
||||||
(state: RootState) => state.kabelueberwachungChartSlice.isFullScreen
|
(state: RootState) => state.kabelueberwachungChartSlice
|
||||||
);
|
);
|
||||||
const slotNumber = useSelector(
|
const { vonDatum: pickerVonDatum, bisDatum: pickerBisDatum } = useSelector(
|
||||||
(state: RootState) => state.kabelueberwachungChartSlice.slotNumber
|
(state: RootState) => state.dateRangePicker
|
||||||
);
|
);
|
||||||
|
|
||||||
// Beim Öffnen Slot setzen (damit konsistent zu anderen Modals)
|
// Beim Öffnen: Slot + Standard-Datumsbereich setzen (30 Tage) – analog zu anderen Modals
|
||||||
useEffect(() => {
|
useEffect(() => {
|
||||||
if (isOpen) {
|
if (!isOpen) return;
|
||||||
dispatch(setSlotNumber(slotIndex));
|
dispatch(setSlotNumber(slotIndex));
|
||||||
}
|
|
||||||
}, [isOpen, slotIndex, dispatch]);
|
|
||||||
|
|
||||||
const handleClose = () => {
|
|
||||||
const today = new Date();
|
const today = new Date();
|
||||||
const thirtyDaysAgo = new Date();
|
const thirtyDaysAgo = new Date();
|
||||||
thirtyDaysAgo.setDate(today.getDate() - 30);
|
thirtyDaysAgo.setDate(today.getDate() - 30);
|
||||||
const toISO = (d: Date) => d.toLocaleDateString("sv-SE");
|
const toISO = (d: Date) => d.toLocaleDateString("sv-SE");
|
||||||
|
dispatch(setVonDatum(toISO(thirtyDaysAgo)));
|
||||||
|
dispatch(setBisDatum(toISO(today)));
|
||||||
|
}, [isOpen, slotIndex, dispatch]);
|
||||||
|
|
||||||
// Zurücksetzen – entspricht Verhalten der anderen Modals
|
const handleClose = () => {
|
||||||
|
// Reset auf Default (wie andere Modals es tun)
|
||||||
|
const today = new Date();
|
||||||
|
const thirtyDaysAgo = new Date();
|
||||||
|
thirtyDaysAgo.setDate(today.getDate() - 30);
|
||||||
|
const toISO = (d: Date) => d.toLocaleDateString("sv-SE");
|
||||||
dispatch(setVonDatum(toISO(thirtyDaysAgo)));
|
dispatch(setVonDatum(toISO(thirtyDaysAgo)));
|
||||||
dispatch(setBisDatum(toISO(today)));
|
dispatch(setBisDatum(toISO(today)));
|
||||||
dispatch(setSelectedMode("DIA1"));
|
dispatch(setSelectedMode("DIA1"));
|
||||||
@@ -59,93 +66,114 @@ const KVZChartView: React.FC<KVZChartViewProps> = ({
|
|||||||
dispatch(setChartOpen(false));
|
dispatch(setChartOpen(false));
|
||||||
dispatch(setFullScreen(false));
|
dispatch(setFullScreen(false));
|
||||||
dispatch(resetBrushRange());
|
dispatch(resetBrushRange());
|
||||||
|
|
||||||
onClose();
|
onClose();
|
||||||
};
|
};
|
||||||
|
|
||||||
const toggleFullScreen = () => {
|
const toggleFullScreen = () => dispatch(setFullScreen(!isFullScreen));
|
||||||
dispatch(setFullScreen(!isFullScreen));
|
|
||||||
};
|
|
||||||
|
|
||||||
|
const handleFetchMessages = async () => {
|
||||||
|
const fromDate = pickerVonDatum ?? vonDatum;
|
||||||
|
const toDate = pickerBisDatum ?? bisDatum;
|
||||||
|
try {
|
||||||
|
dispatch(setLoading(true));
|
||||||
|
await dispatch(getMessagesThunk({ fromDate, toDate })).unwrap();
|
||||||
|
} catch (e) {
|
||||||
|
console.error("Fehler beim Laden der KVZ Meldungen", e);
|
||||||
|
} finally {
|
||||||
|
dispatch(setLoading(false));
|
||||||
|
}
|
||||||
|
};
|
||||||
return (
|
return (
|
||||||
<ReactModal
|
<ReactModal
|
||||||
isOpen={isOpen}
|
isOpen={isOpen}
|
||||||
onRequestClose={handleClose}
|
onRequestClose={handleClose}
|
||||||
ariaHideApp={false}
|
ariaHideApp={false}
|
||||||
style={{
|
style={{
|
||||||
overlay: { backgroundColor: "rgba(0, 0, 0, 0.5)" },
|
overlay: {
|
||||||
|
backgroundColor: "rgba(0,0,0,0.55)",
|
||||||
|
backdropFilter: "blur(2px)",
|
||||||
|
},
|
||||||
content: {
|
content: {
|
||||||
top: "50%",
|
inset: "50% auto auto 50%",
|
||||||
left: "50%",
|
|
||||||
bottom: "auto",
|
|
||||||
marginRight: "-50%",
|
|
||||||
transform: "translate(-50%, -50%)",
|
transform: "translate(-50%, -50%)",
|
||||||
width: isFullScreen ? "90vw" : "50rem",
|
width: isFullScreen ? "90vw" : "72rem",
|
||||||
height: isFullScreen ? "90vh" : "28rem",
|
height: isFullScreen ? "90vh" : "38rem",
|
||||||
padding: "1rem",
|
padding: 0,
|
||||||
transition: "all 0.3s ease-in-out",
|
border: "1px solid var(--color-border)",
|
||||||
|
background: "var(--color-surface)",
|
||||||
|
borderRadius: "14px",
|
||||||
display: "flex",
|
display: "flex",
|
||||||
flexDirection: "column",
|
flexDirection: "column",
|
||||||
|
overflow: "hidden",
|
||||||
},
|
},
|
||||||
}}
|
}}
|
||||||
|
contentLabel="KVZ Zustände & Meldungen"
|
||||||
>
|
>
|
||||||
{/* Action Buttons */}
|
<header className="modal-header relative pr-32">
|
||||||
<div
|
<h3 className="text-sm font-semibold tracking-wide">
|
||||||
style={{
|
KVZ Zustände & Meldungen
|
||||||
position: "absolute",
|
</h3>
|
||||||
top: "0.625rem",
|
<div className="absolute top-2 right-2 flex gap-2">
|
||||||
right: "0.625rem",
|
<button
|
||||||
display: "flex",
|
onClick={toggleFullScreen}
|
||||||
gap: "0.75rem",
|
className="icon-btn"
|
||||||
}}
|
aria-label={isFullScreen ? "Vollbild verlassen" : "Vollbild"}
|
||||||
>
|
type="button"
|
||||||
<button
|
>
|
||||||
onClick={toggleFullScreen}
|
<i
|
||||||
style={{
|
className={
|
||||||
background: "transparent",
|
isFullScreen
|
||||||
border: "none",
|
? "bi bi-fullscreen-exit"
|
||||||
fontSize: "1.5rem",
|
: "bi bi-arrows-fullscreen"
|
||||||
cursor: "pointer",
|
}
|
||||||
}}
|
/>
|
||||||
>
|
</button>
|
||||||
<i
|
<button
|
||||||
className={
|
onClick={handleClose}
|
||||||
isFullScreen ? "bi bi-fullscreen-exit" : "bi bi-arrows-fullscreen"
|
className="icon-btn"
|
||||||
}
|
aria-label="Schließen"
|
||||||
></i>
|
type="button"
|
||||||
</button>
|
>
|
||||||
<button
|
<i
|
||||||
onClick={handleClose}
|
style={{
|
||||||
style={{
|
background: "transparent",
|
||||||
background: "transparent",
|
border: "none",
|
||||||
border: "none",
|
fontSize: "1.5rem",
|
||||||
fontSize: "1.5rem",
|
cursor: "pointer",
|
||||||
cursor: "pointer",
|
}}
|
||||||
}}
|
className="bi bi-x-circle-fill"
|
||||||
>
|
/>
|
||||||
<i className="bi bi-x-circle-fill"></i>
|
</button>
|
||||||
</button>
|
</div>
|
||||||
</div>
|
</header>
|
||||||
|
<div className="flex flex-col flex-1 p-3 gap-3">
|
||||||
{/* Content */}
|
{/* Toolbar */}
|
||||||
<div className="flex flex-col h-full">
|
<div className="w-full flex flex-wrap items-center gap-4">
|
||||||
<h3 className="text-lg font-semibold mb-1">KVz Zustände & Meldungen</h3>
|
<div className="flex items-center gap-2">
|
||||||
|
<span className="text-xs font-semibold opacity-80 select-none text-fg-secondary">
|
||||||
{/* LED Bereich */}
|
|
||||||
<div className="w-full flex justify-between mb-4">
|
|
||||||
<div className="flex items-center">
|
|
||||||
<label className="text-sm font-semibold">
|
|
||||||
KÜ {slotNumber !== null ? slotNumber + 1 : "-"}
|
KÜ {slotNumber !== null ? slotNumber + 1 : "-"}
|
||||||
</label>
|
</span>
|
||||||
</div>
|
</div>
|
||||||
<div style={{ width: "12rem" }}>
|
<div className="flex items-center gap-3 flex-1 min-w-[20rem]">
|
||||||
|
<div className="relative z-[1500]">
|
||||||
|
<DateRangePicker />
|
||||||
|
</div>
|
||||||
|
<button
|
||||||
|
type="button"
|
||||||
|
onClick={handleFetchMessages}
|
||||||
|
className="btn-primary h-8 font-medium px-4"
|
||||||
|
>
|
||||||
|
Anzeigen
|
||||||
|
</button>
|
||||||
|
</div>
|
||||||
|
<div className="flex items-center justify-end w-48">
|
||||||
<FallSensors slotIndex={slotIndex} />
|
<FallSensors slotIndex={slotIndex} />
|
||||||
</div>
|
</div>
|
||||||
<div></div>
|
|
||||||
</div>
|
</div>
|
||||||
{/* Meldungen Bereich */}
|
<div className="flex-1 relative border border-base rounded bg-[var(--color-surface-alt)] text-fg overflow-hidden p-2">
|
||||||
<div className="flex-1 border rounded bg-white overflow-hidden">
|
<div className="w-full h-full rounded bg-[var(--color-surface)] overflow-hidden">
|
||||||
<Report moduleType="KVZ" />
|
<Report moduleType="KVZ" />
|
||||||
|
</div>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
</ReactModal>
|
</ReactModal>
|
||||||
|
|||||||
@@ -281,26 +281,24 @@ const LoopChartActionBar = forwardRef((_props, ref) => {
|
|||||||
handleFetchData,
|
handleFetchData,
|
||||||
}));
|
}));
|
||||||
|
|
||||||
|
// Sichtbarkeits-Flags
|
||||||
|
const isMesskurve = chartTitle === "Messkurve";
|
||||||
|
const isMeldungen = chartTitle === "Meldungen";
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<div className="flex justify-between p-1 bg-gray-100 rounded-lg ">
|
<div className="toolbar w-full flex flex-wrap items-center gap-2">
|
||||||
<div className="flex items-center">
|
<div className="flex items-center mr-2 min-w-[4rem]">
|
||||||
<label className="text-sm font-semibold">
|
<span className="text-xs font-semibold opacity-80 select-none">
|
||||||
KÜ {slotNumber !== null ? slotNumber + 1 : "-"}
|
KÜ {slotNumber !== null ? slotNumber + 1 : "-"}
|
||||||
</label>
|
</span>
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
<div className="flex items-center space-x-2">
|
<div className="flex items-center gap-3 flex-1 justify-end">
|
||||||
{/* DateRangePicker – für beide Ansichten sichtbar */}
|
{/* DateRangePicker immer sichtbar */}
|
||||||
<div>
|
<DateRangePicker />
|
||||||
<DateRangePicker compact />
|
|
||||||
</div>
|
|
||||||
|
|
||||||
{/* DIA0/DIA1/DIA2 Dropdown – nur sichtbar bei Messkurve */}
|
{/* Modus-Dropdown nur für Messkurve */}
|
||||||
<div
|
<div className={isMesskurve ? "" : "hidden"}>
|
||||||
style={{
|
|
||||||
visibility: chartTitle === "Messkurve" ? "visible" : "hidden",
|
|
||||||
}}
|
|
||||||
>
|
|
||||||
<Listbox
|
<Listbox
|
||||||
value={selectedMode}
|
value={selectedMode}
|
||||||
onChange={(value) => {
|
onChange={(value) => {
|
||||||
@@ -309,39 +307,29 @@ const LoopChartActionBar = forwardRef((_props, ref) => {
|
|||||||
}}
|
}}
|
||||||
>
|
>
|
||||||
<div className="relative w-48">
|
<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">
|
<Listbox.Button className="dropdown-surface w-full flex items-center justify-between">
|
||||||
<span>
|
<span className="dropdown-text-fix">
|
||||||
{
|
{
|
||||||
{
|
{
|
||||||
DIA0: "Alle Messwerte",
|
DIA0: "Alle Messwerte",
|
||||||
DIA1: "Stündliche Werte",
|
DIA1: "Stündlich",
|
||||||
DIA2: "Tägliche Werte",
|
DIA2: "Täglich",
|
||||||
}[selectedMode]
|
}[selectedMode]
|
||||||
}
|
}
|
||||||
</span>
|
</span>
|
||||||
<svg
|
<i className="bi bi-chevron-down opacity-70" />
|
||||||
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.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) => (
|
{["DIA0", "DIA1", "DIA2"].map((mode) => (
|
||||||
<Listbox.Option
|
<Listbox.Option
|
||||||
key={mode}
|
key={mode}
|
||||||
value={mode}
|
value={mode}
|
||||||
className={({ selected, active }) =>
|
className={({ selected, active }) =>
|
||||||
`px-4 py-1 cursor-pointer ${
|
`px-3 py-1.5 cursor-pointer rounded-sm m-0.5 ${
|
||||||
selected
|
selected
|
||||||
? "bg-littwin-blue text-white"
|
? "dropdown-option-active"
|
||||||
: active
|
: active
|
||||||
? "bg-gray-200"
|
? "dropdown-option-hover"
|
||||||
: ""
|
: ""
|
||||||
}`
|
}`
|
||||||
}
|
}
|
||||||
@@ -349,8 +337,8 @@ const LoopChartActionBar = forwardRef((_props, ref) => {
|
|||||||
{
|
{
|
||||||
{
|
{
|
||||||
DIA0: "Alle Messwerte",
|
DIA0: "Alle Messwerte",
|
||||||
DIA1: "Stündliche Werte",
|
DIA1: "Stündlich",
|
||||||
DIA2: "Tägliche Werte",
|
DIA2: "Täglich",
|
||||||
}[mode]
|
}[mode]
|
||||||
}
|
}
|
||||||
</Listbox.Option>
|
</Listbox.Option>
|
||||||
@@ -359,41 +347,50 @@ const LoopChartActionBar = forwardRef((_props, ref) => {
|
|||||||
</div>
|
</div>
|
||||||
</Listbox>
|
</Listbox>
|
||||||
</div>
|
</div>
|
||||||
{/* Dropdown für Messkurve / Meldungen in View-Header umgezogen */}
|
|
||||||
|
|
||||||
{/* Buttons – nur sichtbar bei Messkurve, Platz bleibt erhalten */}
|
{/* Buttons */}
|
||||||
<div
|
{isMesskurve && (
|
||||||
style={{
|
<div className="flex items-center gap-2">
|
||||||
visibility: chartTitle === "Messkurve" ? "visible" : "hidden",
|
<button
|
||||||
}}
|
onClick={handleStartRSL}
|
||||||
className="flex items-center space-x-2"
|
className="btn-primary h-8 font-medium px-3"
|
||||||
>
|
disabled={isLoading || rslRunning}
|
||||||
<button
|
type="button"
|
||||||
onClick={handleStartRSL}
|
>
|
||||||
className="px-4 py-1 bg-littwin-blue text-white rounded text-sm whitespace-nowrap"
|
{rslRunning ? "RSL läuft…" : "RSL Messung starten"}
|
||||||
disabled={isLoading || rslRunning}
|
</button>
|
||||||
>
|
<button
|
||||||
{rslRunning ? "RSL läuft..." : "RSL Messung starten"}
|
onClick={handleFetchData}
|
||||||
</button>
|
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="px-4 py-1 bg-littwin-blue text-white rounded text-sm whitespace-nowrap"
|
className="btn-primary h-8 font-medium px-4"
|
||||||
disabled={rslRunning}
|
disabled={rslRunning}
|
||||||
|
type="button"
|
||||||
>
|
>
|
||||||
Daten laden
|
Anzeigen
|
||||||
</button>
|
</button>
|
||||||
</div>
|
)}
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
{rslRunning && (
|
{rslRunning && (
|
||||||
<div className="fixed inset-0 z-[1000] flex flex-col items-center justify-center bg-white/80 backdrop-blur-sm">
|
<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">
|
<div className="mb-4 text-center space-y-1">
|
||||||
<p className="text-lg font-semibold">RSL Messung läuft</p>
|
<p className="text-sm font-semibold">RSL Messung läuft</p>
|
||||||
<p className="text-sm text-gray-700">
|
<p className="text-xs opacity-80">
|
||||||
Bitte warten…{" "}
|
Bitte warten…{" "}
|
||||||
{Math.min(100, Math.round((rslProgress / TOTAL_DURATION) * 100))}%
|
{Math.min(100, Math.round((rslProgress / TOTAL_DURATION) * 100))}%
|
||||||
</p>
|
</p>
|
||||||
</div>
|
</div>
|
||||||
<div className="w-2/3 max-w-xl h-4 bg-gray-200 rounded overflow-hidden shadow-inner">
|
<div className="w-2/3 max-w-xl h-3 bg-[var(--color-border)] rounded overflow-hidden shadow-inner">
|
||||||
<div
|
<div
|
||||||
className="h-full bg-littwin-blue transition-all ease-linear"
|
className="h-full bg-littwin-blue transition-all ease-linear"
|
||||||
style={{ width: `${(rslProgress / TOTAL_DURATION) * 100}%` }}
|
style={{ width: `${(rslProgress / TOTAL_DURATION) * 100}%` }}
|
||||||
|
|||||||
@@ -1,31 +1,24 @@
|
|||||||
"use client"; // LoopChartView.tsx
|
"use client"; // LoopChartView.tsx
|
||||||
|
|
||||||
import React, { useEffect } from "react";
|
import React, { useEffect, useRef } from "react";
|
||||||
import { Listbox } from "@headlessui/react";
|
import { Listbox } from "@headlessui/react";
|
||||||
import ReactModal from "react-modal";
|
import ReactModal from "react-modal";
|
||||||
import LoopMeasurementChart from "./LoopMeasurementChart";
|
import LoopMeasurementChart from "./LoopMeasurementChart";
|
||||||
import Report from "../IsoMeasurementChart/Report";
|
import Report from "../IsoMeasurementChart/Report";
|
||||||
import LoopChartActionBar from "./LoopChartActionBar";
|
import LoopChartActionBar from "./LoopChartActionBar";
|
||||||
import { useRef } from "react";
|
|
||||||
import { useSelector, useDispatch } from "react-redux";
|
import { useSelector, useDispatch } from "react-redux";
|
||||||
import { AppDispatch } from "@/redux/store";
|
import { AppDispatch, RootState } from "@/redux/store";
|
||||||
import { RootState } from "@/redux/store";
|
|
||||||
import {
|
import {
|
||||||
setChartOpen,
|
setChartOpen,
|
||||||
setFullScreen,
|
setFullScreen,
|
||||||
setSlotNumber,
|
setSlotNumber,
|
||||||
} from "@/redux/slices/kabelueberwachungChartSlice";
|
|
||||||
import { setChartTitle as setLoopChartTitle } from "@/redux/slices/loopChartTypeSlice";
|
|
||||||
|
|
||||||
import { resetBrushRange } from "@/redux/slices/brushSlice";
|
|
||||||
import { useLoopChartLoader } from "./LoopChartActionBar";
|
|
||||||
|
|
||||||
import {
|
|
||||||
setVonDatum,
|
setVonDatum,
|
||||||
setBisDatum,
|
setBisDatum,
|
||||||
setSelectedMode,
|
setSelectedMode,
|
||||||
setSelectedSlotType,
|
setSelectedSlotType,
|
||||||
} from "@/redux/slices/kabelueberwachungChartSlice";
|
} from "@/redux/slices/kabelueberwachungChartSlice";
|
||||||
|
import { setChartTitle as setLoopChartTitle } from "@/redux/slices/loopChartTypeSlice";
|
||||||
|
import { resetBrushRange } from "@/redux/slices/brushSlice";
|
||||||
import { resetDateRange } from "@/redux/slices/dateRangePickerSlice";
|
import { resetDateRange } from "@/redux/slices/dateRangePickerSlice";
|
||||||
|
|
||||||
interface LoopChartViewProps {
|
interface LoopChartViewProps {
|
||||||
@@ -34,11 +27,7 @@ interface LoopChartViewProps {
|
|||||||
slotIndex: number;
|
slotIndex: number;
|
||||||
}
|
}
|
||||||
|
|
||||||
const LoopChartView: React.FC<LoopChartViewProps> = ({
|
function LoopChartView({ isOpen, onClose, slotIndex }: LoopChartViewProps) {
|
||||||
isOpen,
|
|
||||||
onClose,
|
|
||||||
slotIndex,
|
|
||||||
}) => {
|
|
||||||
const dispatch = useDispatch<AppDispatch>();
|
const dispatch = useDispatch<AppDispatch>();
|
||||||
const chartTitle = useSelector(
|
const chartTitle = useSelector(
|
||||||
(state: RootState) => state.loopChartType.chartTitle
|
(state: RootState) => state.loopChartType.chartTitle
|
||||||
@@ -48,9 +37,6 @@ const LoopChartView: React.FC<LoopChartViewProps> = ({
|
|||||||
(state: RootState) => state.kabelueberwachungChartSlice.isFullScreen
|
(state: RootState) => state.kabelueberwachungChartSlice.isFullScreen
|
||||||
);
|
);
|
||||||
|
|
||||||
// useLoopChartLoader hook
|
|
||||||
const loadLoopChartData = useLoopChartLoader();
|
|
||||||
|
|
||||||
// slotNumber nicht direkt benötigt – wird intern über Redux genutzt
|
// slotNumber nicht direkt benötigt – wird intern über Redux genutzt
|
||||||
|
|
||||||
// **Modal schließen + Redux-Status zurücksetzen**
|
// **Modal schließen + Redux-Status zurücksetzen**
|
||||||
@@ -58,64 +44,35 @@ const LoopChartView: React.FC<LoopChartViewProps> = ({
|
|||||||
const today = new Date();
|
const today = new Date();
|
||||||
const thirtyDaysAgo = new Date();
|
const thirtyDaysAgo = new Date();
|
||||||
thirtyDaysAgo.setDate(today.getDate() - 30);
|
thirtyDaysAgo.setDate(today.getDate() - 30);
|
||||||
|
|
||||||
const toISO = (date: Date) => date.toLocaleDateString("sv-SE");
|
const toISO = (date: Date) => date.toLocaleDateString("sv-SE");
|
||||||
|
|
||||||
// Reset Datum
|
|
||||||
dispatch(setVonDatum(toISO(thirtyDaysAgo)));
|
dispatch(setVonDatum(toISO(thirtyDaysAgo)));
|
||||||
dispatch(setBisDatum(toISO(today)));
|
dispatch(setBisDatum(toISO(today)));
|
||||||
|
|
||||||
// Reset DateRangePicker
|
|
||||||
dispatch(resetDateRange());
|
dispatch(resetDateRange());
|
||||||
|
dispatch(setSelectedMode("DIA0"));
|
||||||
// Reset Dropdowns
|
|
||||||
dispatch(setSelectedMode("DIA0")); // Reset to Alle Messwerte
|
|
||||||
dispatch(setSelectedSlotType("schleifenwiderstand"));
|
dispatch(setSelectedSlotType("schleifenwiderstand"));
|
||||||
|
|
||||||
// Sonstiges Reset
|
|
||||||
dispatch(setChartOpen(false));
|
dispatch(setChartOpen(false));
|
||||||
dispatch(setFullScreen(false));
|
dispatch(setFullScreen(false));
|
||||||
dispatch(resetBrushRange());
|
dispatch(resetBrushRange());
|
||||||
|
|
||||||
onClose();
|
onClose();
|
||||||
};
|
};
|
||||||
|
|
||||||
// **Vollbildmodus umschalten**
|
const toggleFullScreen = () => dispatch(setFullScreen(!isFullScreen));
|
||||||
const toggleFullScreen = () => {
|
|
||||||
dispatch(setFullScreen(!isFullScreen));
|
|
||||||
};
|
|
||||||
|
|
||||||
// Modal öffnen - RSL spezifische Einstellungen
|
|
||||||
const actionBarRef = useRef<{ handleFetchData: () => void }>(null);
|
const actionBarRef = useRef<{ handleFetchData: () => void }>(null);
|
||||||
useEffect(() => {
|
useEffect(() => {
|
||||||
if (isOpen) {
|
if (isOpen) {
|
||||||
const today = new Date();
|
const today = new Date();
|
||||||
const thirtyDaysAgo = new Date();
|
const thirtyDaysAgo = new Date();
|
||||||
thirtyDaysAgo.setDate(today.getDate() - 30);
|
thirtyDaysAgo.setDate(today.getDate() - 30);
|
||||||
|
|
||||||
const toISO = (date: Date) => date.toLocaleDateString("sv-SE");
|
const toISO = (date: Date) => date.toLocaleDateString("sv-SE");
|
||||||
|
|
||||||
// Set slot number first
|
|
||||||
dispatch(setSlotNumber(slotIndex));
|
dispatch(setSlotNumber(slotIndex));
|
||||||
|
|
||||||
// Set dates
|
|
||||||
dispatch(setVonDatum(toISO(thirtyDaysAgo)));
|
dispatch(setVonDatum(toISO(thirtyDaysAgo)));
|
||||||
dispatch(setBisDatum(toISO(today)));
|
dispatch(setBisDatum(toISO(today)));
|
||||||
|
|
||||||
// Set RSL specific settings
|
|
||||||
dispatch(setSelectedSlotType("schleifenwiderstand"));
|
dispatch(setSelectedSlotType("schleifenwiderstand"));
|
||||||
dispatch(setSelectedMode("DIA0")); // Set to Alle Messwerte on open
|
dispatch(setSelectedMode("DIA0"));
|
||||||
|
const t = setTimeout(() => actionBarRef.current?.handleFetchData(), 120);
|
||||||
// Automatisch Daten laden wie Button-Klick
|
return () => clearTimeout(t);
|
||||||
const timer = setTimeout(() => {
|
|
||||||
actionBarRef.current?.handleFetchData();
|
|
||||||
}, 120);
|
|
||||||
|
|
||||||
// Cleanup timer
|
|
||||||
return () => clearTimeout(timer);
|
|
||||||
}
|
}
|
||||||
//ESLint ignore
|
|
||||||
// eslint-disable-next-line react-hooks/exhaustive-deps
|
|
||||||
}, [isOpen, slotIndex, dispatch]);
|
}, [isOpen, slotIndex, dispatch]);
|
||||||
|
|
||||||
return (
|
return (
|
||||||
@@ -124,76 +81,63 @@ const LoopChartView: React.FC<LoopChartViewProps> = ({
|
|||||||
onRequestClose={handleClose}
|
onRequestClose={handleClose}
|
||||||
ariaHideApp={false}
|
ariaHideApp={false}
|
||||||
style={{
|
style={{
|
||||||
overlay: { backgroundColor: "rgba(0, 0, 0, 0.5)" },
|
overlay: {
|
||||||
|
backgroundColor: "rgba(0,0,0,0.55)",
|
||||||
|
backdropFilter: "blur(2px)",
|
||||||
|
},
|
||||||
content: {
|
content: {
|
||||||
top: "50%",
|
inset: "50% auto auto 50%",
|
||||||
left: "50%",
|
|
||||||
bottom: "auto",
|
|
||||||
marginRight: "-50%",
|
|
||||||
transform: "translate(-50%, -50%)",
|
transform: "translate(-50%, -50%)",
|
||||||
width: isFullScreen ? "90vw" : "70rem",
|
width: isFullScreen ? "90vw" : "72rem",
|
||||||
height: isFullScreen ? "90vh" : "35rem",
|
height: isFullScreen ? "90vh" : "38rem",
|
||||||
padding: "1rem",
|
padding: 0,
|
||||||
transition: "all 0.3s ease-in-out",
|
border: "1px solid var(--color-border)",
|
||||||
|
background: "var(--color-surface)",
|
||||||
|
borderRadius: "14px",
|
||||||
display: "flex",
|
display: "flex",
|
||||||
flexDirection: "column",
|
flexDirection: "column",
|
||||||
|
overflow: "hidden",
|
||||||
},
|
},
|
||||||
}}
|
}}
|
||||||
|
contentLabel="Schleifenwiderstand"
|
||||||
>
|
>
|
||||||
{/* Action-Buttons */}
|
<header className="modal-header relative pr-56">
|
||||||
<div
|
<h3 className="text-sm font-semibold tracking-wide">
|
||||||
style={{
|
Schleifenwiderstand
|
||||||
position: "absolute",
|
</h3>
|
||||||
top: "0.625rem",
|
<div className="absolute top-2 right-2 flex gap-2">
|
||||||
right: "0.625rem",
|
<button
|
||||||
display: "flex",
|
onClick={toggleFullScreen}
|
||||||
gap: "0.75rem",
|
className="icon-btn"
|
||||||
}}
|
aria-label={isFullScreen ? "Vollbild verlassen" : "Vollbild"}
|
||||||
>
|
type="button"
|
||||||
{/* Fullscreen-Button */}
|
>
|
||||||
<button
|
<i
|
||||||
onClick={toggleFullScreen}
|
className={
|
||||||
style={{
|
isFullScreen
|
||||||
background: "transparent",
|
? "bi bi-fullscreen-exit"
|
||||||
border: "none",
|
: "bi bi-arrows-fullscreen"
|
||||||
fontSize: "1.5rem",
|
}
|
||||||
cursor: "pointer",
|
/>
|
||||||
}}
|
</button>
|
||||||
>
|
<button
|
||||||
<i
|
onClick={handleClose}
|
||||||
className={
|
className="icon-btn"
|
||||||
isFullScreen ? "bi bi-fullscreen-exit" : "bi bi-arrows-fullscreen"
|
aria-label="Schließen"
|
||||||
}
|
type="button"
|
||||||
></i>
|
>
|
||||||
</button>
|
<i
|
||||||
|
style={{
|
||||||
{/* Schließen-Button */}
|
background: "transparent",
|
||||||
<button
|
border: "none",
|
||||||
onClick={handleClose}
|
fontSize: "1.5rem",
|
||||||
style={{
|
cursor: "pointer",
|
||||||
background: "transparent",
|
}}
|
||||||
border: "none",
|
className="bi bi-x-circle-fill"
|
||||||
fontSize: "1.5rem",
|
/>
|
||||||
cursor: "pointer",
|
</button>
|
||||||
}}
|
</div>
|
||||||
>
|
<div className="absolute top-2 right-28">
|
||||||
<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">
|
|
||||||
{chartTitle === "Messkurve" ? "Schleifenwiderstand" : "Meldungen"}
|
|
||||||
</h3>
|
|
||||||
<Listbox
|
<Listbox
|
||||||
value={chartTitle}
|
value={chartTitle}
|
||||||
onChange={(value: "Messkurve" | "Meldungen") =>
|
onChange={(value: "Messkurve" | "Meldungen") =>
|
||||||
@@ -201,37 +145,21 @@ const LoopChartView: React.FC<LoopChartViewProps> = ({
|
|||||||
}
|
}
|
||||||
>
|
>
|
||||||
<div className="relative w-40">
|
<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">
|
<Listbox.Button className="dropdown-surface w-full flex items-center justify-between h-8">
|
||||||
<span>{chartTitle}</span>
|
<span className="dropdown-text-fix">{chartTitle}</span>
|
||||||
<svg
|
<i className="bi bi-chevron-down text-sm opacity-70" />
|
||||||
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.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) => (
|
{(["Messkurve", "Meldungen"] as const).map((option) => (
|
||||||
<Listbox.Option
|
<Listbox.Option
|
||||||
key={option}
|
key={option}
|
||||||
value={option}
|
value={option}
|
||||||
className={({
|
className={({ selected, active }) =>
|
||||||
selected,
|
`px-3 py-1.5 cursor-pointer rounded-sm m-0.5 ${
|
||||||
active,
|
|
||||||
}: {
|
|
||||||
selected: boolean;
|
|
||||||
active: boolean;
|
|
||||||
}) =>
|
|
||||||
`px-4 py-1 cursor-pointer ${
|
|
||||||
selected
|
selected
|
||||||
? "bg-littwin-blue text-white"
|
? "dropdown-option-active"
|
||||||
: active
|
: active
|
||||||
? "bg-gray-200"
|
? "dropdown-option-hover"
|
||||||
: ""
|
: ""
|
||||||
}`
|
}`
|
||||||
}
|
}
|
||||||
@@ -243,17 +171,19 @@ const LoopChartView: React.FC<LoopChartViewProps> = ({
|
|||||||
</div>
|
</div>
|
||||||
</Listbox>
|
</Listbox>
|
||||||
</div>
|
</div>
|
||||||
|
</header>
|
||||||
|
<div className="flex flex-col flex-1 p-3 gap-3">
|
||||||
<LoopChartActionBar ref={actionBarRef} />
|
<LoopChartActionBar ref={actionBarRef} />
|
||||||
<div style={{ flex: 1, height: "90%" }}>
|
<div className="flex-1 relative">
|
||||||
{chartTitle === "Messkurve" ? (
|
{chartTitle === "Messkurve" ? (
|
||||||
<LoopMeasurementChart />
|
<LoopMeasurementChart />
|
||||||
) : (
|
) : (
|
||||||
<Report moduleType="RSL" autoLoad={false} />
|
<Report moduleType="RSL" autoLoad={chartTitle === "Meldungen"} />
|
||||||
)}
|
)}
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
</ReactModal>
|
</ReactModal>
|
||||||
);
|
);
|
||||||
};
|
}
|
||||||
|
|
||||||
export default LoopChartView;
|
export default LoopChartView;
|
||||||
|
|||||||
@@ -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 }) => {
|
||||||
@@ -213,8 +212,6 @@ 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">
|
||||||
⚠️ Keine Daten verfügbar für diesen Slot
|
⚠️ Keine Daten verfügbar für diesen Slot
|
||||||
|
|||||||
@@ -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));
|
||||||
}
|
}
|
||||||
@@ -165,139 +153,158 @@ const TDRChartActionBar: React.FC = () => {
|
|||||||
|
|
||||||
return (
|
return (
|
||||||
<>
|
<>
|
||||||
<div className="flex justify-between items-center p-2 bg-gray-100 rounded-lg space-x-4">
|
<div className="toolbar w-full justify-between flex-wrap">
|
||||||
{/* 🧩 Slot-Anzeige (1-basiert für Benutzer) */}
|
{/* KÜ number left, controls right, like IsoChartActionBar */}
|
||||||
<div className="text-sm font-semibold">
|
<div className="flex items-center gap-2 pr-4">
|
||||||
{selectedSlot !== null ? `KÜ ${selectedSlot + 1}` : "Kein KÜ gewählt"}
|
<span className="font-semibold uppercase tracking-wide text-muted">
|
||||||
|
KÜ
|
||||||
|
</span>
|
||||||
|
<span className="font-medium px-2 py-0.5 rounded bg-surface-alt border border-base min-w-[3rem] text-center">
|
||||||
|
{selectedSlot !== null ? selectedSlot + 1 : "-"}
|
||||||
|
</span>
|
||||||
</div>
|
</div>
|
||||||
|
<div className="flex items-center gap-3 flex-1 justify-end">
|
||||||
{/* ✅ Referenz setzen */}
|
{isMeldungen ? (
|
||||||
{selectedId !== null && (
|
<>
|
||||||
<button
|
<DateRangePicker />
|
||||||
onClick={handleSetReference}
|
<button
|
||||||
className="border border-littwin-blue text-littwin-blue bg-white rounded px-3 py-1 text-sm hover:bg-gray-200"
|
type="button"
|
||||||
>
|
onClick={handleFetchMessages}
|
||||||
TDR-Kurve als Referenz speichern
|
className="btn-primary h-8 font-medium px-4"
|
||||||
</button>
|
disabled={selectedSlot === null}
|
||||||
)}
|
>
|
||||||
|
Anzeigen
|
||||||
{/* 🚀 TDR starten */}
|
</button>
|
||||||
<button
|
</>
|
||||||
onClick={handleStartTDR}
|
) : (
|
||||||
className="px-4 py-1 bg-littwin-blue text-white rounded text-sm whitespace-nowrap "
|
<>
|
||||||
disabled={selectedSlot === null || tdrRunning}
|
{selectedId !== null && (
|
||||||
>
|
<button
|
||||||
{tdrRunning
|
onClick={handleSetReference}
|
||||||
? `TDR läuft... (${Math.min(
|
type="button"
|
||||||
100,
|
className="btn-primary h-8 px-3 font-medium"
|
||||||
Math.round((tdrProgress / TDR_TOTAL_DURATION) * 100)
|
disabled={selectedSlot === null}
|
||||||
)}%)`
|
|
||||||
: "TDR-Messung starten"}
|
|
||||||
</button>
|
|
||||||
|
|
||||||
{/* 🔽 Dropdown für Messungen */}
|
|
||||||
<div className="flex items-center space-x-2">
|
|
||||||
<Listbox
|
|
||||||
value={selectedId}
|
|
||||||
onChange={(id) => {
|
|
||||||
setSelectedId(id);
|
|
||||||
if (id !== null) {
|
|
||||||
dispatch(getTDRChartDataByIdThunk(id));
|
|
||||||
}
|
|
||||||
}}
|
|
||||||
disabled={idsForSlot.length === 0}
|
|
||||||
>
|
|
||||||
<div className="relative w-96">
|
|
||||||
<Listbox.Button className="w-full border px-2 py-1 rounded text-left bg-white flex justify-between items-center text-sm">
|
|
||||||
<span className="whitespace-nowrap overflow-hidden text-ellipsis">
|
|
||||||
{selectedId
|
|
||||||
? (() => {
|
|
||||||
const selected = idsForSlot.find(
|
|
||||||
(e) => e.id === selectedId
|
|
||||||
);
|
|
||||||
return selected
|
|
||||||
? `${new Date(selected.t).toLocaleString("de-DE", {
|
|
||||||
day: "2-digit",
|
|
||||||
month: "2-digit",
|
|
||||||
year: "numeric",
|
|
||||||
hour: "2-digit",
|
|
||||||
minute: "2-digit",
|
|
||||||
second: "2-digit",
|
|
||||||
})} – Fehlerstelle: ${selected.d} m`
|
|
||||||
: "Wähle Messung";
|
|
||||||
})()
|
|
||||||
: "Wähle Messung"}
|
|
||||||
</span>
|
|
||||||
<svg
|
|
||||||
className="w-5 h-5 text-gray-400"
|
|
||||||
xmlns="http://www.w3.org/2000/svg"
|
|
||||||
viewBox="0 0 20 20"
|
|
||||||
fill="currentColor"
|
|
||||||
aria-hidden="true"
|
|
||||||
>
|
>
|
||||||
<path
|
TDR-Kurve als Referenz speichern
|
||||||
fillRule="evenodd"
|
</button>
|
||||||
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">
|
|
||||||
{idsForSlot.map((entry) => (
|
|
||||||
<Listbox.Option
|
|
||||||
key={entry.id}
|
|
||||||
value={entry.id}
|
|
||||||
className={({ selected, active }) =>
|
|
||||||
`px-4 py-1 cursor-pointer whitespace-nowrap overflow-hidden text-ellipsis ${
|
|
||||||
selected
|
|
||||||
? "bg-littwin-blue text-white"
|
|
||||||
: active
|
|
||||||
? "bg-gray-200"
|
|
||||||
: ""
|
|
||||||
}`
|
|
||||||
}
|
|
||||||
>
|
|
||||||
{new Date(entry.t).toLocaleString("de-DE", {
|
|
||||||
day: "2-digit",
|
|
||||||
month: "2-digit",
|
|
||||||
year: "numeric",
|
|
||||||
hour: "2-digit",
|
|
||||||
minute: "2-digit",
|
|
||||||
second: "2-digit",
|
|
||||||
})}{" "}
|
|
||||||
– Fehlerstelle: {entry.d} m
|
|
||||||
</Listbox.Option>
|
|
||||||
))}
|
|
||||||
</Listbox.Options>
|
|
||||||
</div>
|
|
||||||
</Listbox>
|
|
||||||
</div>
|
|
||||||
</div>
|
|
||||||
{tdrRunning && (
|
|
||||||
<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">
|
|
||||||
TDR Messung läuft... kann bis zu zwei Minuten dauern
|
|
||||||
</p>
|
|
||||||
<p className="text-sm text-gray-700">
|
|
||||||
Bitte warten…{" "}
|
|
||||||
{Math.min(
|
|
||||||
100,
|
|
||||||
Math.round((tdrProgress / TDR_TOTAL_DURATION) * 100)
|
|
||||||
)}
|
)}
|
||||||
%
|
<button
|
||||||
</p>
|
onClick={handleStartTDR}
|
||||||
</div>
|
type="button"
|
||||||
<div className="w-2/3 max-w-xl h-4 bg-gray-200 rounded overflow-hidden shadow-inner">
|
disabled={selectedSlot === null || tdrRunning}
|
||||||
<div
|
className={`btn-primary h-8 px-4 whitespace-nowrap ${
|
||||||
className="h-full bg-littwin-blue transition-all ease-linear"
|
tdrRunning ? "opacity-90" : ""
|
||||||
style={{
|
}`}
|
||||||
width: `${(tdrProgress / TDR_TOTAL_DURATION) * 100}%`,
|
>
|
||||||
}}
|
{tdrRunning
|
||||||
/>
|
? `TDR läuft... (${Math.min(
|
||||||
</div>
|
100,
|
||||||
|
Math.round((tdrProgress / TDR_TOTAL_DURATION) * 100)
|
||||||
|
)}%)`
|
||||||
|
: "TDR-Messung starten"}
|
||||||
|
</button>
|
||||||
|
<div className="ml-auto flex-1 min-w-[14rem] max-w-[30rem]">
|
||||||
|
<Listbox
|
||||||
|
value={selectedId}
|
||||||
|
onChange={(id) => {
|
||||||
|
setSelectedId(id);
|
||||||
|
if (id !== null) dispatch(getTDRChartDataByIdThunk(id));
|
||||||
|
}}
|
||||||
|
disabled={idsForSlot.length === 0}
|
||||||
|
>
|
||||||
|
<div className="relative w-full">
|
||||||
|
<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">
|
||||||
|
{selectedId
|
||||||
|
? (() => {
|
||||||
|
const selected = idsForSlot.find(
|
||||||
|
(e) => e.id === selectedId
|
||||||
|
);
|
||||||
|
return selected
|
||||||
|
? `${new Date(selected.t).toLocaleString(
|
||||||
|
"de-DE",
|
||||||
|
{
|
||||||
|
day: "2-digit",
|
||||||
|
month: "2-digit",
|
||||||
|
year: "numeric",
|
||||||
|
hour: "2-digit",
|
||||||
|
minute: "2-digit",
|
||||||
|
second: "2-digit",
|
||||||
|
}
|
||||||
|
)} – Fehlerstelle: ${selected.d} m`
|
||||||
|
: "Wähle Messung";
|
||||||
|
})()
|
||||||
|
: "Wähle Messung"}
|
||||||
|
</span>
|
||||||
|
<i className="bi bi-chevron-down text-sm opacity-70" />
|
||||||
|
</Listbox.Button>
|
||||||
|
<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">
|
||||||
|
{idsForSlot.map((entry) => {
|
||||||
|
const dateLabel = new Date(entry.t).toLocaleString(
|
||||||
|
"de-DE",
|
||||||
|
{
|
||||||
|
day: "2-digit",
|
||||||
|
month: "2-digit",
|
||||||
|
year: "numeric",
|
||||||
|
hour: "2-digit",
|
||||||
|
minute: "2-digit",
|
||||||
|
second: "2-digit",
|
||||||
|
}
|
||||||
|
);
|
||||||
|
const fullText = `${dateLabel} – Fehlerstelle: ${entry.d} m`;
|
||||||
|
return (
|
||||||
|
<Listbox.Option
|
||||||
|
key={entry.id}
|
||||||
|
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>
|
</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 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>
|
||||||
</>
|
</>
|
||||||
);
|
);
|
||||||
};
|
};
|
||||||
|
|||||||
@@ -3,30 +3,25 @@
|
|||||||
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 } from "@/redux/store";
|
import { AppDispatch, RootState } from "@/redux/store";
|
||||||
import { RootState } from "@/redux/store";
|
|
||||||
import {
|
import {
|
||||||
setChartOpen,
|
setChartOpen,
|
||||||
setFullScreen,
|
setFullScreen,
|
||||||
setSlotNumber,
|
setSlotNumber,
|
||||||
} from "@/redux/slices/kabelueberwachungChartSlice";
|
|
||||||
|
|
||||||
import { resetBrushRange } from "@/redux/slices/brushSlice";
|
|
||||||
|
|
||||||
import {
|
|
||||||
setVonDatum,
|
setVonDatum,
|
||||||
setBisDatum,
|
setBisDatum,
|
||||||
setSelectedMode,
|
setSelectedMode,
|
||||||
setSelectedSlotType,
|
setSelectedSlotType,
|
||||||
|
setChartTitle,
|
||||||
} from "@/redux/slices/kabelueberwachungChartSlice";
|
} from "@/redux/slices/kabelueberwachungChartSlice";
|
||||||
|
import { resetBrushRange } from "@/redux/slices/brushSlice";
|
||||||
import {
|
import {
|
||||||
setSelectedSlot,
|
setSelectedSlot,
|
||||||
setActiveMode,
|
setActiveMode,
|
||||||
} from "@/redux/slices/kueChartModeSlice";
|
} from "@/redux/slices/kueChartModeSlice";
|
||||||
import { Listbox } from "@headlessui/react";
|
import { Listbox } from "@headlessui/react";
|
||||||
import { setChartTitle } from "@/redux/slices/kabelueberwachungChartSlice";
|
|
||||||
import Report from "../IsoMeasurementChart/Report";
|
import Report from "../IsoMeasurementChart/Report";
|
||||||
|
|
||||||
interface TDRChartViewProps {
|
interface TDRChartViewProps {
|
||||||
@@ -41,64 +36,48 @@ const TDRChartView: React.FC<TDRChartViewProps> = ({
|
|||||||
slotIndex,
|
slotIndex,
|
||||||
}) => {
|
}) => {
|
||||||
const dispatch = useDispatch<AppDispatch>();
|
const dispatch = useDispatch<AppDispatch>();
|
||||||
|
|
||||||
const { isFullScreen, chartTitle } = useSelector(
|
const { isFullScreen, chartTitle } = useSelector(
|
||||||
(state: RootState) => state.kabelueberwachungChartSlice
|
(s: RootState) => s.kabelueberwachungChartSlice
|
||||||
);
|
);
|
||||||
|
|
||||||
// **Modal öffnen - TDR spezifische Einstellungen**
|
// Initialize defaults when opening
|
||||||
useEffect(() => {
|
useEffect(() => {
|
||||||
if (isOpen) {
|
if (!isOpen) return;
|
||||||
const today = new Date();
|
|
||||||
const thirtyDaysAgo = new Date();
|
|
||||||
thirtyDaysAgo.setDate(today.getDate() - 30);
|
|
||||||
|
|
||||||
const toISO = (date: Date) => date.toLocaleDateString("sv-SE");
|
|
||||||
|
|
||||||
// Set TDR mode and slot
|
|
||||||
dispatch(setActiveMode("TDR"));
|
|
||||||
dispatch(setSelectedSlot(slotIndex));
|
|
||||||
|
|
||||||
// Also set slot number for general chart slice
|
|
||||||
dispatch(setSlotNumber(slotIndex));
|
|
||||||
|
|
||||||
// Set dates
|
|
||||||
dispatch(setVonDatum(toISO(thirtyDaysAgo)));
|
|
||||||
dispatch(setBisDatum(toISO(today)));
|
|
||||||
|
|
||||||
// TDR specific settings (if needed)
|
|
||||||
dispatch(setSelectedSlotType("isolationswiderstand"));
|
|
||||||
}
|
|
||||||
}, [isOpen, slotIndex, dispatch]);
|
|
||||||
|
|
||||||
// **Modal schließen + Redux-Status zurücksetzen**
|
|
||||||
const handleClose = () => {
|
|
||||||
const today = new Date();
|
const today = new Date();
|
||||||
const thirtyDaysAgo = new Date();
|
const thirtyDaysAgo = new Date();
|
||||||
thirtyDaysAgo.setDate(today.getDate() - 30);
|
thirtyDaysAgo.setDate(today.getDate() - 30);
|
||||||
|
|
||||||
const toISO = (date: Date) => date.toLocaleDateString("sv-SE");
|
const toISO = (date: Date) => date.toLocaleDateString("sv-SE");
|
||||||
|
|
||||||
// Reset Datum
|
dispatch(setActiveMode("TDR"));
|
||||||
|
dispatch(setSelectedSlot(slotIndex));
|
||||||
|
dispatch(setSlotNumber(slotIndex));
|
||||||
|
|
||||||
dispatch(setVonDatum(toISO(thirtyDaysAgo)));
|
dispatch(setVonDatum(toISO(thirtyDaysAgo)));
|
||||||
dispatch(setBisDatum(toISO(today)));
|
dispatch(setBisDatum(toISO(today)));
|
||||||
|
|
||||||
// Reset Dropdowns
|
if (chartTitle !== "Messkurve" && chartTitle !== "Meldungen") {
|
||||||
|
dispatch(setChartTitle("Messkurve"));
|
||||||
|
}
|
||||||
|
// Only run when opened or slot changes or chartTitle invalid
|
||||||
|
}, [isOpen, slotIndex, chartTitle, dispatch]);
|
||||||
|
|
||||||
|
const handleClose = () => {
|
||||||
|
// Reset generic chart slice to DIA1 isolationswiderstand defaults (same pattern as other modals)
|
||||||
|
const today = new Date();
|
||||||
|
const thirtyDaysAgo = new Date();
|
||||||
|
thirtyDaysAgo.setDate(today.getDate() - 30);
|
||||||
|
const toISO = (date: Date) => date.toLocaleDateString("sv-SE");
|
||||||
|
dispatch(setVonDatum(toISO(thirtyDaysAgo)));
|
||||||
|
dispatch(setBisDatum(toISO(today)));
|
||||||
dispatch(setSelectedMode("DIA1"));
|
dispatch(setSelectedMode("DIA1"));
|
||||||
dispatch(setSelectedSlotType("isolationswiderstand"));
|
dispatch(setSelectedSlotType("isolationswiderstand"));
|
||||||
|
|
||||||
// Sonstiges Reset
|
|
||||||
dispatch(setChartOpen(false));
|
dispatch(setChartOpen(false));
|
||||||
dispatch(setFullScreen(false));
|
dispatch(setFullScreen(false));
|
||||||
dispatch(resetBrushRange());
|
dispatch(resetBrushRange());
|
||||||
|
|
||||||
onClose();
|
onClose();
|
||||||
};
|
};
|
||||||
|
|
||||||
// **Vollbildmodus umschalten**
|
const toggleFullScreen = () => dispatch(setFullScreen(!isFullScreen));
|
||||||
const toggleFullScreen = () => {
|
|
||||||
dispatch(setFullScreen(!isFullScreen));
|
|
||||||
};
|
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<ReactModal
|
<ReactModal
|
||||||
@@ -106,77 +85,61 @@ const TDRChartView: React.FC<TDRChartViewProps> = ({
|
|||||||
onRequestClose={handleClose}
|
onRequestClose={handleClose}
|
||||||
ariaHideApp={false}
|
ariaHideApp={false}
|
||||||
style={{
|
style={{
|
||||||
overlay: { backgroundColor: "rgba(0, 0, 0, 0.5)" },
|
overlay: {
|
||||||
|
backgroundColor: "rgba(0,0,0,0.55)",
|
||||||
|
backdropFilter: "blur(2px)",
|
||||||
|
},
|
||||||
content: {
|
content: {
|
||||||
top: "50%",
|
inset: "50% auto auto 50%",
|
||||||
left: "50%",
|
|
||||||
bottom: "auto",
|
|
||||||
marginRight: "-50%",
|
|
||||||
transform: "translate(-50%, -50%)",
|
transform: "translate(-50%, -50%)",
|
||||||
width: isFullScreen ? "90vw" : "70rem",
|
width: isFullScreen ? "90vw" : "72rem",
|
||||||
height: isFullScreen ? "90vh" : "35rem",
|
height: isFullScreen ? "90vh" : "38rem",
|
||||||
padding: "1rem",
|
padding: 0,
|
||||||
transition: "all 0.3s ease-in-out",
|
border: "1px solid var(--color-border)",
|
||||||
|
background: "var(--color-surface)",
|
||||||
|
borderRadius: "14px",
|
||||||
display: "flex",
|
display: "flex",
|
||||||
flexDirection: "column",
|
flexDirection: "column",
|
||||||
|
overflow: "hidden",
|
||||||
},
|
},
|
||||||
}}
|
}}
|
||||||
|
contentLabel="TDR Messung"
|
||||||
>
|
>
|
||||||
{/* Action-Buttons */}
|
<header className="modal-header relative pr-56">
|
||||||
<div
|
<h3 className="text-sm font-semibold tracking-wide">TDR-Messung</h3>
|
||||||
style={{
|
<div className="absolute top-2 right-2 flex gap-2">
|
||||||
position: "absolute",
|
<button
|
||||||
top: "0.625rem",
|
onClick={toggleFullScreen}
|
||||||
right: "0.625rem",
|
className="icon-btn"
|
||||||
display: "flex",
|
aria-label={isFullScreen ? "Vollbild verlassen" : "Vollbild"}
|
||||||
gap: "0.75rem",
|
type="button"
|
||||||
}}
|
>
|
||||||
>
|
<i
|
||||||
{/* Fullscreen-Button */}
|
className={
|
||||||
<button
|
isFullScreen
|
||||||
onClick={toggleFullScreen}
|
? "bi bi-fullscreen-exit"
|
||||||
style={{
|
: "bi bi-arrows-fullscreen"
|
||||||
background: "transparent",
|
}
|
||||||
border: "none",
|
/>
|
||||||
fontSize: "1.5rem",
|
</button>
|
||||||
cursor: "pointer",
|
<button
|
||||||
}}
|
onClick={handleClose}
|
||||||
>
|
className="icon-btn"
|
||||||
<i
|
aria-label="Schließen"
|
||||||
className={
|
type="button"
|
||||||
isFullScreen ? "bi bi-fullscreen-exit" : "bi bi-arrows-fullscreen"
|
>
|
||||||
}
|
<i
|
||||||
></i>
|
style={{
|
||||||
</button>
|
background: "transparent",
|
||||||
|
border: "none",
|
||||||
{/* Schließen-Button */}
|
fontSize: "1.5rem",
|
||||||
<button
|
cursor: "pointer",
|
||||||
onClick={handleClose}
|
}}
|
||||||
style={{
|
className="bi bi-x-circle-fill"
|
||||||
background: "transparent",
|
/>
|
||||||
border: "none",
|
</button>
|
||||||
fontSize: "1.5rem",
|
</div>
|
||||||
cursor: "pointer",
|
<div className="absolute top-2 right-28">
|
||||||
}}
|
|
||||||
>
|
|
||||||
<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">
|
|
||||||
{chartTitle === "Messkurve" ? "TDR-Messung" : "Meldungen"}
|
|
||||||
</h3>
|
|
||||||
{/* Dropdown Messkurve / Meldungen */}
|
|
||||||
<Listbox
|
<Listbox
|
||||||
value={chartTitle}
|
value={chartTitle}
|
||||||
onChange={(value: "Messkurve" | "Meldungen") =>
|
onChange={(value: "Messkurve" | "Meldungen") =>
|
||||||
@@ -184,31 +147,21 @@ const TDRChartView: React.FC<TDRChartViewProps> = ({
|
|||||||
}
|
}
|
||||||
>
|
>
|
||||||
<div className="relative w-40">
|
<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">
|
<Listbox.Button className="dropdown-surface w-full flex items-center justify-between h-8">
|
||||||
<span>{chartTitle}</span>
|
<span className="dropdown-text-fix">{chartTitle}</span>
|
||||||
<svg
|
<i className="bi bi-chevron-down text-sm opacity-70" />
|
||||||
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.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) => (
|
{(["Messkurve", "Meldungen"] as const).map((option) => (
|
||||||
<Listbox.Option
|
<Listbox.Option
|
||||||
key={option}
|
key={option}
|
||||||
value={option}
|
value={option}
|
||||||
className={({ selected, active }) =>
|
className={({ selected, active }) =>
|
||||||
`px-4 py-1 cursor-pointer ${
|
`px-3 py-1.5 cursor-pointer rounded-sm m-0.5 ${
|
||||||
selected
|
selected
|
||||||
? "bg-littwin-blue text-white"
|
? "dropdown-option-active"
|
||||||
: active
|
: active
|
||||||
? "bg-gray-200"
|
? "dropdown-option-hover"
|
||||||
: ""
|
: ""
|
||||||
}`
|
}`
|
||||||
}
|
}
|
||||||
@@ -220,8 +173,11 @@ const TDRChartView: React.FC<TDRChartViewProps> = ({
|
|||||||
</div>
|
</div>
|
||||||
</Listbox>
|
</Listbox>
|
||||||
</div>
|
</div>
|
||||||
{/* Chart oder Meldungen */}
|
</header>
|
||||||
<div style={{ flex: 1, height: "90%" }}>
|
<div className="flex flex-col flex-1 p-3 gap-3">
|
||||||
|
{/* Action Bar (wie bei ISO / Loop) */}
|
||||||
|
<TDRChartActionBar />
|
||||||
|
<div className="flex-1 relative">
|
||||||
{chartTitle === "Messkurve" ? (
|
{chartTitle === "Messkurve" ? (
|
||||||
<TDRChart isFullScreen={isFullScreen} />
|
<TDRChart isFullScreen={isFullScreen} />
|
||||||
) : (
|
) : (
|
||||||
|
|||||||
@@ -31,7 +31,6 @@ import useKueVersion from "./hooks/useKueVersion";
|
|||||||
import useIsoDisplay from "./hooks/useIsoDisplay";
|
import useIsoDisplay from "./hooks/useIsoDisplay";
|
||||||
import useLoopDisplay from "./hooks/useLoopDisplay";
|
import useLoopDisplay from "./hooks/useLoopDisplay";
|
||||||
import useModulName from "./hooks/useModulName";
|
import useModulName from "./hooks/useModulName";
|
||||||
import { useAdminAuth } from "../../settingsPageComponents/hooks/useAdminAuth";
|
|
||||||
|
|
||||||
//--------handlers----------------
|
//--------handlers----------------
|
||||||
// Keep needed imports
|
// Keep needed imports
|
||||||
@@ -57,7 +56,8 @@ const Kue705FO: React.FC<Kue705FOProps> = ({
|
|||||||
const { kueName } = useSelector((state: RootState) => state.kueDataSlice);
|
const { kueName } = useSelector((state: RootState) => state.kueDataSlice);
|
||||||
|
|
||||||
// Admin authentication hook for security - using showModal as true for continuous auth check
|
// Admin authentication hook for security - using showModal as true for continuous auth check
|
||||||
const { isAdminLoggedIn } = useAdminAuth(true);
|
// Admin Auth hook retained (result not currently needed after KVZ visibility change)
|
||||||
|
// const { isAdminLoggedIn } = useAdminAuth(true);
|
||||||
|
|
||||||
// Modulname (max 48 Zeichen) vorbereiten
|
// Modulname (max 48 Zeichen) vorbereiten
|
||||||
const moduleNameRaw = useMemo(
|
const moduleNameRaw = useMemo(
|
||||||
@@ -277,10 +277,10 @@ const Kue705FO: React.FC<Kue705FOProps> = ({
|
|||||||
const isTdrActiveForSlot = tdrActive?.[slotIndex] === 1;
|
const isTdrActiveForSlot = tdrActive?.[slotIndex] === 1;
|
||||||
|
|
||||||
// KVz aktiv Status für diesen Slot prüfen - nur wenn Admin authentifiziert ist, KVz vorhanden ist UND aktiviert ist
|
// KVz aktiv Status für diesen Slot prüfen - nur wenn Admin authentifiziert ist, KVz vorhanden ist UND aktiviert ist
|
||||||
|
// Anpassung: KVZ Button soll sichtbar/benutzbar bleiben, auch wenn Admin sich abmeldet,
|
||||||
|
// sobald KVZ Präsenz + Aktiv-Flag gesetzt sind. Admin wird nur zum Aktivieren benötigt.
|
||||||
const isKvzActiveForSlot =
|
const isKvzActiveForSlot =
|
||||||
kvzPresence?.[slotIndex] === 1 &&
|
kvzPresence?.[slotIndex] === 1 && kvzActive?.[slotIndex] === 1;
|
||||||
kvzActive?.[slotIndex] === 1 &&
|
|
||||||
isAdminLoggedIn;
|
|
||||||
|
|
||||||
// Removed useChartData(loopMeasurementCurveChartData) as the state was unused
|
// Removed useChartData(loopMeasurementCurveChartData) as the state was unused
|
||||||
|
|
||||||
|
|||||||
@@ -41,68 +41,83 @@ export default function KueModal({ showModal, onClose, slot }: KueModalProps) {
|
|||||||
window.kabelModalOpen = showModal;
|
window.kabelModalOpen = showModal;
|
||||||
}
|
}
|
||||||
}, [showModal]);
|
}, [showModal]);
|
||||||
//-----------------------------------------------------
|
|
||||||
|
|
||||||
//------------------------------------------------------
|
|
||||||
return (
|
return (
|
||||||
<ReactModal
|
<ReactModal
|
||||||
isOpen={showModal}
|
isOpen={showModal}
|
||||||
onRequestClose={onClose}
|
onRequestClose={onClose}
|
||||||
|
shouldCloseOnOverlayClick
|
||||||
ariaHideApp={false}
|
ariaHideApp={false}
|
||||||
style={{
|
style={{
|
||||||
overlay: {
|
overlay: {
|
||||||
backgroundColor: "rgba(0, 0, 0, 0.5)",
|
backgroundColor: "rgba(0,0,0,0.55)",
|
||||||
zIndex: 100,
|
zIndex: 100,
|
||||||
|
backdropFilter: "blur(2px)",
|
||||||
},
|
},
|
||||||
content: {
|
content: {
|
||||||
top: "50%",
|
inset: "50% auto auto 50%",
|
||||||
left: "50%",
|
|
||||||
transform: "translate(-50%, -50%)",
|
transform: "translate(-50%, -50%)",
|
||||||
width: "90%",
|
width: "min(900px,92vw)",
|
||||||
maxWidth: "850px",
|
// Feste / konsistente Höhe, unabhängig vom Tab-Inhalt
|
||||||
padding: "0px",
|
// Wenn Viewport kleiner ist, begrenze auf 80vh
|
||||||
border: "none",
|
height: "min(640px, 80vh)",
|
||||||
borderRadius: "8px",
|
maxHeight: "80vh",
|
||||||
position: "relative",
|
padding: 0,
|
||||||
bottom: "auto",
|
border: "1px solid var(--color-border)",
|
||||||
right: "auto",
|
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">
|
<div className="modal-header">
|
||||||
<h2 className="text-base font-bold text-fg">
|
<h2 className="text-sm font-semibold tracking-wide text-fg">
|
||||||
Einstellungen KÜ {slot + 1}
|
Einstellungen KÜ {slot + 1}
|
||||||
</h2>
|
</h2>
|
||||||
<button
|
<button
|
||||||
onClick={onClose}
|
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"
|
aria-label="Modal schließen"
|
||||||
|
type="button"
|
||||||
>
|
>
|
||||||
<i className="bi bi-x-circle-fill"></i>
|
<i
|
||||||
|
style={{
|
||||||
|
background: "transparent",
|
||||||
|
border: "none",
|
||||||
|
fontSize: "1.5rem",
|
||||||
|
cursor: "pointer",
|
||||||
|
}}
|
||||||
|
className="bi bi-x-circle-fill"
|
||||||
|
/>
|
||||||
</button>
|
</button>
|
||||||
</div>
|
</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: "Allgemein", key: "kue" as const },
|
||||||
{ label: "TDR ", key: "tdr" as const },
|
{ label: "TDR", key: "tdr" as const },
|
||||||
{ label: "KVz", key: "kvz" as const },
|
{ label: "KVz", key: "kvz" as const },
|
||||||
{ label: "Knotenpunkte", key: "knoten" as const },
|
{ label: "Knotenpunkte", key: "knoten" as const },
|
||||||
].map(({ label, key }) => (
|
].map(({ label, key }) => {
|
||||||
<button
|
const isActive = activeTab === key;
|
||||||
key={key}
|
return (
|
||||||
onClick={() => setActiveTab(key)}
|
<button
|
||||||
className={`px-4 py-1 rounded-t font-semibold text-sm transition-colors focus:outline-none focus:ring-2 focus:ring-accent/40 ${
|
key={key}
|
||||||
activeTab === key
|
type="button"
|
||||||
? "bg-surface text-accent shadow-sm"
|
onClick={() => setActiveTab(key)}
|
||||||
: "text-fg-muted hover:text-fg"
|
className={`tab-btn ${isActive ? "tab-btn-active" : ""}`}
|
||||||
}`}
|
aria-current={isActive ? "page" : undefined}
|
||||||
>
|
>
|
||||||
{label}
|
{label}
|
||||||
</button>
|
</button>
|
||||||
))}
|
);
|
||||||
|
})}
|
||||||
</div>
|
</div>
|
||||||
<div className="p-4 bg-surface rounded-b-md h-[20rem] laptop:h-[24rem] 2xl:h-[30rem] overflow-y-auto text-fg">
|
{/* Einheitliche Body-Höhe mit internem Scroll statt dynamischer Außenhöhe */}
|
||||||
|
<div className="modal-body-scroll px-5 py-4 flex-1 text-fg overflow-y-auto">
|
||||||
{activeTab === "kue" && (
|
{activeTab === "kue" && (
|
||||||
<KueEinstellung
|
<KueEinstellung
|
||||||
slot={slot}
|
slot={slot}
|
||||||
|
|||||||
@@ -27,6 +27,10 @@ import {
|
|||||||
Legend,
|
Legend,
|
||||||
Filler,
|
Filler,
|
||||||
TimeScale,
|
TimeScale,
|
||||||
|
type ChartDataset,
|
||||||
|
type ChartOptions,
|
||||||
|
type ChartData,
|
||||||
|
type Chart,
|
||||||
} from "chart.js";
|
} from "chart.js";
|
||||||
|
|
||||||
import "chartjs-adapter-date-fns";
|
import "chartjs-adapter-date-fns";
|
||||||
@@ -65,7 +69,7 @@ type ReduxDataEntry = {
|
|||||||
m?: number; // aktueller Messwert (optional, falls vorhanden)
|
m?: number; // aktueller Messwert (optional, falls vorhanden)
|
||||||
};
|
};
|
||||||
|
|
||||||
const chartOptions = {
|
const chartOptions: ChartOptions<"line"> = {
|
||||||
responsive: true,
|
responsive: true,
|
||||||
maintainAspectRatio: false,
|
maintainAspectRatio: false,
|
||||||
plugins: {
|
plugins: {
|
||||||
@@ -78,9 +82,11 @@ const chartOptions = {
|
|||||||
mode: "index" as const,
|
mode: "index" as const,
|
||||||
intersect: false,
|
intersect: false,
|
||||||
callbacks: {
|
callbacks: {
|
||||||
|
// eslint-disable-next-line @typescript-eslint/no-explicit-any
|
||||||
label: function (ctx: any) {
|
label: function (ctx: any) {
|
||||||
return `Messwert: ${ctx.parsed.y}`;
|
return `Messwert: ${ctx.parsed.y}`;
|
||||||
},
|
},
|
||||||
|
// eslint-disable-next-line @typescript-eslint/no-explicit-any
|
||||||
title: function (items: any[]) {
|
title: function (items: any[]) {
|
||||||
const date = items[0].parsed.x;
|
const date = items[0].parsed.x;
|
||||||
return `Zeitpunkt: ${new Date(date).toLocaleString("de-DE")}`;
|
return `Zeitpunkt: ${new Date(date).toLocaleString("de-DE")}`;
|
||||||
@@ -140,13 +146,13 @@ export const DetailModal = ({
|
|||||||
}: Props) => {
|
}: Props) => {
|
||||||
// Stable empty reference to avoid React-Redux dev warning about selector returning new [] each call
|
// Stable empty reference to avoid React-Redux dev warning about selector returning new [] each call
|
||||||
const EMPTY_REDUX_DATA: ReadonlyArray<ReduxDataEntry> = Object.freeze([]);
|
const EMPTY_REDUX_DATA: ReadonlyArray<ReduxDataEntry> = Object.freeze([]);
|
||||||
const chartRef = useRef<any>(null);
|
const chartRef = useRef<Chart<"line"> | null>(null);
|
||||||
const [chartData, setChartData] = useState<any>({
|
const [chartData, setChartData] = useState<ChartData<"line">>({
|
||||||
datasets: [],
|
datasets: [],
|
||||||
});
|
});
|
||||||
const [isLoading, setIsLoading] = useState(false);
|
const [isLoading, setIsLoading] = useState(false);
|
||||||
const [shouldUpdateChart, setShouldUpdateChart] = useState(false);
|
const [shouldUpdateChart, setShouldUpdateChart] = useState(false);
|
||||||
const [forceUpdate, setForceUpdate] = useState(0); // Für periodische UI-Updates
|
// const [forceUpdate, setForceUpdate] = useState(0); // Für periodische UI-Updates (derzeit nicht benötigt)
|
||||||
|
|
||||||
const reduxData = useSelector((state: RootState) => {
|
const reduxData = useSelector((state: RootState) => {
|
||||||
switch (selectedKey) {
|
switch (selectedKey) {
|
||||||
@@ -156,7 +162,7 @@ export const DetailModal = ({
|
|||||||
return state.systemspannung15Vplus[zeitraum];
|
return state.systemspannung15Vplus[zeitraum];
|
||||||
case "-15V":
|
case "-15V":
|
||||||
return state.systemspannung15Vminus[zeitraum];
|
return state.systemspannung15Vminus[zeitraum];
|
||||||
case "-98V":
|
case "-96V":
|
||||||
return state.systemspannung98Vminus[zeitraum];
|
return state.systemspannung98Vminus[zeitraum];
|
||||||
case "ADC Temp":
|
case "ADC Temp":
|
||||||
return state.temperaturAdWandler[zeitraum];
|
return state.temperaturAdWandler[zeitraum];
|
||||||
@@ -192,7 +198,7 @@ export const DetailModal = ({
|
|||||||
case "-15V":
|
case "-15V":
|
||||||
dispatch(getSystemspannung15VminusThunk(zeitraum));
|
dispatch(getSystemspannung15VminusThunk(zeitraum));
|
||||||
break;
|
break;
|
||||||
case "-98V":
|
case "-96V":
|
||||||
dispatch(getSystemspannung98VminusThunk(zeitraum));
|
dispatch(getSystemspannung98VminusThunk(zeitraum));
|
||||||
break;
|
break;
|
||||||
case "ADC Temp":
|
case "ADC Temp":
|
||||||
@@ -221,11 +227,8 @@ export const DetailModal = ({
|
|||||||
// Periodische UI-Updates alle 2 Sekunden während Wartezeit
|
// Periodische UI-Updates alle 2 Sekunden während Wartezeit
|
||||||
useEffect(() => {
|
useEffect(() => {
|
||||||
if (isOpen && (!chartData.datasets || chartData.datasets.length === 0)) {
|
if (isOpen && (!chartData.datasets || chartData.datasets.length === 0)) {
|
||||||
const interval = setInterval(() => {
|
// Optional: periodische Re-Renders wurden deaktiviert, da nicht mehr notwendig
|
||||||
setForceUpdate((prev) => prev + 1); // Force re-render für cursor-wait Update
|
// (kann wieder aktiviert werden falls Cursor-Animation erwünscht ist)
|
||||||
}, 2000);
|
|
||||||
|
|
||||||
return () => clearInterval(interval);
|
|
||||||
}
|
}
|
||||||
}, [isOpen, chartData.datasets]);
|
}, [isOpen, chartData.datasets]);
|
||||||
|
|
||||||
@@ -274,7 +277,12 @@ export const DetailModal = ({
|
|||||||
|
|
||||||
useEffect(() => {
|
useEffect(() => {
|
||||||
if (chartRef.current && selectedKey) {
|
if (chartRef.current && selectedKey) {
|
||||||
chartRef.current.options.plugins.title.text = `Verlauf ${selectedKey}`;
|
const opts = chartRef.current.options as ChartOptions<"line"> & {
|
||||||
|
plugins?: { title?: { text?: string } };
|
||||||
|
};
|
||||||
|
if (opts.plugins?.title) {
|
||||||
|
opts.plugins.title.text = `Verlauf ${selectedKey}`;
|
||||||
|
}
|
||||||
chartRef.current.update("none");
|
chartRef.current.update("none");
|
||||||
}
|
}
|
||||||
}, [selectedKey]);
|
}, [selectedKey]);
|
||||||
@@ -290,14 +298,13 @@ export const DetailModal = ({
|
|||||||
if (chartRef.current && isLoading) {
|
if (chartRef.current && isLoading) {
|
||||||
const chartInstance = chartRef.current;
|
const chartInstance = chartRef.current;
|
||||||
// Save previous callback to restore later
|
// Save previous callback to restore later
|
||||||
const prevCallback = chartInstance.options.animation?.onComplete;
|
const animation: any = chartInstance.options.animation || {}; // eslint-disable-line @typescript-eslint/no-explicit-any
|
||||||
chartInstance.options.animation = {
|
const prevCallback = animation.onComplete;
|
||||||
...chartInstance.options.animation,
|
animation.onComplete = () => {
|
||||||
onComplete: () => {
|
setIsLoading(false);
|
||||||
setIsLoading(false);
|
if (typeof prevCallback === "function") prevCallback();
|
||||||
if (typeof prevCallback === "function") prevCallback();
|
|
||||||
},
|
|
||||||
};
|
};
|
||||||
|
chartInstance.options.animation = animation;
|
||||||
chartInstance.update();
|
chartInstance.update();
|
||||||
}
|
}
|
||||||
}, [chartData, isLoading]);
|
}, [chartData, isLoading]);
|
||||||
@@ -338,7 +345,7 @@ export const DetailModal = ({
|
|||||||
}
|
}
|
||||||
|
|
||||||
// Create datasets array for multiple lines
|
// Create datasets array for multiple lines
|
||||||
const datasets = [];
|
const datasets: ChartDataset<"line">[] = [];
|
||||||
|
|
||||||
// Check which data fields are available and create datasets accordingly
|
// Check which data fields are available and create datasets accordingly
|
||||||
const hasMinimum = filtered.some(
|
const hasMinimum = filtered.some(
|
||||||
@@ -416,7 +423,8 @@ export const DetailModal = ({
|
|||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
const newChartData = {
|
const newChartData: ChartData<"line"> = {
|
||||||
|
labels: [],
|
||||||
datasets: datasets,
|
datasets: datasets,
|
||||||
};
|
};
|
||||||
|
|
||||||
@@ -428,7 +436,13 @@ export const DetailModal = ({
|
|||||||
setChartData({ datasets: [] });
|
setChartData({ datasets: [] });
|
||||||
setShouldUpdateChart(false); // Reset flag
|
setShouldUpdateChart(false); // Reset flag
|
||||||
}
|
}
|
||||||
}, [reduxData, selectedKey, shouldUpdateChart]);
|
}, [
|
||||||
|
reduxData,
|
||||||
|
selectedKey,
|
||||||
|
shouldUpdateChart,
|
||||||
|
pickerVonDatum,
|
||||||
|
pickerBisDatum,
|
||||||
|
]);
|
||||||
|
|
||||||
if (!isOpen || !selectedKey) return null;
|
if (!isOpen || !selectedKey) return null;
|
||||||
|
|
||||||
@@ -442,19 +456,25 @@ export const DetailModal = ({
|
|||||||
}`}
|
}`}
|
||||||
>
|
>
|
||||||
<div
|
<div
|
||||||
className={`bg-white p-6 rounded-xl overflow-auto shadow-2xl transition-all duration-300 ${
|
role="dialog"
|
||||||
isFullScreen ? "w-[95vw] h-[90vh]" : "w-[50%] h-[60%]"
|
aria-modal="true"
|
||||||
|
className={`bg-[var(--color-surface)] text-fg border border-base rounded-xl shadow-xl flex flex-col overflow-hidden transition-all duration-300 ${
|
||||||
|
isFullScreen
|
||||||
|
? "w-[90vw] h-[90vh]"
|
||||||
|
: "w-[70rem] max-w-[95vw] h-[40rem]"
|
||||||
} ${!hasChartData ? "cursor-wait" : ""}`}
|
} ${!hasChartData ? "cursor-wait" : ""}`}
|
||||||
>
|
>
|
||||||
<div className="relative">
|
{/* Header */}
|
||||||
<h2 className="text-xl font-semibold">
|
<header className="flex items-center justify-between px-6 py-4 border-b border-base select-none bg-[var(--color-surface)]">
|
||||||
|
<h2 className="text-base font-bold tracking-wide">
|
||||||
Detailansicht: {selectedKey}
|
Detailansicht: {selectedKey}
|
||||||
</h2>
|
</h2>
|
||||||
|
<div className="flex items-center gap-3 text-lg">
|
||||||
<div className="absolute top-0 right-0 flex gap-3">
|
|
||||||
<button
|
<button
|
||||||
onClick={toggleFullScreen}
|
onClick={toggleFullScreen}
|
||||||
className="text-2xl text-[var(--color-fg-muted)] hover:text-[var(--color-fg)] transition"
|
className="icon-btn text-[1.4rem] hover:text-fg transition"
|
||||||
|
aria-label={isFullScreen ? "Vollbild verlassen" : "Vollbild"}
|
||||||
|
type="button"
|
||||||
>
|
>
|
||||||
<i
|
<i
|
||||||
className={
|
className={
|
||||||
@@ -462,28 +482,35 @@ export const DetailModal = ({
|
|||||||
? "bi bi-fullscreen-exit"
|
? "bi bi-fullscreen-exit"
|
||||||
: "bi bi-arrows-fullscreen"
|
: "bi bi-arrows-fullscreen"
|
||||||
}
|
}
|
||||||
></i>
|
/>
|
||||||
</button>
|
</button>
|
||||||
|
|
||||||
<button
|
<button
|
||||||
onClick={handleClose}
|
onClick={handleClose}
|
||||||
className="text-2xl text-[var(--color-fg-muted)] hover:text-[var(--color-danger)] transition"
|
className="icon-btn text-[1.4rem] transition"
|
||||||
|
aria-label="Modal schließen"
|
||||||
|
type="button"
|
||||||
>
|
>
|
||||||
<i className="bi bi-x-circle-fill"></i>
|
<i className="bi bi-x-circle-fill" />
|
||||||
</button>
|
</button>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</header>
|
||||||
|
|
||||||
<SystemChartActionBar
|
{/* Body */}
|
||||||
zeitraum={zeitraum}
|
<div className="flex-1 min-h-0 flex flex-col px-6 pt-4 pb-5 bg-[var(--color-surface)] overflow-hidden">
|
||||||
setZeitraum={setZeitraum}
|
<div className="mb-3">
|
||||||
onFetchData={handleFetchData}
|
<SystemChartActionBar
|
||||||
isLoading={isLoading}
|
zeitraum={zeitraum}
|
||||||
/>
|
setZeitraum={setZeitraum}
|
||||||
|
onFetchData={handleFetchData}
|
||||||
<div className="h-[85%] rounded shadow border p-2 bg-[var(--color-surface)] border-[var(--color-border)]">
|
isLoading={isLoading}
|
||||||
<Line ref={chartRef} data={chartData} options={chartOptions} />
|
className="mb-0"
|
||||||
|
/>
|
||||||
|
</div>
|
||||||
|
<div className="flex-1 min-h-0 rounded-lg border border-base bg-[var(--color-surface-alt)] px-3 py-2 shadow-inner">
|
||||||
|
<Line ref={chartRef} data={chartData} options={chartOptions} />
|
||||||
|
</div>
|
||||||
</div>
|
</div>
|
||||||
|
{/* Optional Footer (currently empty, reserved for future) */}
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
);
|
);
|
||||||
|
|||||||
@@ -30,7 +30,7 @@ const SystemChartActionBar: React.FC<Props> = ({
|
|||||||
<label className="font-medium text-sm">Zeitraum:</label>
|
<label className="font-medium text-sm">Zeitraum:</label>
|
||||||
<Listbox value={zeitraum} onChange={setZeitraum}>
|
<Listbox value={zeitraum} onChange={setZeitraum}>
|
||||||
<div className="relative w-48">
|
<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">
|
<Listbox.Button className="w-full border border-base px-3 py-1 rounded text-left bg-[var(--color-surface-alt)] text-fg flex justify-between items-center text-sm focus:outline-none focus:ring-2 focus:ring-[var(--color-accent)]/40 transition">
|
||||||
<span>
|
<span>
|
||||||
{
|
{
|
||||||
{ DIA0: "Alle Messwerte", DIA1: "Stündlich", DIA2: "Täglich" }[
|
{ DIA0: "Alle Messwerte", DIA1: "Stündlich", DIA2: "Täglich" }[
|
||||||
@@ -39,7 +39,7 @@ const SystemChartActionBar: React.FC<Props> = ({
|
|||||||
}
|
}
|
||||||
</span>
|
</span>
|
||||||
<svg
|
<svg
|
||||||
className="w-5 h-5 text-gray-400"
|
className="w-5 h-5 text-[var(--color-fg-muted)]"
|
||||||
viewBox="0 0 20 20"
|
viewBox="0 0 20 20"
|
||||||
fill="currentColor"
|
fill="currentColor"
|
||||||
>
|
>
|
||||||
@@ -50,20 +50,18 @@ const SystemChartActionBar: React.FC<Props> = ({
|
|||||||
/>
|
/>
|
||||||
</svg>
|
</svg>
|
||||||
</Listbox.Button>
|
</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="absolute z-50 mt-1 w-full border border-base rounded bg-[var(--color-surface)] text-fg shadow-lg max-h-60 overflow-auto text-sm focus:outline-none">
|
||||||
{["DIA0", "DIA1", "DIA2"].map((option) => (
|
{["DIA0", "DIA1", "DIA2"].map((option) => (
|
||||||
<Listbox.Option
|
<Listbox.Option
|
||||||
key={option}
|
key={option}
|
||||||
value={option}
|
value={option}
|
||||||
className={({ selected, active }) =>
|
className={({ selected, active }) => {
|
||||||
`px-4 py-1 cursor-pointer ${
|
const base = "px-4 py-1 cursor-pointer text-sm";
|
||||||
selected
|
if (selected) return `${base} bg-littwin-blue text-white`; // selected highlight
|
||||||
? "bg-littwin-blue text-white"
|
if (active)
|
||||||
: active
|
return `${base} bg-[var(--color-surface-alt)] text-fg`;
|
||||||
? "bg-gray-200"
|
return `${base} text-fg`;
|
||||||
: ""
|
}}
|
||||||
}`
|
|
||||||
}
|
|
||||||
>
|
>
|
||||||
{
|
{
|
||||||
{
|
{
|
||||||
|
|||||||
@@ -28,7 +28,7 @@ export type HistoryEntry = {
|
|||||||
"+5V": number;
|
"+5V": number;
|
||||||
"+15V": number;
|
"+15V": number;
|
||||||
"-15V": number;
|
"-15V": number;
|
||||||
"-98V": number;
|
"-96V": number;
|
||||||
"ADC Temp": number;
|
"ADC Temp": number;
|
||||||
"CPU Temp": number;
|
"CPU Temp": number;
|
||||||
};
|
};
|
||||||
@@ -110,8 +110,8 @@ export const SystemCharts = ({ history }: Props) => {
|
|||||||
fill: false,
|
fill: false,
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
label: "-98V",
|
label: "-96V",
|
||||||
data: history.map((h) => formatValue(h["-98V"])),
|
data: history.map((h) => formatValue(h["-96V"])),
|
||||||
borderColor: "rgba(234,179,8,1)",
|
borderColor: "rgba(234,179,8,1)",
|
||||||
backgroundColor: "rgba(234,179,8,0.5)",
|
backgroundColor: "rgba(234,179,8,0.5)",
|
||||||
fill: false,
|
fill: false,
|
||||||
|
|||||||
@@ -53,7 +53,7 @@ const SystemPage = () => {
|
|||||||
case "-15V":
|
case "-15V":
|
||||||
dispatch(getSystemspannung15VminusThunk(zeitraum));
|
dispatch(getSystemspannung15VminusThunk(zeitraum));
|
||||||
break;
|
break;
|
||||||
case "-98V":
|
case "-96V":
|
||||||
dispatch(getSystemspannung98VminusThunk(zeitraum));
|
dispatch(getSystemspannung98VminusThunk(zeitraum));
|
||||||
break;
|
break;
|
||||||
case "ADC Temp":
|
case "ADC Temp":
|
||||||
|
|||||||
@@ -36,8 +36,8 @@ const Navigation: React.FC<NavigationProps> = ({ className }) => {
|
|||||||
];
|
];
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<aside className="h-full bg-[var(--color-surface)] dark:bg-[var(--color-surface)] border-r border-[var(--color-border)]">
|
<aside className="h-full bg-[var(--color-surface)] dark:bg-[var(--color-surface)] ">
|
||||||
<nav className={`h-full flex-shrink-0 mt-16 ${className || "w-48"}`}>
|
<nav className={`h-full flex-shrink-0 mt-24 ${className || "w-48"}`}>
|
||||||
{menuItems.map((item) => (
|
{menuItems.map((item) => (
|
||||||
<div key={item.name}>
|
<div key={item.name}>
|
||||||
{item.disabled ? (
|
{item.disabled ? (
|
||||||
|
|||||||
23
docs/TODO.md
23
docs/TODO.md
@@ -87,8 +87,7 @@ in Rot, wenn Schleifenfehler ansteht
|
|||||||
- [x] TODO: RSL starten in RSL Messung starten umbenennen
|
- [x] TODO: RSL starten in RSL Messung starten umbenennen
|
||||||
- [x] TODO: TDR-Messung starten statt TDR aktivieren in ChartBar
|
- [x] TODO: TDR-Messung starten statt TDR aktivieren in ChartBar
|
||||||
- [x] TODO: KÜ TDR-aktiviert alert entfernen
|
- [x] TODO: KÜ TDR-aktiviert alert entfernen
|
||||||
- [ ] TODO: Systemdaten unter Detailansicht ein Verlaufsdiagramm hinzufügen mit Datumsauswahl
|
- [x] TODO: Systemdaten unter Detailansicht ein Verlaufsdiagramm hinzufügen mit Datumsauswahl
|
||||||
- [ ] TODO: Playwright testen mit der Entwicklung
|
|
||||||
|
|
||||||
# Kai Schmidt:
|
# Kai Schmidt:
|
||||||
|
|
||||||
@@ -98,10 +97,26 @@ in Rot, wenn Schleifenfehler ansteht
|
|||||||
|
|
||||||
[x] TODO: Formatierung der Kabelüberwachungswerten in den visuellen Einschüben (Isowert mit Komma und 2 Nachkommastellen; RSL mit Komma und 3 Noachkommastellen) Nachkommastellen immer anzeigen und mit Nullen auffüllen.
|
[x] TODO: Formatierung der Kabelüberwachungswerten in den visuellen Einschüben (Isowert mit Komma und 2 Nachkommastellen; RSL mit Komma und 3 Noachkommastellen) Nachkommastellen immer anzeigen und mit Nullen auffüllen.
|
||||||
|
|
||||||
[ ] TODO: Admin User nach einer Zeit von einer Stunde löschen (Cookie oder Local Storrage)
|
[x] TODO: lange Modulnamen bei KÜ ermöglichen (48 Zeichen) bei Version ab V4.30. Laufschrift möglich?
|
||||||
|
|
||||||
[ ] TODO: lange Modulnamen bei KÜ ermöglichen (48 Zeichen) bei Version ab V4.30. Laufschrift möglich?
|
# ------------------------------------------
|
||||||
|
|
||||||
|
# 08.09.2025
|
||||||
|
|
||||||
|
[x] TODO: Beim Ausführen einer TDR-Messung (Klick auf blauen Button in der TDR-Detailseite) erscheint keine Rückmeldung. Dort müsste ein Hinweis erscheinen “TDR-Messung wird ausgeführt und kann bis zu zwei Minuten dauern”
|
||||||
|
|
||||||
|
## 09.09.2025
|
||||||
|
|
||||||
|
[x] TODO: Admin User nach einer Zeit von einer Stunde löschen (Cookie oder Local Storrage)
|
||||||
|
|
||||||
[ ] TODO: Darkmode ermöglichen
|
[ ] TODO: Darkmode ermöglichen
|
||||||
|
|
||||||
[ ] TODO: Wenn im Browser Darkmode eingschaltet ist muss die Webseite erkennbar sein.
|
[ ] TODO: Wenn im Browser Darkmode eingschaltet ist muss die Webseite erkennbar sein.
|
||||||
|
|
||||||
|
[ ] TODO: KÜ TDR-aktiviert alert entfernen
|
||||||
|
|
||||||
|
[ ] TODO: Playwright testen mit der Entwicklung
|
||||||
|
|
||||||
|
# 11.09.2025
|
||||||
|
|
||||||
|
[ ] TODO: KÜ ISO Modal -> Meldungen z-index datePicker von bis
|
||||||
|
|||||||
@@ -5,7 +5,7 @@ Meine Tabelle ist falsch. Ich werde sie anpassen. Korrekt ist:
|
|||||||
108: +15V
|
108: +15V
|
||||||
110: +5V
|
110: +5V
|
||||||
114: -15V
|
114: -15V
|
||||||
115: -98V
|
115: -96V
|
||||||
116: Temperatur AD Wandler
|
116: Temperatur AD Wandler
|
||||||
117: Temperatur Prozessor
|
117: Temperatur Prozessor
|
||||||
------------------------------------
|
------------------------------------
|
||||||
@@ -8,7 +8,7 @@ In der **Systemseite** werden die aktuellen **Versorgungsspannungen** und **Temp
|
|||||||
|
|
||||||
Die Seite zeigt:
|
Die Seite zeigt:
|
||||||
|
|
||||||
- **Live-Werte** aller Spannungen (+5V, +15V, -15V, -98V)
|
- **Live-Werte** aller Spannungen (+5V, +15V, -15V, -96V)
|
||||||
- **Temperaturen** von CPU und ADC
|
- **Temperaturen** von CPU und ADC
|
||||||
- **Verlauf** der Werte in einem **Liniendiagramm**
|
- **Verlauf** der Werte in einem **Liniendiagramm**
|
||||||
|
|
||||||
|
|||||||
@@ -8,7 +8,7 @@ In der **Systemseite** werden die aktuellen **Versorgungsspannungen** und **Temp
|
|||||||
|
|
||||||
Die Seite zeigt:
|
Die Seite zeigt:
|
||||||
|
|
||||||
- **Live-Werte** aller Spannungen (+5V, +15V, -15V, -98V)
|
- **Live-Werte** aller Spannungen (+5V, +15V, -15V, -96V)
|
||||||
- **Temperaturen** von CPU und ADC
|
- **Temperaturen** von CPU und ADC
|
||||||
- **Verlauf** der Werte in einem **Liniendiagramm**
|
- **Verlauf** der Werte in einem **Liniendiagramm**
|
||||||
|
|
||||||
|
|||||||
@@ -5,7 +5,7 @@ var win_systemVoltTempMockData = [
|
|||||||
];
|
];
|
||||||
|
|
||||||
/*
|
/*
|
||||||
var win_systemVoltTempMockData = ["+15V","+5V", "-15V","-98V","ADC Temperatur", "CPU Temperatur"];
|
var win_systemVoltTempMockData = ["+15V","+5V", "-15V","-96V","ADC Temperatur", "CPU Temperatur"];
|
||||||
|
|
||||||
ae09.value=system[0]; //+15V
|
ae09.value=system[0]; //+15V
|
||||||
ae11.value=system[1]; //5V
|
ae11.value=system[1]; //5V
|
||||||
|
|||||||
@@ -1,6 +1,6 @@
|
|||||||
108: +15V
|
108: +15V
|
||||||
110: +5V
|
110: +5V
|
||||||
114: -15V
|
114: -15V
|
||||||
115: -98V
|
115: -96V
|
||||||
116: Temperatur AD Wandler
|
116: Temperatur AD Wandler
|
||||||
117: Temperatur Prozessor
|
117: Temperatur Prozessor
|
||||||
|
|||||||
@@ -1,24 +1,206 @@
|
|||||||
{
|
{
|
||||||
"kvzPresence": [
|
"kvzPresence": [
|
||||||
1, 1, 1, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0,
|
1,
|
||||||
0, 0, 0, 0, 0, 0, 0
|
1,
|
||||||
|
1,
|
||||||
|
0,
|
||||||
|
0,
|
||||||
|
0,
|
||||||
|
0,
|
||||||
|
0,
|
||||||
|
0,
|
||||||
|
0,
|
||||||
|
0,
|
||||||
|
0,
|
||||||
|
0,
|
||||||
|
0,
|
||||||
|
0,
|
||||||
|
0,
|
||||||
|
0,
|
||||||
|
0,
|
||||||
|
0,
|
||||||
|
0,
|
||||||
|
0,
|
||||||
|
0,
|
||||||
|
0,
|
||||||
|
0,
|
||||||
|
0,
|
||||||
|
0,
|
||||||
|
0,
|
||||||
|
0,
|
||||||
|
0,
|
||||||
|
0,
|
||||||
|
0,
|
||||||
|
0
|
||||||
],
|
],
|
||||||
"kvzActive": [
|
"kvzActive": [
|
||||||
1, 1, 1, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0,
|
1,
|
||||||
0, 0, 0, 0, 0, 0, 0
|
1,
|
||||||
|
1,
|
||||||
|
0,
|
||||||
|
0,
|
||||||
|
0,
|
||||||
|
0,
|
||||||
|
0,
|
||||||
|
0,
|
||||||
|
0,
|
||||||
|
0,
|
||||||
|
0,
|
||||||
|
0,
|
||||||
|
0,
|
||||||
|
0,
|
||||||
|
0,
|
||||||
|
0,
|
||||||
|
0,
|
||||||
|
0,
|
||||||
|
0,
|
||||||
|
0,
|
||||||
|
0,
|
||||||
|
0,
|
||||||
|
0,
|
||||||
|
0,
|
||||||
|
0,
|
||||||
|
0,
|
||||||
|
0,
|
||||||
|
0,
|
||||||
|
0,
|
||||||
|
0,
|
||||||
|
0
|
||||||
],
|
],
|
||||||
"kvzStatus": [
|
"kvzStatus": [
|
||||||
1, 0, 2, 1, 2, 0, 1, 0, 2, 1, 0, 2, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0,
|
1,
|
||||||
0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0,
|
0,
|
||||||
0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0,
|
2,
|
||||||
0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0,
|
1,
|
||||||
0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0,
|
2,
|
||||||
0, 0, 0
|
0,
|
||||||
|
1,
|
||||||
|
0,
|
||||||
|
2,
|
||||||
|
1,
|
||||||
|
0,
|
||||||
|
2,
|
||||||
|
0,
|
||||||
|
0,
|
||||||
|
0,
|
||||||
|
0,
|
||||||
|
0,
|
||||||
|
0,
|
||||||
|
0,
|
||||||
|
0,
|
||||||
|
0,
|
||||||
|
0,
|
||||||
|
0,
|
||||||
|
0,
|
||||||
|
0,
|
||||||
|
0,
|
||||||
|
0,
|
||||||
|
0,
|
||||||
|
0,
|
||||||
|
0,
|
||||||
|
0,
|
||||||
|
0,
|
||||||
|
0,
|
||||||
|
0,
|
||||||
|
0,
|
||||||
|
0,
|
||||||
|
0,
|
||||||
|
0,
|
||||||
|
0,
|
||||||
|
0,
|
||||||
|
0,
|
||||||
|
0,
|
||||||
|
0,
|
||||||
|
0,
|
||||||
|
0,
|
||||||
|
0,
|
||||||
|
0,
|
||||||
|
0,
|
||||||
|
0,
|
||||||
|
0,
|
||||||
|
0,
|
||||||
|
0,
|
||||||
|
0,
|
||||||
|
0,
|
||||||
|
0,
|
||||||
|
0,
|
||||||
|
0,
|
||||||
|
0,
|
||||||
|
0,
|
||||||
|
0,
|
||||||
|
0,
|
||||||
|
0,
|
||||||
|
0,
|
||||||
|
0,
|
||||||
|
0,
|
||||||
|
0,
|
||||||
|
0,
|
||||||
|
0,
|
||||||
|
0,
|
||||||
|
0,
|
||||||
|
0,
|
||||||
|
0,
|
||||||
|
0,
|
||||||
|
0,
|
||||||
|
0,
|
||||||
|
0,
|
||||||
|
0,
|
||||||
|
0,
|
||||||
|
0,
|
||||||
|
0,
|
||||||
|
0,
|
||||||
|
0,
|
||||||
|
0,
|
||||||
|
0,
|
||||||
|
0,
|
||||||
|
0,
|
||||||
|
0,
|
||||||
|
0,
|
||||||
|
0,
|
||||||
|
0,
|
||||||
|
0,
|
||||||
|
0,
|
||||||
|
0,
|
||||||
|
0,
|
||||||
|
0,
|
||||||
|
0,
|
||||||
|
0,
|
||||||
|
0,
|
||||||
|
0,
|
||||||
|
0,
|
||||||
|
0,
|
||||||
|
0,
|
||||||
|
0,
|
||||||
|
0,
|
||||||
|
0,
|
||||||
|
0,
|
||||||
|
0,
|
||||||
|
0,
|
||||||
|
0,
|
||||||
|
0,
|
||||||
|
0,
|
||||||
|
0,
|
||||||
|
0,
|
||||||
|
0,
|
||||||
|
0,
|
||||||
|
0,
|
||||||
|
0,
|
||||||
|
0,
|
||||||
|
0,
|
||||||
|
0,
|
||||||
|
0,
|
||||||
|
0,
|
||||||
|
0,
|
||||||
|
0,
|
||||||
|
0,
|
||||||
|
0,
|
||||||
|
0,
|
||||||
|
0
|
||||||
],
|
],
|
||||||
"timestamp": "2025-07-31T11:39:22.951Z",
|
"timestamp": "2025-09-10T06:37:13.443Z",
|
||||||
"description": {
|
"description": {
|
||||||
"kvzPresence": "32 Slots: 1=KVZ Gerät vorhanden, 0=nicht vorhanden. Slots 0,2 haben KVZ-Geräte",
|
"kvzPresence": "32 Slots: 1=KVZ Gerät vorhanden, 0=nicht vorhanden. Slots 0,2 haben KVZ-Geräte",
|
||||||
"kvzActive": "32 Slots: 1=KVZ aktiviert, 0=deaktiviert. Nur Slot 0 ist aktiviert",
|
"kvzActive": "32 Slots: 1=KVZ aktiviert, 0=deaktiviert. Nur Slot 0 ist aktiviert",
|
||||||
"kvzStatus": "128 LEDs: 4 LEDs pro Slot. Slot 0: [1,0,1,0], Slot 2: [1,1,0,1] (aber Slot 2 ist deaktiviert)"
|
"kvzStatus": "128 LEDs: 4 LEDs pro Slot. Slot 0: [1,0,1,0], Slot 2: [1,1,0,1] (aber Slot 2 ist deaktiviert)"
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@@ -4,7 +4,7 @@
|
|||||||
Dieses Script lädt sowohl Systemdaten (z.B. Spannungen und Temperaturen) als auch die Messdaten der 8 analogen Eingänge eines CPL-Geräts
|
Dieses Script lädt sowohl Systemdaten (z.B. Spannungen und Temperaturen) als auch die Messdaten der 8 analogen Eingänge eines CPL-Geräts
|
||||||
für die letzten 30 Tage per HTTP/HTTPS-API herunter und speichert sie als Mockdaten im lokalen Dateisystem.
|
für die letzten 30 Tage per HTTP/HTTPS-API herunter und speichert sie als Mockdaten im lokalen Dateisystem.
|
||||||
|
|
||||||
- Systemdaten: Für die Inputs 108 (+15V), 110 (+5V), 114 (-15V), 115 (-98V), 116 (Temperatur AD Wandler), 117 (Temperatur Prozessor)
|
- Systemdaten: Für die Inputs 108 (+15V), 110 (+5V), 114 (-15V), 115 (-96V), 116 (Temperatur AD Wandler), 117 (Temperatur Prozessor)
|
||||||
werden die Daten für die DIA-Typen DIA0, DIA1, DIA2 jeweils in das Verzeichnis
|
werden die Daten für die DIA-Typen DIA0, DIA1, DIA2 jeweils in das Verzeichnis
|
||||||
mocks/device-cgi-simulator/chartsData/<systemVerzeichnis>/DIAx.json geschrieben.
|
mocks/device-cgi-simulator/chartsData/<systemVerzeichnis>/DIAx.json geschrieben.
|
||||||
|
|
||||||
|
|||||||
555
package-lock.json
generated
555
package-lock.json
generated
@@ -1,13 +1,15 @@
|
|||||||
{
|
{
|
||||||
"name": "cpl-v4",
|
"name": "cpl-v4",
|
||||||
"version": "1.6.879",
|
"version": "1.6.913",
|
||||||
"lockfileVersion": 3,
|
"lockfileVersion": 3,
|
||||||
"requires": true,
|
"requires": true,
|
||||||
"packages": {
|
"packages": {
|
||||||
"": {
|
"": {
|
||||||
"name": "cpl-v4",
|
"name": "cpl-v4",
|
||||||
"version": "1.6.879",
|
"version": "1.6.913",
|
||||||
"dependencies": {
|
"dependencies": {
|
||||||
|
"@emotion/react": "^11.13.0",
|
||||||
|
"@emotion/styled": "^11.13.0",
|
||||||
"@fontsource/roboto": "^5.1.0",
|
"@fontsource/roboto": "^5.1.0",
|
||||||
"@headlessui/react": "^2.2.4",
|
"@headlessui/react": "^2.2.4",
|
||||||
"@iconify-icons/ri": "^1.2.10",
|
"@iconify-icons/ri": "^1.2.10",
|
||||||
@@ -15,6 +17,7 @@
|
|||||||
"@iconify/icons-mdi": "^1.2.48",
|
"@iconify/icons-mdi": "^1.2.48",
|
||||||
"@iconify/json": "^2.2.253",
|
"@iconify/json": "^2.2.253",
|
||||||
"@iconify/react": "^5.0.2",
|
"@iconify/react": "^5.0.2",
|
||||||
|
"@mui/material": "^6.0.0",
|
||||||
"@reduxjs/toolkit": "^2.3.0",
|
"@reduxjs/toolkit": "^2.3.0",
|
||||||
"autoprefixer": "^10.4.20",
|
"autoprefixer": "^10.4.20",
|
||||||
"bcryptjs": "^2.4.3",
|
"bcryptjs": "^2.4.3",
|
||||||
@@ -112,7 +115,6 @@
|
|||||||
"version": "7.27.1",
|
"version": "7.27.1",
|
||||||
"resolved": "https://registry.npmjs.org/@babel/code-frame/-/code-frame-7.27.1.tgz",
|
"resolved": "https://registry.npmjs.org/@babel/code-frame/-/code-frame-7.27.1.tgz",
|
||||||
"integrity": "sha512-cjQ7ZlQ0Mv3b47hABuTevyTuYN4i+loJKGeV9flcCgIK37cCXRh+L1bd3iBHlynerhQ7BhCkn2BPbQUL+rGqFg==",
|
"integrity": "sha512-cjQ7ZlQ0Mv3b47hABuTevyTuYN4i+loJKGeV9flcCgIK37cCXRh+L1bd3iBHlynerhQ7BhCkn2BPbQUL+rGqFg==",
|
||||||
"dev": true,
|
|
||||||
"dependencies": {
|
"dependencies": {
|
||||||
"@babel/helper-validator-identifier": "^7.27.1",
|
"@babel/helper-validator-identifier": "^7.27.1",
|
||||||
"js-tokens": "^4.0.0",
|
"js-tokens": "^4.0.0",
|
||||||
@@ -174,7 +176,6 @@
|
|||||||
"version": "7.27.5",
|
"version": "7.27.5",
|
||||||
"resolved": "https://registry.npmjs.org/@babel/generator/-/generator-7.27.5.tgz",
|
"resolved": "https://registry.npmjs.org/@babel/generator/-/generator-7.27.5.tgz",
|
||||||
"integrity": "sha512-ZGhA37l0e/g2s1Cnzdix0O3aLYm66eF8aufiVteOgnwxgnRP8GoyMj7VWsgWnQbVKXyge7hqrFh2K2TQM6t1Hw==",
|
"integrity": "sha512-ZGhA37l0e/g2s1Cnzdix0O3aLYm66eF8aufiVteOgnwxgnRP8GoyMj7VWsgWnQbVKXyge7hqrFh2K2TQM6t1Hw==",
|
||||||
"dev": true,
|
|
||||||
"dependencies": {
|
"dependencies": {
|
||||||
"@babel/parser": "^7.27.5",
|
"@babel/parser": "^7.27.5",
|
||||||
"@babel/types": "^7.27.3",
|
"@babel/types": "^7.27.3",
|
||||||
@@ -215,7 +216,6 @@
|
|||||||
"version": "7.27.1",
|
"version": "7.27.1",
|
||||||
"resolved": "https://registry.npmjs.org/@babel/helper-module-imports/-/helper-module-imports-7.27.1.tgz",
|
"resolved": "https://registry.npmjs.org/@babel/helper-module-imports/-/helper-module-imports-7.27.1.tgz",
|
||||||
"integrity": "sha512-0gSFWUPNXNopqtIPQvlD5WgXYI5GY2kP2cCvoT8kczjbfcfuIljTbcWrulD1CIPIX2gt1wghbDy08yE1p+/r3w==",
|
"integrity": "sha512-0gSFWUPNXNopqtIPQvlD5WgXYI5GY2kP2cCvoT8kczjbfcfuIljTbcWrulD1CIPIX2gt1wghbDy08yE1p+/r3w==",
|
||||||
"dev": true,
|
|
||||||
"dependencies": {
|
"dependencies": {
|
||||||
"@babel/traverse": "^7.27.1",
|
"@babel/traverse": "^7.27.1",
|
||||||
"@babel/types": "^7.27.1"
|
"@babel/types": "^7.27.1"
|
||||||
@@ -254,7 +254,6 @@
|
|||||||
"version": "7.27.1",
|
"version": "7.27.1",
|
||||||
"resolved": "https://registry.npmjs.org/@babel/helper-string-parser/-/helper-string-parser-7.27.1.tgz",
|
"resolved": "https://registry.npmjs.org/@babel/helper-string-parser/-/helper-string-parser-7.27.1.tgz",
|
||||||
"integrity": "sha512-qMlSxKbpRlAridDExk92nSobyDdpPijUq2DW6oDnUqd0iOGxmQjyqhMIihI9+zv4LPyZdRje2cavWPbCbWm3eA==",
|
"integrity": "sha512-qMlSxKbpRlAridDExk92nSobyDdpPijUq2DW6oDnUqd0iOGxmQjyqhMIihI9+zv4LPyZdRje2cavWPbCbWm3eA==",
|
||||||
"dev": true,
|
|
||||||
"engines": {
|
"engines": {
|
||||||
"node": ">=6.9.0"
|
"node": ">=6.9.0"
|
||||||
}
|
}
|
||||||
@@ -263,7 +262,6 @@
|
|||||||
"version": "7.27.1",
|
"version": "7.27.1",
|
||||||
"resolved": "https://registry.npmjs.org/@babel/helper-validator-identifier/-/helper-validator-identifier-7.27.1.tgz",
|
"resolved": "https://registry.npmjs.org/@babel/helper-validator-identifier/-/helper-validator-identifier-7.27.1.tgz",
|
||||||
"integrity": "sha512-D2hP9eA+Sqx1kBZgzxZh0y1trbuU+JoDkiEwqhQ36nodYqJwyEIhPSdMNd7lOm/4io72luTPWH20Yda0xOuUow==",
|
"integrity": "sha512-D2hP9eA+Sqx1kBZgzxZh0y1trbuU+JoDkiEwqhQ36nodYqJwyEIhPSdMNd7lOm/4io72luTPWH20Yda0xOuUow==",
|
||||||
"dev": true,
|
|
||||||
"engines": {
|
"engines": {
|
||||||
"node": ">=6.9.0"
|
"node": ">=6.9.0"
|
||||||
}
|
}
|
||||||
@@ -294,7 +292,6 @@
|
|||||||
"version": "7.27.5",
|
"version": "7.27.5",
|
||||||
"resolved": "https://registry.npmjs.org/@babel/parser/-/parser-7.27.5.tgz",
|
"resolved": "https://registry.npmjs.org/@babel/parser/-/parser-7.27.5.tgz",
|
||||||
"integrity": "sha512-OsQd175SxWkGlzbny8J3K8TnnDD0N3lrIUtB92xwyRpzaenGZhxDvxN/JgU00U3CDZNj9tPuDJ5H0WS4Nt3vKg==",
|
"integrity": "sha512-OsQd175SxWkGlzbny8J3K8TnnDD0N3lrIUtB92xwyRpzaenGZhxDvxN/JgU00U3CDZNj9tPuDJ5H0WS4Nt3vKg==",
|
||||||
"dev": true,
|
|
||||||
"dependencies": {
|
"dependencies": {
|
||||||
"@babel/types": "^7.27.3"
|
"@babel/types": "^7.27.3"
|
||||||
},
|
},
|
||||||
@@ -539,7 +536,6 @@
|
|||||||
"version": "7.27.2",
|
"version": "7.27.2",
|
||||||
"resolved": "https://registry.npmjs.org/@babel/template/-/template-7.27.2.tgz",
|
"resolved": "https://registry.npmjs.org/@babel/template/-/template-7.27.2.tgz",
|
||||||
"integrity": "sha512-LPDZ85aEJyYSd18/DkjNh4/y1ntkE5KwUHWTiqgRxruuZL2F1yuHligVHLvcHY2vMHXttKFpJn6LwfI7cw7ODw==",
|
"integrity": "sha512-LPDZ85aEJyYSd18/DkjNh4/y1ntkE5KwUHWTiqgRxruuZL2F1yuHligVHLvcHY2vMHXttKFpJn6LwfI7cw7ODw==",
|
||||||
"dev": true,
|
|
||||||
"dependencies": {
|
"dependencies": {
|
||||||
"@babel/code-frame": "^7.27.1",
|
"@babel/code-frame": "^7.27.1",
|
||||||
"@babel/parser": "^7.27.2",
|
"@babel/parser": "^7.27.2",
|
||||||
@@ -553,7 +549,6 @@
|
|||||||
"version": "7.27.4",
|
"version": "7.27.4",
|
||||||
"resolved": "https://registry.npmjs.org/@babel/traverse/-/traverse-7.27.4.tgz",
|
"resolved": "https://registry.npmjs.org/@babel/traverse/-/traverse-7.27.4.tgz",
|
||||||
"integrity": "sha512-oNcu2QbHqts9BtOWJosOVJapWjBDSxGCpFvikNR5TGDYDQf3JwpIoMzIKrvfoti93cLfPJEG4tH9SPVeyCGgdA==",
|
"integrity": "sha512-oNcu2QbHqts9BtOWJosOVJapWjBDSxGCpFvikNR5TGDYDQf3JwpIoMzIKrvfoti93cLfPJEG4tH9SPVeyCGgdA==",
|
||||||
"dev": true,
|
|
||||||
"dependencies": {
|
"dependencies": {
|
||||||
"@babel/code-frame": "^7.27.1",
|
"@babel/code-frame": "^7.27.1",
|
||||||
"@babel/generator": "^7.27.3",
|
"@babel/generator": "^7.27.3",
|
||||||
@@ -571,7 +566,6 @@
|
|||||||
"version": "7.27.6",
|
"version": "7.27.6",
|
||||||
"resolved": "https://registry.npmjs.org/@babel/types/-/types-7.27.6.tgz",
|
"resolved": "https://registry.npmjs.org/@babel/types/-/types-7.27.6.tgz",
|
||||||
"integrity": "sha512-ETyHEk2VHHvl9b9jZP5IHPavHYk57EhanlRRuae9XCpb/j5bDCbPPMOBfCWhnl/7EDJz0jEMCi/RhccCE8r1+Q==",
|
"integrity": "sha512-ETyHEk2VHHvl9b9jZP5IHPavHYk57EhanlRRuae9XCpb/j5bDCbPPMOBfCWhnl/7EDJz0jEMCi/RhccCE8r1+Q==",
|
||||||
"dev": true,
|
|
||||||
"dependencies": {
|
"dependencies": {
|
||||||
"@babel/helper-string-parser": "^7.27.1",
|
"@babel/helper-string-parser": "^7.27.1",
|
||||||
"@babel/helper-validator-identifier": "^7.27.1"
|
"@babel/helper-validator-identifier": "^7.27.1"
|
||||||
@@ -644,6 +638,167 @@
|
|||||||
"tslib": "^2.4.0"
|
"tslib": "^2.4.0"
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
|
"node_modules/@emotion/babel-plugin": {
|
||||||
|
"version": "11.13.5",
|
||||||
|
"resolved": "https://registry.npmjs.org/@emotion/babel-plugin/-/babel-plugin-11.13.5.tgz",
|
||||||
|
"integrity": "sha512-pxHCpT2ex+0q+HH91/zsdHkw/lXd468DIN2zvfvLtPKLLMo6gQj7oLObq8PhkrxOZb/gGCq03S3Z7PDhS8pduQ==",
|
||||||
|
"license": "MIT",
|
||||||
|
"dependencies": {
|
||||||
|
"@babel/helper-module-imports": "^7.16.7",
|
||||||
|
"@babel/runtime": "^7.18.3",
|
||||||
|
"@emotion/hash": "^0.9.2",
|
||||||
|
"@emotion/memoize": "^0.9.0",
|
||||||
|
"@emotion/serialize": "^1.3.3",
|
||||||
|
"babel-plugin-macros": "^3.1.0",
|
||||||
|
"convert-source-map": "^1.5.0",
|
||||||
|
"escape-string-regexp": "^4.0.0",
|
||||||
|
"find-root": "^1.1.0",
|
||||||
|
"source-map": "^0.5.7",
|
||||||
|
"stylis": "4.2.0"
|
||||||
|
}
|
||||||
|
},
|
||||||
|
"node_modules/@emotion/babel-plugin/node_modules/convert-source-map": {
|
||||||
|
"version": "1.9.0",
|
||||||
|
"resolved": "https://registry.npmjs.org/convert-source-map/-/convert-source-map-1.9.0.tgz",
|
||||||
|
"integrity": "sha512-ASFBup0Mz1uyiIjANan1jzLQami9z1PoYSZCiiYW2FczPbenXc45FZdBZLzOT+r6+iciuEModtmCti+hjaAk0A==",
|
||||||
|
"license": "MIT"
|
||||||
|
},
|
||||||
|
"node_modules/@emotion/babel-plugin/node_modules/source-map": {
|
||||||
|
"version": "0.5.7",
|
||||||
|
"resolved": "https://registry.npmjs.org/source-map/-/source-map-0.5.7.tgz",
|
||||||
|
"integrity": "sha512-LbrmJOMUSdEVxIKvdcJzQC+nQhe8FUZQTXQy6+I75skNgn3OoQ0DZA8YnFa7gp8tqtL3KPf1kmo0R5DoApeSGQ==",
|
||||||
|
"license": "BSD-3-Clause",
|
||||||
|
"engines": {
|
||||||
|
"node": ">=0.10.0"
|
||||||
|
}
|
||||||
|
},
|
||||||
|
"node_modules/@emotion/cache": {
|
||||||
|
"version": "11.14.0",
|
||||||
|
"resolved": "https://registry.npmjs.org/@emotion/cache/-/cache-11.14.0.tgz",
|
||||||
|
"integrity": "sha512-L/B1lc/TViYk4DcpGxtAVbx0ZyiKM5ktoIyafGkH6zg/tj+mA+NE//aPYKG0k8kCHSHVJrpLpcAlOBEXQ3SavA==",
|
||||||
|
"license": "MIT",
|
||||||
|
"dependencies": {
|
||||||
|
"@emotion/memoize": "^0.9.0",
|
||||||
|
"@emotion/sheet": "^1.4.0",
|
||||||
|
"@emotion/utils": "^1.4.2",
|
||||||
|
"@emotion/weak-memoize": "^0.4.0",
|
||||||
|
"stylis": "4.2.0"
|
||||||
|
}
|
||||||
|
},
|
||||||
|
"node_modules/@emotion/hash": {
|
||||||
|
"version": "0.9.2",
|
||||||
|
"resolved": "https://registry.npmjs.org/@emotion/hash/-/hash-0.9.2.tgz",
|
||||||
|
"integrity": "sha512-MyqliTZGuOm3+5ZRSaaBGP3USLw6+EGykkwZns2EPC5g8jJ4z9OrdZY9apkl3+UP9+sdz76YYkwCKP5gh8iY3g==",
|
||||||
|
"license": "MIT"
|
||||||
|
},
|
||||||
|
"node_modules/@emotion/is-prop-valid": {
|
||||||
|
"version": "1.4.0",
|
||||||
|
"resolved": "https://registry.npmjs.org/@emotion/is-prop-valid/-/is-prop-valid-1.4.0.tgz",
|
||||||
|
"integrity": "sha512-QgD4fyscGcbbKwJmqNvUMSE02OsHUa+lAWKdEUIJKgqe5IwRSKd7+KhibEWdaKwgjLj0DRSHA9biAIqGBk05lw==",
|
||||||
|
"license": "MIT",
|
||||||
|
"dependencies": {
|
||||||
|
"@emotion/memoize": "^0.9.0"
|
||||||
|
}
|
||||||
|
},
|
||||||
|
"node_modules/@emotion/memoize": {
|
||||||
|
"version": "0.9.0",
|
||||||
|
"resolved": "https://registry.npmjs.org/@emotion/memoize/-/memoize-0.9.0.tgz",
|
||||||
|
"integrity": "sha512-30FAj7/EoJ5mwVPOWhAyCX+FPfMDrVecJAM+Iw9NRoSl4BBAQeqj4cApHHUXOVvIPgLVDsCFoz/hGD+5QQD1GQ==",
|
||||||
|
"license": "MIT"
|
||||||
|
},
|
||||||
|
"node_modules/@emotion/react": {
|
||||||
|
"version": "11.14.0",
|
||||||
|
"resolved": "https://registry.npmjs.org/@emotion/react/-/react-11.14.0.tgz",
|
||||||
|
"integrity": "sha512-O000MLDBDdk/EohJPFUqvnp4qnHeYkVP5B0xEG0D/L7cOKP9kefu2DXn8dj74cQfsEzUqh+sr1RzFqiL1o+PpA==",
|
||||||
|
"license": "MIT",
|
||||||
|
"dependencies": {
|
||||||
|
"@babel/runtime": "^7.18.3",
|
||||||
|
"@emotion/babel-plugin": "^11.13.5",
|
||||||
|
"@emotion/cache": "^11.14.0",
|
||||||
|
"@emotion/serialize": "^1.3.3",
|
||||||
|
"@emotion/use-insertion-effect-with-fallbacks": "^1.2.0",
|
||||||
|
"@emotion/utils": "^1.4.2",
|
||||||
|
"@emotion/weak-memoize": "^0.4.0",
|
||||||
|
"hoist-non-react-statics": "^3.3.1"
|
||||||
|
},
|
||||||
|
"peerDependencies": {
|
||||||
|
"react": ">=16.8.0"
|
||||||
|
},
|
||||||
|
"peerDependenciesMeta": {
|
||||||
|
"@types/react": {
|
||||||
|
"optional": true
|
||||||
|
}
|
||||||
|
}
|
||||||
|
},
|
||||||
|
"node_modules/@emotion/serialize": {
|
||||||
|
"version": "1.3.3",
|
||||||
|
"resolved": "https://registry.npmjs.org/@emotion/serialize/-/serialize-1.3.3.tgz",
|
||||||
|
"integrity": "sha512-EISGqt7sSNWHGI76hC7x1CksiXPahbxEOrC5RjmFRJTqLyEK9/9hZvBbiYn70dw4wuwMKiEMCUlR6ZXTSWQqxA==",
|
||||||
|
"license": "MIT",
|
||||||
|
"dependencies": {
|
||||||
|
"@emotion/hash": "^0.9.2",
|
||||||
|
"@emotion/memoize": "^0.9.0",
|
||||||
|
"@emotion/unitless": "^0.10.0",
|
||||||
|
"@emotion/utils": "^1.4.2",
|
||||||
|
"csstype": "^3.0.2"
|
||||||
|
}
|
||||||
|
},
|
||||||
|
"node_modules/@emotion/sheet": {
|
||||||
|
"version": "1.4.0",
|
||||||
|
"resolved": "https://registry.npmjs.org/@emotion/sheet/-/sheet-1.4.0.tgz",
|
||||||
|
"integrity": "sha512-fTBW9/8r2w3dXWYM4HCB1Rdp8NLibOw2+XELH5m5+AkWiL/KqYX6dc0kKYlaYyKjrQ6ds33MCdMPEwgs2z1rqg==",
|
||||||
|
"license": "MIT"
|
||||||
|
},
|
||||||
|
"node_modules/@emotion/styled": {
|
||||||
|
"version": "11.14.1",
|
||||||
|
"resolved": "https://registry.npmjs.org/@emotion/styled/-/styled-11.14.1.tgz",
|
||||||
|
"integrity": "sha512-qEEJt42DuToa3gurlH4Qqc1kVpNq8wO8cJtDzU46TjlzWjDlsVyevtYCRijVq3SrHsROS+gVQ8Fnea108GnKzw==",
|
||||||
|
"license": "MIT",
|
||||||
|
"dependencies": {
|
||||||
|
"@babel/runtime": "^7.18.3",
|
||||||
|
"@emotion/babel-plugin": "^11.13.5",
|
||||||
|
"@emotion/is-prop-valid": "^1.3.0",
|
||||||
|
"@emotion/serialize": "^1.3.3",
|
||||||
|
"@emotion/use-insertion-effect-with-fallbacks": "^1.2.0",
|
||||||
|
"@emotion/utils": "^1.4.2"
|
||||||
|
},
|
||||||
|
"peerDependencies": {
|
||||||
|
"@emotion/react": "^11.0.0-rc.0",
|
||||||
|
"react": ">=16.8.0"
|
||||||
|
},
|
||||||
|
"peerDependenciesMeta": {
|
||||||
|
"@types/react": {
|
||||||
|
"optional": true
|
||||||
|
}
|
||||||
|
}
|
||||||
|
},
|
||||||
|
"node_modules/@emotion/unitless": {
|
||||||
|
"version": "0.10.0",
|
||||||
|
"resolved": "https://registry.npmjs.org/@emotion/unitless/-/unitless-0.10.0.tgz",
|
||||||
|
"integrity": "sha512-dFoMUuQA20zvtVTuxZww6OHoJYgrzfKM1t52mVySDJnMSEa08ruEvdYQbhvyu6soU+NeLVd3yKfTfT0NeV6qGg==",
|
||||||
|
"license": "MIT"
|
||||||
|
},
|
||||||
|
"node_modules/@emotion/use-insertion-effect-with-fallbacks": {
|
||||||
|
"version": "1.2.0",
|
||||||
|
"resolved": "https://registry.npmjs.org/@emotion/use-insertion-effect-with-fallbacks/-/use-insertion-effect-with-fallbacks-1.2.0.tgz",
|
||||||
|
"integrity": "sha512-yJMtVdH59sxi/aVJBpk9FQq+OR8ll5GT8oWd57UpeaKEVGab41JWaCFA7FRLoMLloOZF/c/wsPoe+bfGmRKgDg==",
|
||||||
|
"license": "MIT",
|
||||||
|
"peerDependencies": {
|
||||||
|
"react": ">=16.8.0"
|
||||||
|
}
|
||||||
|
},
|
||||||
|
"node_modules/@emotion/utils": {
|
||||||
|
"version": "1.4.2",
|
||||||
|
"resolved": "https://registry.npmjs.org/@emotion/utils/-/utils-1.4.2.tgz",
|
||||||
|
"integrity": "sha512-3vLclRofFziIa3J2wDh9jjbkUz9qk5Vi3IZ/FSTKViB0k+ef0fPV7dYrUIugbgupYDx7v9ud/SjrtEP8Y4xLoA==",
|
||||||
|
"license": "MIT"
|
||||||
|
},
|
||||||
|
"node_modules/@emotion/weak-memoize": {
|
||||||
|
"version": "0.4.0",
|
||||||
|
"resolved": "https://registry.npmjs.org/@emotion/weak-memoize/-/weak-memoize-0.4.0.tgz",
|
||||||
|
"integrity": "sha512-snKqtPW01tN0ui7yu9rGv69aJXr/a/Ywvl11sUjNtEcRc+ng/mQriFL0wLXMef74iHa/EkftbDzU9F8iFbH+zg==",
|
||||||
|
"license": "MIT"
|
||||||
|
},
|
||||||
"node_modules/@eslint-community/eslint-utils": {
|
"node_modules/@eslint-community/eslint-utils": {
|
||||||
"version": "4.7.0",
|
"version": "4.7.0",
|
||||||
"resolved": "https://registry.npmjs.org/@eslint-community/eslint-utils/-/eslint-utils-4.7.0.tgz",
|
"resolved": "https://registry.npmjs.org/@eslint-community/eslint-utils/-/eslint-utils-4.7.0.tgz",
|
||||||
@@ -1374,7 +1529,6 @@
|
|||||||
"version": "0.3.8",
|
"version": "0.3.8",
|
||||||
"resolved": "https://registry.npmjs.org/@jridgewell/gen-mapping/-/gen-mapping-0.3.8.tgz",
|
"resolved": "https://registry.npmjs.org/@jridgewell/gen-mapping/-/gen-mapping-0.3.8.tgz",
|
||||||
"integrity": "sha512-imAbBGkb+ebQyxKgzv5Hu2nmROxoDOXHh80evxdoXNOrvAnVx7zimzc1Oo5h9RlfV4vPXaE2iM5pOFbvOCClWA==",
|
"integrity": "sha512-imAbBGkb+ebQyxKgzv5Hu2nmROxoDOXHh80evxdoXNOrvAnVx7zimzc1Oo5h9RlfV4vPXaE2iM5pOFbvOCClWA==",
|
||||||
"dev": true,
|
|
||||||
"dependencies": {
|
"dependencies": {
|
||||||
"@jridgewell/set-array": "^1.2.1",
|
"@jridgewell/set-array": "^1.2.1",
|
||||||
"@jridgewell/sourcemap-codec": "^1.4.10",
|
"@jridgewell/sourcemap-codec": "^1.4.10",
|
||||||
@@ -1388,7 +1542,6 @@
|
|||||||
"version": "3.1.2",
|
"version": "3.1.2",
|
||||||
"resolved": "https://registry.npmjs.org/@jridgewell/resolve-uri/-/resolve-uri-3.1.2.tgz",
|
"resolved": "https://registry.npmjs.org/@jridgewell/resolve-uri/-/resolve-uri-3.1.2.tgz",
|
||||||
"integrity": "sha512-bRISgCIjP20/tbWSPWMEi54QVPRZExkuD9lJL+UIxUKtwVJA8wW1Trb1jMs1RFXo1CBTNZ/5hpC9QvmKWdopKw==",
|
"integrity": "sha512-bRISgCIjP20/tbWSPWMEi54QVPRZExkuD9lJL+UIxUKtwVJA8wW1Trb1jMs1RFXo1CBTNZ/5hpC9QvmKWdopKw==",
|
||||||
"dev": true,
|
|
||||||
"engines": {
|
"engines": {
|
||||||
"node": ">=6.0.0"
|
"node": ">=6.0.0"
|
||||||
}
|
}
|
||||||
@@ -1397,7 +1550,6 @@
|
|||||||
"version": "1.2.1",
|
"version": "1.2.1",
|
||||||
"resolved": "https://registry.npmjs.org/@jridgewell/set-array/-/set-array-1.2.1.tgz",
|
"resolved": "https://registry.npmjs.org/@jridgewell/set-array/-/set-array-1.2.1.tgz",
|
||||||
"integrity": "sha512-R8gLRTZeyp03ymzP/6Lil/28tGeGEzhx1q2k703KGWRAI1VdvPIXdG70VJc2pAMw3NA6JKL5hhFu1sJX0Mnn/A==",
|
"integrity": "sha512-R8gLRTZeyp03ymzP/6Lil/28tGeGEzhx1q2k703KGWRAI1VdvPIXdG70VJc2pAMw3NA6JKL5hhFu1sJX0Mnn/A==",
|
||||||
"dev": true,
|
|
||||||
"engines": {
|
"engines": {
|
||||||
"node": ">=6.0.0"
|
"node": ">=6.0.0"
|
||||||
}
|
}
|
||||||
@@ -1405,14 +1557,12 @@
|
|||||||
"node_modules/@jridgewell/sourcemap-codec": {
|
"node_modules/@jridgewell/sourcemap-codec": {
|
||||||
"version": "1.5.0",
|
"version": "1.5.0",
|
||||||
"resolved": "https://registry.npmjs.org/@jridgewell/sourcemap-codec/-/sourcemap-codec-1.5.0.tgz",
|
"resolved": "https://registry.npmjs.org/@jridgewell/sourcemap-codec/-/sourcemap-codec-1.5.0.tgz",
|
||||||
"integrity": "sha512-gv3ZRaISU3fjPAgNsriBRqGWQL6quFx04YMPW/zD8XMLsU32mhCCbfbO6KZFLjvYpCZ8zyDEgqsgf+PwPaM7GQ==",
|
"integrity": "sha512-gv3ZRaISU3fjPAgNsriBRqGWQL6quFx04YMPW/zD8XMLsU32mhCCbfbO6KZFLjvYpCZ8zyDEgqsgf+PwPaM7GQ=="
|
||||||
"dev": true
|
|
||||||
},
|
},
|
||||||
"node_modules/@jridgewell/trace-mapping": {
|
"node_modules/@jridgewell/trace-mapping": {
|
||||||
"version": "0.3.25",
|
"version": "0.3.25",
|
||||||
"resolved": "https://registry.npmjs.org/@jridgewell/trace-mapping/-/trace-mapping-0.3.25.tgz",
|
"resolved": "https://registry.npmjs.org/@jridgewell/trace-mapping/-/trace-mapping-0.3.25.tgz",
|
||||||
"integrity": "sha512-vNk6aEwybGtawWmy/PzwnGDOjCkLWSD2wqvjGGAgOAwCGWySYXfYoxt00IJkTF+8Lb57DwOb3Aa0o9CApepiYQ==",
|
"integrity": "sha512-vNk6aEwybGtawWmy/PzwnGDOjCkLWSD2wqvjGGAgOAwCGWySYXfYoxt00IJkTF+8Lb57DwOb3Aa0o9CApepiYQ==",
|
||||||
"dev": true,
|
|
||||||
"dependencies": {
|
"dependencies": {
|
||||||
"@jridgewell/resolve-uri": "^3.1.0",
|
"@jridgewell/resolve-uri": "^3.1.0",
|
||||||
"@jridgewell/sourcemap-codec": "^1.4.14"
|
"@jridgewell/sourcemap-codec": "^1.4.14"
|
||||||
@@ -1423,6 +1573,222 @@
|
|||||||
"resolved": "https://registry.npmjs.org/@kurkle/color/-/color-0.3.4.tgz",
|
"resolved": "https://registry.npmjs.org/@kurkle/color/-/color-0.3.4.tgz",
|
||||||
"integrity": "sha512-M5UknZPHRu3DEDWoipU6sE8PdkZ6Z/S+v4dD+Ke8IaNlpdSQah50lz1KtcFBa2vsdOnwbbnxJwVM4wty6udA5w=="
|
"integrity": "sha512-M5UknZPHRu3DEDWoipU6sE8PdkZ6Z/S+v4dD+Ke8IaNlpdSQah50lz1KtcFBa2vsdOnwbbnxJwVM4wty6udA5w=="
|
||||||
},
|
},
|
||||||
|
"node_modules/@mui/core-downloads-tracker": {
|
||||||
|
"version": "6.5.0",
|
||||||
|
"resolved": "https://registry.npmjs.org/@mui/core-downloads-tracker/-/core-downloads-tracker-6.5.0.tgz",
|
||||||
|
"integrity": "sha512-LGb8t8i6M2ZtS3Drn3GbTI1DVhDY6FJ9crEey2lZ0aN2EMZo8IZBZj9wRf4vqbZHaWjsYgtbOnJw5V8UWbmK2Q==",
|
||||||
|
"license": "MIT",
|
||||||
|
"funding": {
|
||||||
|
"type": "opencollective",
|
||||||
|
"url": "https://opencollective.com/mui-org"
|
||||||
|
}
|
||||||
|
},
|
||||||
|
"node_modules/@mui/material": {
|
||||||
|
"version": "6.5.0",
|
||||||
|
"resolved": "https://registry.npmjs.org/@mui/material/-/material-6.5.0.tgz",
|
||||||
|
"integrity": "sha512-yjvtXoFcrPLGtgKRxFaH6OQPtcLPhkloC0BML6rBG5UeldR0nPULR/2E2BfXdo5JNV7j7lOzrrLX2Qf/iSidow==",
|
||||||
|
"license": "MIT",
|
||||||
|
"dependencies": {
|
||||||
|
"@babel/runtime": "^7.26.0",
|
||||||
|
"@mui/core-downloads-tracker": "^6.5.0",
|
||||||
|
"@mui/system": "^6.5.0",
|
||||||
|
"@mui/types": "~7.2.24",
|
||||||
|
"@mui/utils": "^6.4.9",
|
||||||
|
"@popperjs/core": "^2.11.8",
|
||||||
|
"@types/react-transition-group": "^4.4.12",
|
||||||
|
"clsx": "^2.1.1",
|
||||||
|
"csstype": "^3.1.3",
|
||||||
|
"prop-types": "^15.8.1",
|
||||||
|
"react-is": "^19.0.0",
|
||||||
|
"react-transition-group": "^4.4.5"
|
||||||
|
},
|
||||||
|
"engines": {
|
||||||
|
"node": ">=14.0.0"
|
||||||
|
},
|
||||||
|
"funding": {
|
||||||
|
"type": "opencollective",
|
||||||
|
"url": "https://opencollective.com/mui-org"
|
||||||
|
},
|
||||||
|
"peerDependencies": {
|
||||||
|
"@emotion/react": "^11.5.0",
|
||||||
|
"@emotion/styled": "^11.3.0",
|
||||||
|
"@mui/material-pigment-css": "^6.5.0",
|
||||||
|
"@types/react": "^17.0.0 || ^18.0.0 || ^19.0.0",
|
||||||
|
"react": "^17.0.0 || ^18.0.0 || ^19.0.0",
|
||||||
|
"react-dom": "^17.0.0 || ^18.0.0 || ^19.0.0"
|
||||||
|
},
|
||||||
|
"peerDependenciesMeta": {
|
||||||
|
"@emotion/react": {
|
||||||
|
"optional": true
|
||||||
|
},
|
||||||
|
"@emotion/styled": {
|
||||||
|
"optional": true
|
||||||
|
},
|
||||||
|
"@mui/material-pigment-css": {
|
||||||
|
"optional": true
|
||||||
|
},
|
||||||
|
"@types/react": {
|
||||||
|
"optional": true
|
||||||
|
}
|
||||||
|
}
|
||||||
|
},
|
||||||
|
"node_modules/@mui/material/node_modules/react-is": {
|
||||||
|
"version": "19.1.1",
|
||||||
|
"resolved": "https://registry.npmjs.org/react-is/-/react-is-19.1.1.tgz",
|
||||||
|
"integrity": "sha512-tr41fA15Vn8p4X9ntI+yCyeGSf1TlYaY5vlTZfQmeLBrFo3psOPX6HhTDnFNL9uj3EhP0KAQ80cugCl4b4BERA==",
|
||||||
|
"license": "MIT"
|
||||||
|
},
|
||||||
|
"node_modules/@mui/private-theming": {
|
||||||
|
"version": "6.4.9",
|
||||||
|
"resolved": "https://registry.npmjs.org/@mui/private-theming/-/private-theming-6.4.9.tgz",
|
||||||
|
"integrity": "sha512-LktcVmI5X17/Q5SkwjCcdOLBzt1hXuc14jYa7NPShog0GBDCDvKtcnP0V7a2s6EiVRlv7BzbWEJzH6+l/zaCxw==",
|
||||||
|
"license": "MIT",
|
||||||
|
"dependencies": {
|
||||||
|
"@babel/runtime": "^7.26.0",
|
||||||
|
"@mui/utils": "^6.4.9",
|
||||||
|
"prop-types": "^15.8.1"
|
||||||
|
},
|
||||||
|
"engines": {
|
||||||
|
"node": ">=14.0.0"
|
||||||
|
},
|
||||||
|
"funding": {
|
||||||
|
"type": "opencollective",
|
||||||
|
"url": "https://opencollective.com/mui-org"
|
||||||
|
},
|
||||||
|
"peerDependencies": {
|
||||||
|
"@types/react": "^17.0.0 || ^18.0.0 || ^19.0.0",
|
||||||
|
"react": "^17.0.0 || ^18.0.0 || ^19.0.0"
|
||||||
|
},
|
||||||
|
"peerDependenciesMeta": {
|
||||||
|
"@types/react": {
|
||||||
|
"optional": true
|
||||||
|
}
|
||||||
|
}
|
||||||
|
},
|
||||||
|
"node_modules/@mui/styled-engine": {
|
||||||
|
"version": "6.5.0",
|
||||||
|
"resolved": "https://registry.npmjs.org/@mui/styled-engine/-/styled-engine-6.5.0.tgz",
|
||||||
|
"integrity": "sha512-8woC2zAqF4qUDSPIBZ8v3sakj+WgweolpyM/FXf8jAx6FMls+IE4Y8VDZc+zS805J7PRz31vz73n2SovKGaYgw==",
|
||||||
|
"license": "MIT",
|
||||||
|
"dependencies": {
|
||||||
|
"@babel/runtime": "^7.26.0",
|
||||||
|
"@emotion/cache": "^11.13.5",
|
||||||
|
"@emotion/serialize": "^1.3.3",
|
||||||
|
"@emotion/sheet": "^1.4.0",
|
||||||
|
"csstype": "^3.1.3",
|
||||||
|
"prop-types": "^15.8.1"
|
||||||
|
},
|
||||||
|
"engines": {
|
||||||
|
"node": ">=14.0.0"
|
||||||
|
},
|
||||||
|
"funding": {
|
||||||
|
"type": "opencollective",
|
||||||
|
"url": "https://opencollective.com/mui-org"
|
||||||
|
},
|
||||||
|
"peerDependencies": {
|
||||||
|
"@emotion/react": "^11.4.1",
|
||||||
|
"@emotion/styled": "^11.3.0",
|
||||||
|
"react": "^17.0.0 || ^18.0.0 || ^19.0.0"
|
||||||
|
},
|
||||||
|
"peerDependenciesMeta": {
|
||||||
|
"@emotion/react": {
|
||||||
|
"optional": true
|
||||||
|
},
|
||||||
|
"@emotion/styled": {
|
||||||
|
"optional": true
|
||||||
|
}
|
||||||
|
}
|
||||||
|
},
|
||||||
|
"node_modules/@mui/system": {
|
||||||
|
"version": "6.5.0",
|
||||||
|
"resolved": "https://registry.npmjs.org/@mui/system/-/system-6.5.0.tgz",
|
||||||
|
"integrity": "sha512-XcbBYxDS+h/lgsoGe78ExXFZXtuIlSBpn/KsZq8PtZcIkUNJInkuDqcLd2rVBQrDC1u+rvVovdaWPf2FHKJf3w==",
|
||||||
|
"license": "MIT",
|
||||||
|
"dependencies": {
|
||||||
|
"@babel/runtime": "^7.26.0",
|
||||||
|
"@mui/private-theming": "^6.4.9",
|
||||||
|
"@mui/styled-engine": "^6.5.0",
|
||||||
|
"@mui/types": "~7.2.24",
|
||||||
|
"@mui/utils": "^6.4.9",
|
||||||
|
"clsx": "^2.1.1",
|
||||||
|
"csstype": "^3.1.3",
|
||||||
|
"prop-types": "^15.8.1"
|
||||||
|
},
|
||||||
|
"engines": {
|
||||||
|
"node": ">=14.0.0"
|
||||||
|
},
|
||||||
|
"funding": {
|
||||||
|
"type": "opencollective",
|
||||||
|
"url": "https://opencollective.com/mui-org"
|
||||||
|
},
|
||||||
|
"peerDependencies": {
|
||||||
|
"@emotion/react": "^11.5.0",
|
||||||
|
"@emotion/styled": "^11.3.0",
|
||||||
|
"@types/react": "^17.0.0 || ^18.0.0 || ^19.0.0",
|
||||||
|
"react": "^17.0.0 || ^18.0.0 || ^19.0.0"
|
||||||
|
},
|
||||||
|
"peerDependenciesMeta": {
|
||||||
|
"@emotion/react": {
|
||||||
|
"optional": true
|
||||||
|
},
|
||||||
|
"@emotion/styled": {
|
||||||
|
"optional": true
|
||||||
|
},
|
||||||
|
"@types/react": {
|
||||||
|
"optional": true
|
||||||
|
}
|
||||||
|
}
|
||||||
|
},
|
||||||
|
"node_modules/@mui/types": {
|
||||||
|
"version": "7.2.24",
|
||||||
|
"resolved": "https://registry.npmjs.org/@mui/types/-/types-7.2.24.tgz",
|
||||||
|
"integrity": "sha512-3c8tRt/CbWZ+pEg7QpSwbdxOk36EfmhbKf6AGZsD1EcLDLTSZoxxJ86FVtcjxvjuhdyBiWKSTGZFaXCnidO2kw==",
|
||||||
|
"license": "MIT",
|
||||||
|
"peerDependencies": {
|
||||||
|
"@types/react": "^17.0.0 || ^18.0.0 || ^19.0.0"
|
||||||
|
},
|
||||||
|
"peerDependenciesMeta": {
|
||||||
|
"@types/react": {
|
||||||
|
"optional": true
|
||||||
|
}
|
||||||
|
}
|
||||||
|
},
|
||||||
|
"node_modules/@mui/utils": {
|
||||||
|
"version": "6.4.9",
|
||||||
|
"resolved": "https://registry.npmjs.org/@mui/utils/-/utils-6.4.9.tgz",
|
||||||
|
"integrity": "sha512-Y12Q9hbK9g+ZY0T3Rxrx9m2m10gaphDuUMgWxyV5kNJevVxXYCLclYUCC9vXaIk1/NdNDTcW2Yfr2OGvNFNmHg==",
|
||||||
|
"license": "MIT",
|
||||||
|
"dependencies": {
|
||||||
|
"@babel/runtime": "^7.26.0",
|
||||||
|
"@mui/types": "~7.2.24",
|
||||||
|
"@types/prop-types": "^15.7.14",
|
||||||
|
"clsx": "^2.1.1",
|
||||||
|
"prop-types": "^15.8.1",
|
||||||
|
"react-is": "^19.0.0"
|
||||||
|
},
|
||||||
|
"engines": {
|
||||||
|
"node": ">=14.0.0"
|
||||||
|
},
|
||||||
|
"funding": {
|
||||||
|
"type": "opencollective",
|
||||||
|
"url": "https://opencollective.com/mui-org"
|
||||||
|
},
|
||||||
|
"peerDependencies": {
|
||||||
|
"@types/react": "^17.0.0 || ^18.0.0 || ^19.0.0",
|
||||||
|
"react": "^17.0.0 || ^18.0.0 || ^19.0.0"
|
||||||
|
},
|
||||||
|
"peerDependenciesMeta": {
|
||||||
|
"@types/react": {
|
||||||
|
"optional": true
|
||||||
|
}
|
||||||
|
}
|
||||||
|
},
|
||||||
|
"node_modules/@mui/utils/node_modules/react-is": {
|
||||||
|
"version": "19.1.1",
|
||||||
|
"resolved": "https://registry.npmjs.org/react-is/-/react-is-19.1.1.tgz",
|
||||||
|
"integrity": "sha512-tr41fA15Vn8p4X9ntI+yCyeGSf1TlYaY5vlTZfQmeLBrFo3psOPX6HhTDnFNL9uj3EhP0KAQ80cugCl4b4BERA==",
|
||||||
|
"license": "MIT"
|
||||||
|
},
|
||||||
"node_modules/@napi-rs/wasm-runtime": {
|
"node_modules/@napi-rs/wasm-runtime": {
|
||||||
"version": "0.2.11",
|
"version": "0.2.11",
|
||||||
"resolved": "https://registry.npmjs.org/@napi-rs/wasm-runtime/-/wasm-runtime-0.2.11.tgz",
|
"resolved": "https://registry.npmjs.org/@napi-rs/wasm-runtime/-/wasm-runtime-0.2.11.tgz",
|
||||||
@@ -1681,6 +2047,16 @@
|
|||||||
"node": ">=18"
|
"node": ">=18"
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
|
"node_modules/@popperjs/core": {
|
||||||
|
"version": "2.11.8",
|
||||||
|
"resolved": "https://registry.npmjs.org/@popperjs/core/-/core-2.11.8.tgz",
|
||||||
|
"integrity": "sha512-P1st0aksCrn9sGZhp8GMYwBnQsbvAWsZAX44oXNNvLHGqAOcoVxmjZiohstwQ7SqKnbR47akdNi+uleWD8+g6A==",
|
||||||
|
"license": "MIT",
|
||||||
|
"funding": {
|
||||||
|
"type": "opencollective",
|
||||||
|
"url": "https://opencollective.com/popperjs"
|
||||||
|
}
|
||||||
|
},
|
||||||
"node_modules/@react-aria/focus": {
|
"node_modules/@react-aria/focus": {
|
||||||
"version": "3.20.5",
|
"version": "3.20.5",
|
||||||
"resolved": "https://registry.npmjs.org/@react-aria/focus/-/focus-3.20.5.tgz",
|
"resolved": "https://registry.npmjs.org/@react-aria/focus/-/focus-3.20.5.tgz",
|
||||||
@@ -2236,17 +2612,21 @@
|
|||||||
"undici-types": "~6.21.0"
|
"undici-types": "~6.21.0"
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
|
"node_modules/@types/parse-json": {
|
||||||
|
"version": "4.0.2",
|
||||||
|
"resolved": "https://registry.npmjs.org/@types/parse-json/-/parse-json-4.0.2.tgz",
|
||||||
|
"integrity": "sha512-dISoDXWWQwUquiKsyZ4Ng+HX2KsPL7LyHKHQwgGFEA3IaKac4Obd+h2a/a6waisAoepJlBcx9paWqjA8/HVjCw==",
|
||||||
|
"license": "MIT"
|
||||||
|
},
|
||||||
"node_modules/@types/prop-types": {
|
"node_modules/@types/prop-types": {
|
||||||
"version": "15.7.15",
|
"version": "15.7.15",
|
||||||
"resolved": "https://registry.npmjs.org/@types/prop-types/-/prop-types-15.7.15.tgz",
|
"resolved": "https://registry.npmjs.org/@types/prop-types/-/prop-types-15.7.15.tgz",
|
||||||
"integrity": "sha512-F6bEyamV9jKGAFBEmlQnesRPGOQqS2+Uwi0Em15xenOxHaf2hv6L8YCVn3rPdPJOiJfPiCnLIRyvwVaqMY3MIw==",
|
"integrity": "sha512-F6bEyamV9jKGAFBEmlQnesRPGOQqS2+Uwi0Em15xenOxHaf2hv6L8YCVn3rPdPJOiJfPiCnLIRyvwVaqMY3MIw=="
|
||||||
"devOptional": true
|
|
||||||
},
|
},
|
||||||
"node_modules/@types/react": {
|
"node_modules/@types/react": {
|
||||||
"version": "18.3.23",
|
"version": "18.3.23",
|
||||||
"resolved": "https://registry.npmjs.org/@types/react/-/react-18.3.23.tgz",
|
"resolved": "https://registry.npmjs.org/@types/react/-/react-18.3.23.tgz",
|
||||||
"integrity": "sha512-/LDXMQh55EzZQ0uVAZmKKhfENivEvWz6E+EYzh+/MCjMhNsotd+ZHhBGIjFDTi6+fz0OhQQQLbTgdQIxxCsC0w==",
|
"integrity": "sha512-/LDXMQh55EzZQ0uVAZmKKhfENivEvWz6E+EYzh+/MCjMhNsotd+ZHhBGIjFDTi6+fz0OhQQQLbTgdQIxxCsC0w==",
|
||||||
"devOptional": true,
|
|
||||||
"dependencies": {
|
"dependencies": {
|
||||||
"@types/prop-types": "*",
|
"@types/prop-types": "*",
|
||||||
"csstype": "^3.0.2"
|
"csstype": "^3.0.2"
|
||||||
@@ -2270,6 +2650,15 @@
|
|||||||
"@types/react": "*"
|
"@types/react": "*"
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
|
"node_modules/@types/react-transition-group": {
|
||||||
|
"version": "4.4.12",
|
||||||
|
"resolved": "https://registry.npmjs.org/@types/react-transition-group/-/react-transition-group-4.4.12.tgz",
|
||||||
|
"integrity": "sha512-8TV6R3h2j7a91c+1DXdJi3Syo69zzIZbz7Lg5tORM5LEJG7X/E6a1V3drRyBRZq7/utz7A+c4OgYLiLcYGHG6w==",
|
||||||
|
"license": "MIT",
|
||||||
|
"peerDependencies": {
|
||||||
|
"@types/react": "*"
|
||||||
|
}
|
||||||
|
},
|
||||||
"node_modules/@types/redux-mock-store": {
|
"node_modules/@types/redux-mock-store": {
|
||||||
"version": "1.5.0",
|
"version": "1.5.0",
|
||||||
"resolved": "https://registry.npmjs.org/@types/redux-mock-store/-/redux-mock-store-1.5.0.tgz",
|
"resolved": "https://registry.npmjs.org/@types/redux-mock-store/-/redux-mock-store-1.5.0.tgz",
|
||||||
@@ -3347,6 +3736,21 @@
|
|||||||
"node": "^14.15.0 || ^16.10.0 || >=18.0.0"
|
"node": "^14.15.0 || ^16.10.0 || >=18.0.0"
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
|
"node_modules/babel-plugin-macros": {
|
||||||
|
"version": "3.1.0",
|
||||||
|
"resolved": "https://registry.npmjs.org/babel-plugin-macros/-/babel-plugin-macros-3.1.0.tgz",
|
||||||
|
"integrity": "sha512-Cg7TFGpIr01vOQNODXOOaGz2NpCU5gl8x1qJFbb6hbZxR7XrcE2vtbAsTAbJ7/xwJtUuJEw8K8Zr/AE0LHlesg==",
|
||||||
|
"license": "MIT",
|
||||||
|
"dependencies": {
|
||||||
|
"@babel/runtime": "^7.12.5",
|
||||||
|
"cosmiconfig": "^7.0.0",
|
||||||
|
"resolve": "^1.19.0"
|
||||||
|
},
|
||||||
|
"engines": {
|
||||||
|
"node": ">=10",
|
||||||
|
"npm": ">=6"
|
||||||
|
}
|
||||||
|
},
|
||||||
"node_modules/babel-preset-current-node-syntax": {
|
"node_modules/babel-preset-current-node-syntax": {
|
||||||
"version": "1.1.0",
|
"version": "1.1.0",
|
||||||
"resolved": "https://registry.npmjs.org/babel-preset-current-node-syntax/-/babel-preset-current-node-syntax-1.1.0.tgz",
|
"resolved": "https://registry.npmjs.org/babel-preset-current-node-syntax/-/babel-preset-current-node-syntax-1.1.0.tgz",
|
||||||
@@ -3574,7 +3978,6 @@
|
|||||||
"version": "3.1.0",
|
"version": "3.1.0",
|
||||||
"resolved": "https://registry.npmjs.org/callsites/-/callsites-3.1.0.tgz",
|
"resolved": "https://registry.npmjs.org/callsites/-/callsites-3.1.0.tgz",
|
||||||
"integrity": "sha512-P8BjAsXvZS+VIDUI11hHCQEv74YT67YUi5JJFNWIqL235sBmjX4+qx9Muvls5ivyNENctx46xQLQ3aTuE7ssaQ==",
|
"integrity": "sha512-P8BjAsXvZS+VIDUI11hHCQEv74YT67YUi5JJFNWIqL235sBmjX4+qx9Muvls5ivyNENctx46xQLQ3aTuE7ssaQ==",
|
||||||
"dev": true,
|
|
||||||
"engines": {
|
"engines": {
|
||||||
"node": ">=6"
|
"node": ">=6"
|
||||||
}
|
}
|
||||||
@@ -3825,6 +4228,31 @@
|
|||||||
"integrity": "sha512-Kvp459HrV2FEJ1CAsi1Ku+MY3kasH19TFykTz2xWmMeq6bk2NU3XXvfJ+Q61m0xktWwt+1HSYf3JZsTms3aRJg==",
|
"integrity": "sha512-Kvp459HrV2FEJ1CAsi1Ku+MY3kasH19TFykTz2xWmMeq6bk2NU3XXvfJ+Q61m0xktWwt+1HSYf3JZsTms3aRJg==",
|
||||||
"dev": true
|
"dev": true
|
||||||
},
|
},
|
||||||
|
"node_modules/cosmiconfig": {
|
||||||
|
"version": "7.1.0",
|
||||||
|
"resolved": "https://registry.npmjs.org/cosmiconfig/-/cosmiconfig-7.1.0.tgz",
|
||||||
|
"integrity": "sha512-AdmX6xUzdNASswsFtmwSt7Vj8po9IuqXm0UXz7QKPuEUmPB4XyjGfaAr2PSuELMwkRMVH1EpIkX5bTZGRB3eCA==",
|
||||||
|
"license": "MIT",
|
||||||
|
"dependencies": {
|
||||||
|
"@types/parse-json": "^4.0.0",
|
||||||
|
"import-fresh": "^3.2.1",
|
||||||
|
"parse-json": "^5.0.0",
|
||||||
|
"path-type": "^4.0.0",
|
||||||
|
"yaml": "^1.10.0"
|
||||||
|
},
|
||||||
|
"engines": {
|
||||||
|
"node": ">=10"
|
||||||
|
}
|
||||||
|
},
|
||||||
|
"node_modules/cosmiconfig/node_modules/yaml": {
|
||||||
|
"version": "1.10.2",
|
||||||
|
"resolved": "https://registry.npmjs.org/yaml/-/yaml-1.10.2.tgz",
|
||||||
|
"integrity": "sha512-r3vXyErRCYJ7wg28yvBY5VSoAF8ZvlcW9/BwUzEtUsjvX/DKs24dIkuwjtuprwJJHsbyUbLApepYTR1BN4uHrg==",
|
||||||
|
"license": "ISC",
|
||||||
|
"engines": {
|
||||||
|
"node": ">= 6"
|
||||||
|
}
|
||||||
|
},
|
||||||
"node_modules/create-jest": {
|
"node_modules/create-jest": {
|
||||||
"version": "29.7.0",
|
"version": "29.7.0",
|
||||||
"resolved": "https://registry.npmjs.org/create-jest/-/create-jest-29.7.0.tgz",
|
"resolved": "https://registry.npmjs.org/create-jest/-/create-jest-29.7.0.tgz",
|
||||||
@@ -4143,7 +4571,6 @@
|
|||||||
"version": "4.4.1",
|
"version": "4.4.1",
|
||||||
"resolved": "https://registry.npmjs.org/debug/-/debug-4.4.1.tgz",
|
"resolved": "https://registry.npmjs.org/debug/-/debug-4.4.1.tgz",
|
||||||
"integrity": "sha512-KcKCqiftBJcZr++7ykoDIEwSa3XWowTfNPo92BYxjXiyYEVrUQh2aLyhxBCwww+heortUFxEJYcRzosstTEBYQ==",
|
"integrity": "sha512-KcKCqiftBJcZr++7ykoDIEwSa3XWowTfNPo92BYxjXiyYEVrUQh2aLyhxBCwww+heortUFxEJYcRzosstTEBYQ==",
|
||||||
"dev": true,
|
|
||||||
"dependencies": {
|
"dependencies": {
|
||||||
"ms": "^2.1.3"
|
"ms": "^2.1.3"
|
||||||
},
|
},
|
||||||
@@ -4418,7 +4845,6 @@
|
|||||||
"version": "1.3.2",
|
"version": "1.3.2",
|
||||||
"resolved": "https://registry.npmjs.org/error-ex/-/error-ex-1.3.2.tgz",
|
"resolved": "https://registry.npmjs.org/error-ex/-/error-ex-1.3.2.tgz",
|
||||||
"integrity": "sha512-7dFHNmqeFSEt2ZBsCriorKnn3Z2pj+fd9kmI6QoWw4//DL+icEBfc0U7qJCisqrTsKTjw4fNFy2pW9OqStD84g==",
|
"integrity": "sha512-7dFHNmqeFSEt2ZBsCriorKnn3Z2pj+fd9kmI6QoWw4//DL+icEBfc0U7qJCisqrTsKTjw4fNFy2pW9OqStD84g==",
|
||||||
"dev": true,
|
|
||||||
"dependencies": {
|
"dependencies": {
|
||||||
"is-arrayish": "^0.2.1"
|
"is-arrayish": "^0.2.1"
|
||||||
}
|
}
|
||||||
@@ -4600,6 +5026,18 @@
|
|||||||
"node": ">=6"
|
"node": ">=6"
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
|
"node_modules/escape-string-regexp": {
|
||||||
|
"version": "4.0.0",
|
||||||
|
"resolved": "https://registry.npmjs.org/escape-string-regexp/-/escape-string-regexp-4.0.0.tgz",
|
||||||
|
"integrity": "sha512-TtpcNJ3XAzx3Gq8sWRzJaVajRs0uVxA2YAkdb1jm2YkPz4G6egUFAyA3n5vtEIZefPk5Wa4UXbKuS5fKkJWdgA==",
|
||||||
|
"license": "MIT",
|
||||||
|
"engines": {
|
||||||
|
"node": ">=10"
|
||||||
|
},
|
||||||
|
"funding": {
|
||||||
|
"url": "https://github.com/sponsors/sindresorhus"
|
||||||
|
}
|
||||||
|
},
|
||||||
"node_modules/escodegen": {
|
"node_modules/escodegen": {
|
||||||
"version": "2.1.0",
|
"version": "2.1.0",
|
||||||
"resolved": "https://registry.npmjs.org/escodegen/-/escodegen-2.1.0.tgz",
|
"resolved": "https://registry.npmjs.org/escodegen/-/escodegen-2.1.0.tgz",
|
||||||
@@ -5007,18 +5445,6 @@
|
|||||||
"integrity": "sha512-8+9WqebbFzpX9OR+Wa6O29asIogeRMzcGtAINdpMHHyAg10f05aSFVBbcEqGf/PXw1EjAZ+q2/bEBg3DvurK3Q==",
|
"integrity": "sha512-8+9WqebbFzpX9OR+Wa6O29asIogeRMzcGtAINdpMHHyAg10f05aSFVBbcEqGf/PXw1EjAZ+q2/bEBg3DvurK3Q==",
|
||||||
"dev": true
|
"dev": true
|
||||||
},
|
},
|
||||||
"node_modules/eslint/node_modules/escape-string-regexp": {
|
|
||||||
"version": "4.0.0",
|
|
||||||
"resolved": "https://registry.npmjs.org/escape-string-regexp/-/escape-string-regexp-4.0.0.tgz",
|
|
||||||
"integrity": "sha512-TtpcNJ3XAzx3Gq8sWRzJaVajRs0uVxA2YAkdb1jm2YkPz4G6egUFAyA3n5vtEIZefPk5Wa4UXbKuS5fKkJWdgA==",
|
|
||||||
"dev": true,
|
|
||||||
"engines": {
|
|
||||||
"node": ">=10"
|
|
||||||
},
|
|
||||||
"funding": {
|
|
||||||
"url": "https://github.com/sponsors/sindresorhus"
|
|
||||||
}
|
|
||||||
},
|
|
||||||
"node_modules/eslint/node_modules/find-up": {
|
"node_modules/eslint/node_modules/find-up": {
|
||||||
"version": "5.0.0",
|
"version": "5.0.0",
|
||||||
"resolved": "https://registry.npmjs.org/find-up/-/find-up-5.0.0.tgz",
|
"resolved": "https://registry.npmjs.org/find-up/-/find-up-5.0.0.tgz",
|
||||||
@@ -5359,6 +5785,12 @@
|
|||||||
"node": ">=8"
|
"node": ">=8"
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
|
"node_modules/find-root": {
|
||||||
|
"version": "1.1.0",
|
||||||
|
"resolved": "https://registry.npmjs.org/find-root/-/find-root-1.1.0.tgz",
|
||||||
|
"integrity": "sha512-NKfW6bec6GfKc0SGx1e07QZY9PE99u0Bft/0rzSD5k3sO/vwkVUpDUKVm5Gpp5Ue3YfShPFTX2070tDs5kB9Ng==",
|
||||||
|
"license": "MIT"
|
||||||
|
},
|
||||||
"node_modules/find-up": {
|
"node_modules/find-up": {
|
||||||
"version": "4.1.0",
|
"version": "4.1.0",
|
||||||
"resolved": "https://registry.npmjs.org/find-up/-/find-up-4.1.0.tgz",
|
"resolved": "https://registry.npmjs.org/find-up/-/find-up-4.1.0.tgz",
|
||||||
@@ -5514,7 +5946,6 @@
|
|||||||
"version": "1.1.2",
|
"version": "1.1.2",
|
||||||
"resolved": "https://registry.npmjs.org/function-bind/-/function-bind-1.1.2.tgz",
|
"resolved": "https://registry.npmjs.org/function-bind/-/function-bind-1.1.2.tgz",
|
||||||
"integrity": "sha512-7XHNxH7qX9xG5mIwxkhumTox/MIRNcOgDrxWsMt2pAr23WHp6MrRlN7FBSFpCpr+oVO0F744iUgR82nJMfG2SA==",
|
"integrity": "sha512-7XHNxH7qX9xG5mIwxkhumTox/MIRNcOgDrxWsMt2pAr23WHp6MrRlN7FBSFpCpr+oVO0F744iUgR82nJMfG2SA==",
|
||||||
"dev": true,
|
|
||||||
"funding": {
|
"funding": {
|
||||||
"url": "https://github.com/sponsors/ljharb"
|
"url": "https://github.com/sponsors/ljharb"
|
||||||
}
|
}
|
||||||
@@ -5689,7 +6120,6 @@
|
|||||||
"version": "11.12.0",
|
"version": "11.12.0",
|
||||||
"resolved": "https://registry.npmjs.org/globals/-/globals-11.12.0.tgz",
|
"resolved": "https://registry.npmjs.org/globals/-/globals-11.12.0.tgz",
|
||||||
"integrity": "sha512-WOBp/EEGUiIsJSp7wcv/y6MO+lV9UoncWqxuFfm8eBwzWNgyfBd6Gz+IeKQ9jCmyhoH99g15M3T+QaVHFjizVA==",
|
"integrity": "sha512-WOBp/EEGUiIsJSp7wcv/y6MO+lV9UoncWqxuFfm8eBwzWNgyfBd6Gz+IeKQ9jCmyhoH99g15M3T+QaVHFjizVA==",
|
||||||
"dev": true,
|
|
||||||
"engines": {
|
"engines": {
|
||||||
"node": ">=4"
|
"node": ">=4"
|
||||||
}
|
}
|
||||||
@@ -5826,7 +6256,6 @@
|
|||||||
"version": "2.0.2",
|
"version": "2.0.2",
|
||||||
"resolved": "https://registry.npmjs.org/hasown/-/hasown-2.0.2.tgz",
|
"resolved": "https://registry.npmjs.org/hasown/-/hasown-2.0.2.tgz",
|
||||||
"integrity": "sha512-0hJU9SCPvmMzIBdZFqNPXWa6dqh7WdH0cII9y+CyS8rG3nL48Bclra9HmKhVVUHyPWNH5Y7xDwAB7bfgSjkUMQ==",
|
"integrity": "sha512-0hJU9SCPvmMzIBdZFqNPXWa6dqh7WdH0cII9y+CyS8rG3nL48Bclra9HmKhVVUHyPWNH5Y7xDwAB7bfgSjkUMQ==",
|
||||||
"dev": true,
|
|
||||||
"dependencies": {
|
"dependencies": {
|
||||||
"function-bind": "^1.1.2"
|
"function-bind": "^1.1.2"
|
||||||
},
|
},
|
||||||
@@ -5834,6 +6263,21 @@
|
|||||||
"node": ">= 0.4"
|
"node": ">= 0.4"
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
|
"node_modules/hoist-non-react-statics": {
|
||||||
|
"version": "3.3.2",
|
||||||
|
"resolved": "https://registry.npmjs.org/hoist-non-react-statics/-/hoist-non-react-statics-3.3.2.tgz",
|
||||||
|
"integrity": "sha512-/gGivxi8JPKWNm/W0jSmzcMPpfpPLc3dY/6GxhX2hQ9iGj3aDfklV4ET7NjKpSinLpJ5vafa9iiGIEZg10SfBw==",
|
||||||
|
"license": "BSD-3-Clause",
|
||||||
|
"dependencies": {
|
||||||
|
"react-is": "^16.7.0"
|
||||||
|
}
|
||||||
|
},
|
||||||
|
"node_modules/hoist-non-react-statics/node_modules/react-is": {
|
||||||
|
"version": "16.13.1",
|
||||||
|
"resolved": "https://registry.npmjs.org/react-is/-/react-is-16.13.1.tgz",
|
||||||
|
"integrity": "sha512-24e6ynE2H+OKt4kqsOvNd8kBpV65zoxbA4BVsEOB3ARVWQki/DHzaUoC5KuON/BiccDaCCTZBuOcfZs70kR8bQ==",
|
||||||
|
"license": "MIT"
|
||||||
|
},
|
||||||
"node_modules/html-encoding-sniffer": {
|
"node_modules/html-encoding-sniffer": {
|
||||||
"version": "3.0.0",
|
"version": "3.0.0",
|
||||||
"resolved": "https://registry.npmjs.org/html-encoding-sniffer/-/html-encoding-sniffer-3.0.0.tgz",
|
"resolved": "https://registry.npmjs.org/html-encoding-sniffer/-/html-encoding-sniffer-3.0.0.tgz",
|
||||||
@@ -5945,7 +6389,6 @@
|
|||||||
"version": "3.3.1",
|
"version": "3.3.1",
|
||||||
"resolved": "https://registry.npmjs.org/import-fresh/-/import-fresh-3.3.1.tgz",
|
"resolved": "https://registry.npmjs.org/import-fresh/-/import-fresh-3.3.1.tgz",
|
||||||
"integrity": "sha512-TR3KfrTZTYLPB6jUjfx6MF9WcWrHL9su5TObK4ZkYgBdWKPOFoSoQIdEuTuR82pmtxH2spWG9h6etwfr1pLBqQ==",
|
"integrity": "sha512-TR3KfrTZTYLPB6jUjfx6MF9WcWrHL9su5TObK4ZkYgBdWKPOFoSoQIdEuTuR82pmtxH2spWG9h6etwfr1pLBqQ==",
|
||||||
"dev": true,
|
|
||||||
"dependencies": {
|
"dependencies": {
|
||||||
"parent-module": "^1.0.0",
|
"parent-module": "^1.0.0",
|
||||||
"resolve-from": "^4.0.0"
|
"resolve-from": "^4.0.0"
|
||||||
@@ -5961,7 +6404,6 @@
|
|||||||
"version": "4.0.0",
|
"version": "4.0.0",
|
||||||
"resolved": "https://registry.npmjs.org/resolve-from/-/resolve-from-4.0.0.tgz",
|
"resolved": "https://registry.npmjs.org/resolve-from/-/resolve-from-4.0.0.tgz",
|
||||||
"integrity": "sha512-pb/MYmXstAkysRFx8piNI1tGFNQIFA3vkE3Gq4EuA1dF6gHp/+vgZqsCGJapvy8N3Q+4o7FwvquPJcnZ7RYy4g==",
|
"integrity": "sha512-pb/MYmXstAkysRFx8piNI1tGFNQIFA3vkE3Gq4EuA1dF6gHp/+vgZqsCGJapvy8N3Q+4o7FwvquPJcnZ7RYy4g==",
|
||||||
"dev": true,
|
|
||||||
"engines": {
|
"engines": {
|
||||||
"node": ">=4"
|
"node": ">=4"
|
||||||
}
|
}
|
||||||
@@ -6062,8 +6504,7 @@
|
|||||||
"node_modules/is-arrayish": {
|
"node_modules/is-arrayish": {
|
||||||
"version": "0.2.1",
|
"version": "0.2.1",
|
||||||
"resolved": "https://registry.npmjs.org/is-arrayish/-/is-arrayish-0.2.1.tgz",
|
"resolved": "https://registry.npmjs.org/is-arrayish/-/is-arrayish-0.2.1.tgz",
|
||||||
"integrity": "sha512-zz06S8t0ozoDXMG+ube26zeCTNXcKIPJZJi8hBrF4idCLms4CG9QtK7qBl1boi5ODzFpjswb5JPmHCbMpjaYzg==",
|
"integrity": "sha512-zz06S8t0ozoDXMG+ube26zeCTNXcKIPJZJi8hBrF4idCLms4CG9QtK7qBl1boi5ODzFpjswb5JPmHCbMpjaYzg=="
|
||||||
"dev": true
|
|
||||||
},
|
},
|
||||||
"node_modules/is-async-function": {
|
"node_modules/is-async-function": {
|
||||||
"version": "2.1.1",
|
"version": "2.1.1",
|
||||||
@@ -6152,7 +6593,6 @@
|
|||||||
"version": "2.16.1",
|
"version": "2.16.1",
|
||||||
"resolved": "https://registry.npmjs.org/is-core-module/-/is-core-module-2.16.1.tgz",
|
"resolved": "https://registry.npmjs.org/is-core-module/-/is-core-module-2.16.1.tgz",
|
||||||
"integrity": "sha512-UfoeMA6fIJ8wTYFEUjelnaGI67v6+N7qXJEvQuIGa99l4xsCruSYOVSQ0uPANn4dAzm8lkYPaKLrrijLq7x23w==",
|
"integrity": "sha512-UfoeMA6fIJ8wTYFEUjelnaGI67v6+N7qXJEvQuIGa99l4xsCruSYOVSQ0uPANn4dAzm8lkYPaKLrrijLq7x23w==",
|
||||||
"dev": true,
|
|
||||||
"dependencies": {
|
"dependencies": {
|
||||||
"hasown": "^2.0.2"
|
"hasown": "^2.0.2"
|
||||||
},
|
},
|
||||||
@@ -7652,7 +8092,6 @@
|
|||||||
"version": "3.1.0",
|
"version": "3.1.0",
|
||||||
"resolved": "https://registry.npmjs.org/jsesc/-/jsesc-3.1.0.tgz",
|
"resolved": "https://registry.npmjs.org/jsesc/-/jsesc-3.1.0.tgz",
|
||||||
"integrity": "sha512-/sM3dO2FOzXjKQhJuo0Q173wf2KOo8t4I8vHy6lF9poUp7bKT0/NHE8fPX23PwfhnykfqnC2xRxOnVw5XuGIaA==",
|
"integrity": "sha512-/sM3dO2FOzXjKQhJuo0Q173wf2KOo8t4I8vHy6lF9poUp7bKT0/NHE8fPX23PwfhnykfqnC2xRxOnVw5XuGIaA==",
|
||||||
"dev": true,
|
|
||||||
"bin": {
|
"bin": {
|
||||||
"jsesc": "bin/jsesc"
|
"jsesc": "bin/jsesc"
|
||||||
},
|
},
|
||||||
@@ -7669,8 +8108,7 @@
|
|||||||
"node_modules/json-parse-even-better-errors": {
|
"node_modules/json-parse-even-better-errors": {
|
||||||
"version": "2.3.1",
|
"version": "2.3.1",
|
||||||
"resolved": "https://registry.npmjs.org/json-parse-even-better-errors/-/json-parse-even-better-errors-2.3.1.tgz",
|
"resolved": "https://registry.npmjs.org/json-parse-even-better-errors/-/json-parse-even-better-errors-2.3.1.tgz",
|
||||||
"integrity": "sha512-xyFwyhro/JEof6Ghe2iz2NcXoj2sloNsWr/XsERDK/oiPCfaNhl5ONfp+jQdAZRQQ0IJWNzH9zIZF7li91kh2w==",
|
"integrity": "sha512-xyFwyhro/JEof6Ghe2iz2NcXoj2sloNsWr/XsERDK/oiPCfaNhl5ONfp+jQdAZRQQ0IJWNzH9zIZF7li91kh2w=="
|
||||||
"dev": true
|
|
||||||
},
|
},
|
||||||
"node_modules/json-schema-traverse": {
|
"node_modules/json-schema-traverse": {
|
||||||
"version": "0.4.1",
|
"version": "0.4.1",
|
||||||
@@ -7832,8 +8270,7 @@
|
|||||||
"node_modules/lines-and-columns": {
|
"node_modules/lines-and-columns": {
|
||||||
"version": "1.2.4",
|
"version": "1.2.4",
|
||||||
"resolved": "https://registry.npmjs.org/lines-and-columns/-/lines-and-columns-1.2.4.tgz",
|
"resolved": "https://registry.npmjs.org/lines-and-columns/-/lines-and-columns-1.2.4.tgz",
|
||||||
"integrity": "sha512-7ylylesZQ/PV29jhEDl3Ufjo6ZX7gCqJr5F7PKrqc93v7fzSymt1BpwEU8nAUXs8qzzvqhbjhK5QZg6Mt/HkBg==",
|
"integrity": "sha512-7ylylesZQ/PV29jhEDl3Ufjo6ZX7gCqJr5F7PKrqc93v7fzSymt1BpwEU8nAUXs8qzzvqhbjhK5QZg6Mt/HkBg=="
|
||||||
"dev": true
|
|
||||||
},
|
},
|
||||||
"node_modules/locate-path": {
|
"node_modules/locate-path": {
|
||||||
"version": "5.0.0",
|
"version": "5.0.0",
|
||||||
@@ -8578,7 +9015,6 @@
|
|||||||
"version": "1.0.1",
|
"version": "1.0.1",
|
||||||
"resolved": "https://registry.npmjs.org/parent-module/-/parent-module-1.0.1.tgz",
|
"resolved": "https://registry.npmjs.org/parent-module/-/parent-module-1.0.1.tgz",
|
||||||
"integrity": "sha512-GQ2EWRpQV8/o+Aw8YqtfZZPfNRWZYkbidE9k5rpl/hC3vtHHBfGm2Ifi6qWV+coDGkrUKZAxE3Lot5kcsRlh+g==",
|
"integrity": "sha512-GQ2EWRpQV8/o+Aw8YqtfZZPfNRWZYkbidE9k5rpl/hC3vtHHBfGm2Ifi6qWV+coDGkrUKZAxE3Lot5kcsRlh+g==",
|
||||||
"dev": true,
|
|
||||||
"dependencies": {
|
"dependencies": {
|
||||||
"callsites": "^3.0.0"
|
"callsites": "^3.0.0"
|
||||||
},
|
},
|
||||||
@@ -8590,7 +9026,6 @@
|
|||||||
"version": "5.2.0",
|
"version": "5.2.0",
|
||||||
"resolved": "https://registry.npmjs.org/parse-json/-/parse-json-5.2.0.tgz",
|
"resolved": "https://registry.npmjs.org/parse-json/-/parse-json-5.2.0.tgz",
|
||||||
"integrity": "sha512-ayCKvm/phCGxOkYRSCM82iDwct8/EonSEgCSxWxD7ve6jHggsFl4fZVQBPRNgQoKiuV/odhFrGzQXZwbifC8Rg==",
|
"integrity": "sha512-ayCKvm/phCGxOkYRSCM82iDwct8/EonSEgCSxWxD7ve6jHggsFl4fZVQBPRNgQoKiuV/odhFrGzQXZwbifC8Rg==",
|
||||||
"dev": true,
|
|
||||||
"dependencies": {
|
"dependencies": {
|
||||||
"@babel/code-frame": "^7.0.0",
|
"@babel/code-frame": "^7.0.0",
|
||||||
"error-ex": "^1.3.1",
|
"error-ex": "^1.3.1",
|
||||||
@@ -8646,8 +9081,7 @@
|
|||||||
"node_modules/path-parse": {
|
"node_modules/path-parse": {
|
||||||
"version": "1.0.7",
|
"version": "1.0.7",
|
||||||
"resolved": "https://registry.npmjs.org/path-parse/-/path-parse-1.0.7.tgz",
|
"resolved": "https://registry.npmjs.org/path-parse/-/path-parse-1.0.7.tgz",
|
||||||
"integrity": "sha512-LDJzPVEEEPR+y48z93A0Ed0yXb8pAByGWo/k5YYdYgpY2/2EsOsksJrq7lOHxryrVOn1ejG6oAp8ahvOIQD8sw==",
|
"integrity": "sha512-LDJzPVEEEPR+y48z93A0Ed0yXb8pAByGWo/k5YYdYgpY2/2EsOsksJrq7lOHxryrVOn1ejG6oAp8ahvOIQD8sw=="
|
||||||
"dev": true
|
|
||||||
},
|
},
|
||||||
"node_modules/path-scurry": {
|
"node_modules/path-scurry": {
|
||||||
"version": "1.11.1",
|
"version": "1.11.1",
|
||||||
@@ -8671,6 +9105,15 @@
|
|||||||
"integrity": "sha512-JNAzZcXrCt42VGLuYz0zfAzDfAvJWW6AfYlDBQyDV5DClI2m5sAmK+OIO7s59XfsRsWHp02jAJrRadPRGTt6SQ==",
|
"integrity": "sha512-JNAzZcXrCt42VGLuYz0zfAzDfAvJWW6AfYlDBQyDV5DClI2m5sAmK+OIO7s59XfsRsWHp02jAJrRadPRGTt6SQ==",
|
||||||
"dev": true
|
"dev": true
|
||||||
},
|
},
|
||||||
|
"node_modules/path-type": {
|
||||||
|
"version": "4.0.0",
|
||||||
|
"resolved": "https://registry.npmjs.org/path-type/-/path-type-4.0.0.tgz",
|
||||||
|
"integrity": "sha512-gDKb8aZMDeD/tZWs9P6+q0J9Mwkdl6xMV8TjnGP3qJVJ06bdMgkbBlLU8IdfOsIsFz2BW1rNVT3XuNEl8zPAvw==",
|
||||||
|
"license": "MIT",
|
||||||
|
"engines": {
|
||||||
|
"node": ">=8"
|
||||||
|
}
|
||||||
|
},
|
||||||
"node_modules/pathe": {
|
"node_modules/pathe": {
|
||||||
"version": "1.1.2",
|
"version": "1.1.2",
|
||||||
"resolved": "https://registry.npmjs.org/pathe/-/pathe-1.1.2.tgz",
|
"resolved": "https://registry.npmjs.org/pathe/-/pathe-1.1.2.tgz",
|
||||||
@@ -9456,7 +9899,6 @@
|
|||||||
"version": "1.22.10",
|
"version": "1.22.10",
|
||||||
"resolved": "https://registry.npmjs.org/resolve/-/resolve-1.22.10.tgz",
|
"resolved": "https://registry.npmjs.org/resolve/-/resolve-1.22.10.tgz",
|
||||||
"integrity": "sha512-NPRy+/ncIMeDlTAsuqwKIiferiawhefFJtkNSW0qZJEqMEb+qBt/77B/jGeeek+F0uOeN05CDa6HXbbIgtVX4w==",
|
"integrity": "sha512-NPRy+/ncIMeDlTAsuqwKIiferiawhefFJtkNSW0qZJEqMEb+qBt/77B/jGeeek+F0uOeN05CDa6HXbbIgtVX4w==",
|
||||||
"dev": true,
|
|
||||||
"dependencies": {
|
"dependencies": {
|
||||||
"is-core-module": "^2.16.0",
|
"is-core-module": "^2.16.0",
|
||||||
"path-parse": "^1.0.7",
|
"path-parse": "^1.0.7",
|
||||||
@@ -10190,6 +10632,12 @@
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
|
"node_modules/stylis": {
|
||||||
|
"version": "4.2.0",
|
||||||
|
"resolved": "https://registry.npmjs.org/stylis/-/stylis-4.2.0.tgz",
|
||||||
|
"integrity": "sha512-Orov6g6BB1sDfYgzWfTHDOxamtX1bE/zo104Dh9e6fqJ3PooipYyfJ0pUmrZO2wAvO8YbEyeFrkV91XTsGMSrw==",
|
||||||
|
"license": "MIT"
|
||||||
|
},
|
||||||
"node_modules/sucrase": {
|
"node_modules/sucrase": {
|
||||||
"version": "3.35.0",
|
"version": "3.35.0",
|
||||||
"resolved": "https://registry.npmjs.org/sucrase/-/sucrase-3.35.0.tgz",
|
"resolved": "https://registry.npmjs.org/sucrase/-/sucrase-3.35.0.tgz",
|
||||||
@@ -10281,7 +10729,6 @@
|
|||||||
"version": "1.0.0",
|
"version": "1.0.0",
|
||||||
"resolved": "https://registry.npmjs.org/supports-preserve-symlinks-flag/-/supports-preserve-symlinks-flag-1.0.0.tgz",
|
"resolved": "https://registry.npmjs.org/supports-preserve-symlinks-flag/-/supports-preserve-symlinks-flag-1.0.0.tgz",
|
||||||
"integrity": "sha512-ot0WnXS9fgdkgIcePe6RHNk1WA8+muPa6cSjeR3V8K27q9BB1rTE3R1p7Hv0z1ZyAc8s6Vvv8DIyWf681MAt0w==",
|
"integrity": "sha512-ot0WnXS9fgdkgIcePe6RHNk1WA8+muPa6cSjeR3V8K27q9BB1rTE3R1p7Hv0z1ZyAc8s6Vvv8DIyWf681MAt0w==",
|
||||||
"dev": true,
|
|
||||||
"engines": {
|
"engines": {
|
||||||
"node": ">= 0.4"
|
"node": ">= 0.4"
|
||||||
},
|
},
|
||||||
|
|||||||
@@ -1,6 +1,6 @@
|
|||||||
{
|
{
|
||||||
"name": "cpl-v4",
|
"name": "cpl-v4",
|
||||||
"version": "1.6.879",
|
"version": "1.6.913",
|
||||||
"private": true,
|
"private": true,
|
||||||
"scripts": {
|
"scripts": {
|
||||||
"dev": "next dev -p 3000",
|
"dev": "next dev -p 3000",
|
||||||
@@ -71,7 +71,10 @@
|
|||||||
"react-spinners": "^0.14.1",
|
"react-spinners": "^0.14.1",
|
||||||
"react-toastify": "^10.0.6",
|
"react-toastify": "^10.0.6",
|
||||||
"recharts": "^2.15.1",
|
"recharts": "^2.15.1",
|
||||||
"redux": "^5.0.1"
|
"redux": "^5.0.1",
|
||||||
|
"@mui/material": "^6.0.0",
|
||||||
|
"@emotion/react": "^11.13.0",
|
||||||
|
"@emotion/styled": "^11.13.0"
|
||||||
},
|
},
|
||||||
"devDependencies": {
|
"devDependencies": {
|
||||||
"@playwright/test": "^1.54.2",
|
"@playwright/test": "^1.54.2",
|
||||||
|
|||||||
@@ -13,7 +13,6 @@ import Footer from "@/components/footer/Footer";
|
|||||||
import { store } from "@/redux/store";
|
import { store } from "@/redux/store";
|
||||||
import Script from "next/script";
|
import Script from "next/script";
|
||||||
import DeviceEventsBridge from "@/components/common/DeviceEventsBridge";
|
import DeviceEventsBridge from "@/components/common/DeviceEventsBridge";
|
||||||
import { usePathname } from "next/navigation";
|
|
||||||
|
|
||||||
// Thunks importieren
|
// Thunks importieren
|
||||||
import { getKueDataThunk } from "@/redux/thunks/getKueDataThunk";
|
import { getKueDataThunk } from "@/redux/thunks/getKueDataThunk";
|
||||||
@@ -39,22 +38,36 @@ if (typeof window !== "undefined") {
|
|||||||
}
|
}
|
||||||
|
|
||||||
import "@/styles/globals.css";
|
import "@/styles/globals.css";
|
||||||
|
import CssBaseline from "@mui/material/CssBaseline";
|
||||||
|
import { ThemeProvider } from "@mui/material/styles";
|
||||||
|
import { muiTheme, buildTheme } from "@/styles/muiTheme";
|
||||||
|
|
||||||
function MyApp({ Component, pageProps }: AppProps) {
|
function MyApp({ Component, pageProps }: AppProps) {
|
||||||
|
// Rebuild theme on client if dark mode toggles (simple example)
|
||||||
|
const [theme, setTheme] = useState(muiTheme);
|
||||||
|
useEffect(() => {
|
||||||
|
const observer = new MutationObserver(() => setTheme(buildTheme()));
|
||||||
|
observer.observe(document.documentElement, {
|
||||||
|
attributes: true,
|
||||||
|
attributeFilter: ["class"],
|
||||||
|
});
|
||||||
|
return () => observer.disconnect();
|
||||||
|
}, []);
|
||||||
return (
|
return (
|
||||||
<Provider store={store}>
|
<Provider store={store}>
|
||||||
{/* Load global data: dev -> API mock JS; prod -> real device JS from public */}
|
<ThemeProvider theme={theme}>
|
||||||
{process.env.NODE_ENV === "development" ? (
|
<CssBaseline />
|
||||||
<Script
|
{process.env.NODE_ENV === "development" ? (
|
||||||
src="/api/cpl/kabelueberwachungAPIHandler"
|
<Script
|
||||||
strategy="afterInteractive"
|
src="/api/cpl/kabelueberwachungAPIHandler"
|
||||||
/>
|
strategy="afterInteractive"
|
||||||
) : (
|
/>
|
||||||
<Script src="/CPL/SERVICE/kueData.js" strategy="afterInteractive" />
|
) : (
|
||||||
)}
|
<Script src="/CPL/SERVICE/kueData.js" strategy="afterInteractive" />
|
||||||
<AppContent Component={Component} pageProps={pageProps} />
|
)}
|
||||||
{/* Bridge window events -> Redux (works across all pages) */}
|
<AppContent Component={Component} pageProps={pageProps} />
|
||||||
<DeviceEventsBridge />
|
<DeviceEventsBridge />
|
||||||
|
</ThemeProvider>
|
||||||
</Provider>
|
</Provider>
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
@@ -67,7 +80,6 @@ function AppContent({
|
|||||||
pageProps: AppProps["pageProps"];
|
pageProps: AppProps["pageProps"];
|
||||||
}): JSX.Element {
|
}): JSX.Element {
|
||||||
const dispatch = useAppDispatch();
|
const dispatch = useAppDispatch();
|
||||||
const pathnameHook = usePathname();
|
|
||||||
const [sessionExpired] = useState(false);
|
const [sessionExpired] = useState(false);
|
||||||
const mode = "DIA0"; // oder aus Router oder Session
|
const mode = "DIA0"; // oder aus Router oder Session
|
||||||
const type = 0; // Beispiel: 0 für "loop", 1 für "iso" (bitte ggf. anpassen)
|
const type = 0; // Beispiel: 0 für "loop", 1 für "iso" (bitte ggf. anpassen)
|
||||||
@@ -151,8 +163,8 @@ function AppContent({
|
|||||||
<div className="flex flex-col h-screen overflow-hidden bg-[var(--color-background)] text-[var(--color-fg)]">
|
<div className="flex flex-col h-screen overflow-hidden bg-[var(--color-background)] text-[var(--color-fg)]">
|
||||||
<Header />
|
<Header />
|
||||||
<div className="flex flex-grow w-full">
|
<div className="flex flex-grow w-full">
|
||||||
<Navigation className="w-56" />
|
<Navigation className="w-56 mr-8" />
|
||||||
<main className="w-full flex-grow bg-[var(--color-surface)] dark:bg-[var(--color-surface)] border-l border-[var(--color-border)]">
|
<main className="w-full flex-grow bg-[var(--color-surface)] dark:bg-[var(--color-surface)] border border-base rounded-lg m-1 p-1 overflow-auto relative">
|
||||||
{sessionExpired && (
|
{sessionExpired && (
|
||||||
<div className="bg-red-500 text-white p-4 text-center">
|
<div className="bg-red-500 text-white p-4 text-center">
|
||||||
❌ Ihre Sitzung ist abgelaufen oder die Verbindung ist
|
❌ Ihre Sitzung ist abgelaufen oder die Verbindung ist
|
||||||
|
|||||||
@@ -1,56 +1,27 @@
|
|||||||
import type { Page } from "@playwright/test";
|
import { Page, expect } from "@playwright/test";
|
||||||
import { highlightAndExpectVisible } from "@playwright/utils/highlight";
|
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Footer assertions.
|
||||||
|
*/
|
||||||
export async function footerTest(page: Page) {
|
export async function footerTest(page: Page) {
|
||||||
await highlightAndExpectVisible(
|
// Auf Footer-Bereich einschränken, damit Selektoren eindeutig bleiben
|
||||||
page,
|
const footer = page.getByRole("contentinfo");
|
||||||
page
|
await expect(footer).toBeVisible();
|
||||||
.locator("div")
|
|
||||||
.filter({ hasText: /^Littwin Systemtechnik GmbH & Co\. KG$/ })
|
await expect(
|
||||||
.locator("svg")
|
footer.getByText("Littwin Systemtechnik GmbH & Co. KG")
|
||||||
);
|
).toBeVisible();
|
||||||
await highlightAndExpectVisible(
|
await expect(footer.getByText("Telefon: 04402 972577-0")).toBeVisible();
|
||||||
page,
|
await expect(
|
||||||
page.getByText("Littwin Systemtechnik GmbH &")
|
footer.getByText("kontakt@littwin-systemtechnik.de")
|
||||||
);
|
).toBeVisible();
|
||||||
await highlightAndExpectVisible(
|
// Exaktes Label im Footer, nicht die Überschrift "PDF Handbücher"
|
||||||
page,
|
await expect(footer.getByText("Handbücher", { exact: true })).toBeVisible();
|
||||||
page
|
|
||||||
.locator("div")
|
|
||||||
.filter({ hasText: /^Telefon: 04402 972577-0$/ })
|
|
||||||
.locator("svg")
|
|
||||||
);
|
|
||||||
await highlightAndExpectVisible(
|
|
||||||
page,
|
|
||||||
page.getByText("Telefon: 04402 972577-")
|
|
||||||
);
|
|
||||||
await highlightAndExpectVisible(
|
|
||||||
page,
|
|
||||||
page
|
|
||||||
.locator("div")
|
|
||||||
.filter({ hasText: /^kontakt@littwin-systemtechnik\.de$/ })
|
|
||||||
.locator("svg")
|
|
||||||
);
|
|
||||||
await highlightAndExpectVisible(
|
|
||||||
page,
|
|
||||||
page.getByText("kontakt@littwin-systemtechnik")
|
|
||||||
);
|
|
||||||
await highlightAndExpectVisible(
|
|
||||||
page,
|
|
||||||
page
|
|
||||||
.locator("div")
|
|
||||||
.filter({ hasText: /^Handbücher$/ })
|
|
||||||
.locator("svg")
|
|
||||||
);
|
|
||||||
await highlightAndExpectVisible(
|
|
||||||
page,
|
|
||||||
page.getByText("Handbücher", { exact: true })
|
|
||||||
);
|
|
||||||
await page.getByText("Handbücher", { exact: true }).click();
|
await page.getByText("Handbücher", { exact: true }).click();
|
||||||
await highlightAndExpectVisible(
|
await expect(
|
||||||
page,
|
|
||||||
page.getByRole("heading", { name: "PDF Handbücher" })
|
page.getByRole("heading", { name: "PDF Handbücher" })
|
||||||
);
|
).toBeVisible();
|
||||||
await highlightAndExpectVisible(page, page.getByText("KUE705FO.PDF"));
|
await expect(page.getByRole("button", { name: "Schließen" })).toBeVisible();
|
||||||
await page.getByRole("contentinfo").getByRole("button").click();
|
await expect(page.getByText("KUE705FO.PDF")).toBeVisible();
|
||||||
|
await page.getByRole("button", { name: "Schließen" }).click();
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -1,14 +1,22 @@
|
|||||||
import type { Page } from "@playwright/test";
|
import { Page, expect } from "@playwright/test";
|
||||||
import { highlightAndExpectVisible } from "@playwright/utils/highlight";
|
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Reusable header assertions.
|
||||||
|
* Add more checks here if the header grows (logout button, admin badge etc.).
|
||||||
|
*/
|
||||||
export async function headerTest(page: Page) {
|
export async function headerTest(page: Page) {
|
||||||
await highlightAndExpectVisible(
|
// Haupttitel
|
||||||
page,
|
await expect(
|
||||||
page.getByRole("heading", { name: "Meldestation" })
|
page.getByRole("heading", { name: "Meldestation" })
|
||||||
);
|
).toBeVisible();
|
||||||
await highlightAndExpectVisible(page, page.getByRole("banner"));
|
// Logo (alt="Logo")
|
||||||
await highlightAndExpectVisible(
|
await expect(
|
||||||
page,
|
|
||||||
page.getByRole("img", { name: "Logo", exact: true })
|
page.getByRole("img", { name: "Logo", exact: true })
|
||||||
);
|
).toBeVisible();
|
||||||
|
// Talas Logo
|
||||||
|
await expect(page.getByRole("img", { name: "TALAS Logo" })).toBeVisible();
|
||||||
|
// Theme Toggle (Label wechselt Dark/Light). Wir akzeptieren beide.
|
||||||
|
const darkBtn = page.getByRole("button", { name: /Dark Mode|Light Mode/ });
|
||||||
|
await expect(darkBtn).toBeVisible();
|
||||||
|
await expect(page.getByText("CPLV4 Ismail Rastede")).toBeVisible();
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -1,19 +0,0 @@
|
|||||||
import { test, expect } from "@playwright/test";
|
|
||||||
import { highlightAndExpectVisible } from "@playwright/utils/highlight";
|
|
||||||
|
|
||||||
test("AnalogInputsChartModal opens after clicking chart button", async ({
|
|
||||||
page,
|
|
||||||
}) => {
|
|
||||||
await page.goto("/analogInputs");
|
|
||||||
// Öffne Modal via Chart-Button (📈)
|
|
||||||
await highlightAndExpectVisible(
|
|
||||||
page,
|
|
||||||
page.getByRole("button", { name: "Messkurve anzeigen" })
|
|
||||||
);
|
|
||||||
await page
|
|
||||||
.getByRole("button", { name: "Messkurve anzeigen" })
|
|
||||||
.first()
|
|
||||||
.click();
|
|
||||||
await highlightAndExpectVisible(page, page.getByRole("dialog"));
|
|
||||||
await expect(page.getByRole("dialog")).toBeVisible();
|
|
||||||
});
|
|
||||||
@@ -1,15 +0,0 @@
|
|||||||
import { test } from "@playwright/test";
|
|
||||||
import { highlightAndExpectVisible } from "@playwright/utils/highlight";
|
|
||||||
|
|
||||||
test("AnalogInputsDatePicker renders two inputs", async ({ page }) => {
|
|
||||||
await page.goto("/analogInputs");
|
|
||||||
// Öffne erst die Chart-Ansicht (enthält den DatePicker)
|
|
||||||
const chartBtn = page
|
|
||||||
.getByRole("button", { name: "Messkurve anzeigen" })
|
|
||||||
.first();
|
|
||||||
await highlightAndExpectVisible(page, chartBtn);
|
|
||||||
await chartBtn.click();
|
|
||||||
await highlightAndExpectVisible(page, page.getByRole("dialog"));
|
|
||||||
await highlightAndExpectVisible(page, page.getByText("Von"));
|
|
||||||
await highlightAndExpectVisible(page, page.getByText("Bis"));
|
|
||||||
});
|
|
||||||
@@ -1,17 +0,0 @@
|
|||||||
import { test } from "@playwright/test";
|
|
||||||
import { highlightAndExpectVisible } from "@playwright/utils/highlight";
|
|
||||||
|
|
||||||
test("AnalogInputsSettingsModal opens after clicking settings", async ({
|
|
||||||
page,
|
|
||||||
}) => {
|
|
||||||
await page.goto("/analogInputs");
|
|
||||||
// Wähle die erste Tabellenzeile und in der 5. Spalte (Einstellungen) den Button
|
|
||||||
const firstRow = page.locator("table tbody tr").first();
|
|
||||||
const settingsButton = firstRow.locator("td").nth(4).locator("button");
|
|
||||||
await highlightAndExpectVisible(page, settingsButton);
|
|
||||||
await settingsButton.click();
|
|
||||||
await highlightAndExpectVisible(
|
|
||||||
page,
|
|
||||||
page.getByRole("button", { name: "Modal schließen" })
|
|
||||||
);
|
|
||||||
});
|
|
||||||
@@ -1,9 +0,0 @@
|
|||||||
import { test } from "@playwright/test";
|
|
||||||
import { highlightAndExpectVisible } from "@playwright/utils/highlight";
|
|
||||||
|
|
||||||
test("AnalogInputsTable renders rows", async ({ page }) => {
|
|
||||||
await page.goto("/analogInputs");
|
|
||||||
await highlightAndExpectVisible(page, page.getByRole("table"));
|
|
||||||
// Mindestens eine Tabellenzeile sichtbar
|
|
||||||
await highlightAndExpectVisible(page, page.locator("tbody tr").first());
|
|
||||||
});
|
|
||||||
@@ -1,12 +0,0 @@
|
|||||||
import { test, expect } from "@playwright/test";
|
|
||||||
import { highlightAndExpectVisible } from "@playwright/utils/highlight";
|
|
||||||
|
|
||||||
test("AnalogInputsView shows heading and table", async ({ page }) => {
|
|
||||||
await page.goto("/analogInputs");
|
|
||||||
await highlightAndExpectVisible(
|
|
||||||
page,
|
|
||||||
page.getByRole("heading", { name: "Messwerteingänge" })
|
|
||||||
);
|
|
||||||
await highlightAndExpectVisible(page, page.getByRole("table"));
|
|
||||||
await expect(page.getByRole("table")).toBeVisible();
|
|
||||||
});
|
|
||||||
@@ -1,5 +0,0 @@
|
|||||||
import { test } from "@playwright/test";
|
|
||||||
|
|
||||||
test.fixme("XioPM visual presence", async ({ page }) => {
|
|
||||||
await page.goto("/analogInputs");
|
|
||||||
});
|
|
||||||
@@ -1,57 +1,20 @@
|
|||||||
import type { Page } from "@playwright/test";
|
import { Page, expect } from "@playwright/test";
|
||||||
import { highlightAndExpectVisible } from "@playwright/utils/highlight";
|
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Sidebar / Navigation visibility + core links.
|
||||||
|
*/
|
||||||
export async function navTest(page: Page) {
|
export async function navTest(page: Page) {
|
||||||
await highlightAndExpectVisible(
|
const links = [
|
||||||
page,
|
"Übersicht",
|
||||||
page.getByRole("link", { name: "Übersicht" })
|
"Kabelüberwachung",
|
||||||
);
|
"Meldungseingänge",
|
||||||
await highlightAndExpectVisible(
|
"Schaltausgänge",
|
||||||
page,
|
"Messwerteingänge",
|
||||||
page.getByRole("link", { name: "Kabelüberwachung" })
|
"Berichte",
|
||||||
);
|
"System",
|
||||||
await highlightAndExpectVisible(
|
"Einstellungen",
|
||||||
page,
|
|
||||||
page.getByRole("link", { name: "Meldungseingänge" })
|
|
||||||
);
|
|
||||||
await highlightAndExpectVisible(
|
|
||||||
page,
|
|
||||||
page.getByRole("link", { name: "Schaltausgänge" })
|
|
||||||
);
|
|
||||||
await highlightAndExpectVisible(
|
|
||||||
page,
|
|
||||||
page.getByRole("link", { name: "Messwerteingänge" })
|
|
||||||
);
|
|
||||||
await highlightAndExpectVisible(
|
|
||||||
page,
|
|
||||||
page.getByRole("link", { name: "Berichte" })
|
|
||||||
);
|
|
||||||
await highlightAndExpectVisible(
|
|
||||||
page,
|
|
||||||
page.getByRole("link", { name: "System" })
|
|
||||||
);
|
|
||||||
await highlightAndExpectVisible(
|
|
||||||
page,
|
|
||||||
page.getByRole("link", { name: "Einstellungen" })
|
|
||||||
);
|
|
||||||
}
|
|
||||||
|
|
||||||
/*
|
|
||||||
// Sidebar Links sichtbar
|
|
||||||
const sidebarLinks2 = [
|
|
||||||
{ role: "link", name: "Übersicht" },
|
|
||||||
{ role: "link", name: "Kabelüberwachung" },
|
|
||||||
{ role: "link", name: "Meldungseingänge" },
|
|
||||||
{ role: "link", name: "Schaltausgänge" },
|
|
||||||
{ role: "link", name: "Messwerteingänge" },
|
|
||||||
{ role: "link", name: "Berichte" },
|
|
||||||
{ role: "link", name: "System" },
|
|
||||||
{ role: "link", name: "Einstellungen" },
|
|
||||||
];
|
];
|
||||||
for (const link of sidebarLinks2) {
|
for (const name of links) {
|
||||||
const locator = page.getByRole(link.role as any, { name: link.name });
|
await expect(page.getByRole("link", { name })).toBeVisible();
|
||||||
await highlightAndExpectVisible(page, locator);
|
|
||||||
await expect(locator).toBeVisible();
|
|
||||||
await page.waitForTimeout(50);
|
|
||||||
}
|
}
|
||||||
*/
|
}
|
||||||
|
|||||||
@@ -1,20 +0,0 @@
|
|||||||
import { test } from "../fixtures";
|
|
||||||
import { runDashboardTest } from "./pages/dashboard/dashboardTest";
|
|
||||||
import { runCableMonitoringTest } from "./pages/kabelueberwachung/kabelueberwachungTest";
|
|
||||||
import { runDigitalInputsTest } from "./pages/digitalInputs/digitalInputsTest";
|
|
||||||
import { runDigitalOutputsTest } from "./pages/digitalOutputs/digitalOutputsTest";
|
|
||||||
import { runAnalogInputsTest } from "./pages/analogInputs/analogInputsTest";
|
|
||||||
import { runMeldungenTest } from "./pages/meldungen/meldungenTest";
|
|
||||||
import { runSystemTest } from "./pages/system/systemTest";
|
|
||||||
import { runSettingsPageTest } from "./pages/settingsPage/settingsPageTest";
|
|
||||||
|
|
||||||
test("Dashboard, AnalogInputs und SettingsPage", async ({ page }) => {
|
|
||||||
await runDashboardTest(page);
|
|
||||||
await runCableMonitoringTest(page);
|
|
||||||
await runDigitalInputsTest(page);
|
|
||||||
await runDigitalOutputsTest(page);
|
|
||||||
await runAnalogInputsTest(page);
|
|
||||||
await runMeldungenTest(page);
|
|
||||||
await runSystemTest(page);
|
|
||||||
await runSettingsPageTest(page);
|
|
||||||
});
|
|
||||||
139
playwright/tests/pages/analogInputs/analogInputs.test.ts
Normal file
139
playwright/tests/pages/analogInputs/analogInputs.test.ts
Normal file
@@ -0,0 +1,139 @@
|
|||||||
|
import { test, expect } from "@playwright/test";
|
||||||
|
import { headerTest } from "@/playwright/tests/components/header/headerTest";
|
||||||
|
import { navTest } from "@/playwright/tests/components/navigation/navTest";
|
||||||
|
import { footerTest } from "@/playwright/tests/components/footer/footerTest";
|
||||||
|
|
||||||
|
test.use({
|
||||||
|
viewport: {
|
||||||
|
height: 1080,
|
||||||
|
width: 1920,
|
||||||
|
},
|
||||||
|
});
|
||||||
|
|
||||||
|
test("analogInputs", async ({ page }) => {
|
||||||
|
await page.goto("/analogInputs");
|
||||||
|
// Gemeinsame Layout-Checks
|
||||||
|
await headerTest(page);
|
||||||
|
await navTest(page);
|
||||||
|
await footerTest(page);
|
||||||
|
// Seitenspezifische Checks
|
||||||
|
await expect(
|
||||||
|
page.getByRole("heading", { name: "Messwerteingänge" }).first()
|
||||||
|
).toBeVisible();
|
||||||
|
await expect(page.locator(".text-littwin-blue")).toBeVisible();
|
||||||
|
await expect(
|
||||||
|
page.getByRole("heading", { name: "Messwerteingänge" }).nth(1)
|
||||||
|
).toBeVisible();
|
||||||
|
await expect(page.getByRole("cell", { name: "Eingang" })).toBeVisible();
|
||||||
|
await expect(page.getByRole("cell", { name: "Messwert" })).toBeVisible();
|
||||||
|
await expect(page.getByRole("cell", { name: "Einheit" })).toBeVisible();
|
||||||
|
await expect(page.getByRole("cell", { name: "Bezeichnung" })).toBeVisible();
|
||||||
|
await expect(page.getByRole("cell", { name: "Einstellungen" })).toBeVisible();
|
||||||
|
await expect(
|
||||||
|
page.getByRole("cell", { name: "Messkurve", exact: true })
|
||||||
|
).toBeVisible();
|
||||||
|
await expect(
|
||||||
|
page.getByRole("cell", { name: "1", exact: true })
|
||||||
|
).toBeVisible();
|
||||||
|
await expect(
|
||||||
|
page.getByRole("cell", { name: "2", exact: true })
|
||||||
|
).toBeVisible();
|
||||||
|
await expect(
|
||||||
|
page.getByRole("cell", { name: "3", exact: true })
|
||||||
|
).toBeVisible();
|
||||||
|
await expect(
|
||||||
|
page.getByRole("cell", { name: "4", exact: true })
|
||||||
|
).toBeVisible();
|
||||||
|
await expect(
|
||||||
|
page.getByRole("cell", { name: "5", exact: true })
|
||||||
|
).toBeVisible();
|
||||||
|
await expect(
|
||||||
|
page.getByRole("cell", { name: "6", exact: true })
|
||||||
|
).toBeVisible();
|
||||||
|
await expect(
|
||||||
|
page.getByRole("cell", { name: "7", exact: true })
|
||||||
|
).toBeVisible();
|
||||||
|
await expect(
|
||||||
|
page.getByRole("cell", { name: "8", exact: true })
|
||||||
|
).toBeVisible();
|
||||||
|
await expect(page.getByRole("cell", { name: "126.63" })).toBeVisible();
|
||||||
|
await expect(page.getByRole("cell", { name: "5.67" })).toBeVisible();
|
||||||
|
await expect(page.getByRole("cell", { name: "-" }).first()).toBeVisible();
|
||||||
|
await expect(page.getByRole("cell", { name: "AE 1" })).toBeVisible();
|
||||||
|
await expect(page.getByRole("cell", { name: "Temperatur" })).toBeVisible();
|
||||||
|
await expect(page.getByRole("cell", { name: "AE 3" })).toBeVisible();
|
||||||
|
await expect(page.getByRole("cell", { name: "AE 4" })).toBeVisible();
|
||||||
|
await expect(page.getByRole("cell", { name: "AE 5" })).toBeVisible();
|
||||||
|
await expect(page.getByRole("cell", { name: "AE 6" })).toBeVisible();
|
||||||
|
await expect(page.getByRole("cell", { name: "AE 7" })).toBeVisible();
|
||||||
|
await expect(page.getByRole("cell", { name: "AE 8" })).toBeVisible();
|
||||||
|
await expect(page.locator(".border.p-2.text-center").first()).toBeVisible();
|
||||||
|
await expect(page.locator("tr:nth-child(2) > td:nth-child(5)")).toBeVisible();
|
||||||
|
await expect(page.locator("tr:nth-child(8) > td:nth-child(5)")).toBeVisible();
|
||||||
|
await expect(page.locator("td:nth-child(6)").first()).toBeVisible();
|
||||||
|
await expect(page.locator("tr:nth-child(8) > td:nth-child(6)")).toBeVisible();
|
||||||
|
await page.locator(".border.p-2.text-center").first().click();
|
||||||
|
await expect(
|
||||||
|
page.getByRole("button", { name: "Modal schließen" })
|
||||||
|
).toBeVisible();
|
||||||
|
await page
|
||||||
|
.getByRole("heading", { name: "Einstellungen Messwerteingang" })
|
||||||
|
.click();
|
||||||
|
await page.getByText("Bezeichnung:").click();
|
||||||
|
await page.getByText("Offset:").click();
|
||||||
|
await page.getByText("Faktor:").click();
|
||||||
|
await page.getByText("Einheit:").click();
|
||||||
|
await page.getByText("Speicherintervall:").click();
|
||||||
|
await page.getByRole("textbox").click();
|
||||||
|
await page
|
||||||
|
.locator("div")
|
||||||
|
.filter({ hasText: /^Offset:$/ })
|
||||||
|
.getByRole("spinbutton")
|
||||||
|
.click();
|
||||||
|
await page
|
||||||
|
.locator("div")
|
||||||
|
.filter({ hasText: /^Faktor:$/ })
|
||||||
|
.getByRole("spinbutton")
|
||||||
|
.click();
|
||||||
|
await page.getByRole("button", { name: "V", exact: true }).click();
|
||||||
|
await page.getByRole("button", { name: "V", exact: true }).click();
|
||||||
|
await page
|
||||||
|
.locator("div")
|
||||||
|
.filter({ hasText: /^Minuten$/ })
|
||||||
|
.getByRole("spinbutton")
|
||||||
|
.click();
|
||||||
|
await expect(page.getByRole("button", { name: "Speichern" })).toBeVisible();
|
||||||
|
await page.getByRole("button", { name: "Modal schließen" }).click();
|
||||||
|
await page.locator("tr:nth-child(8) > td:nth-child(5)").click();
|
||||||
|
await expect(
|
||||||
|
page.getByRole("button", { name: "Modal schließen" })
|
||||||
|
).toBeVisible();
|
||||||
|
await page
|
||||||
|
.getByRole("heading", { name: "Einstellungen Messwerteingang" })
|
||||||
|
.click();
|
||||||
|
await page.getByText("Bezeichnung:").click();
|
||||||
|
await page.getByText("Offset:").click();
|
||||||
|
await page.getByText("Faktor:").click();
|
||||||
|
await page.getByText("Einheit:").click();
|
||||||
|
await page.getByText("Speicherintervall:").click();
|
||||||
|
await page.getByRole("textbox").click();
|
||||||
|
await page
|
||||||
|
.locator("div")
|
||||||
|
.filter({ hasText: /^Offset:$/ })
|
||||||
|
.getByRole("spinbutton")
|
||||||
|
.click();
|
||||||
|
await page
|
||||||
|
.locator("div")
|
||||||
|
.filter({ hasText: /^Faktor:$/ })
|
||||||
|
.getByRole("spinbutton")
|
||||||
|
.click();
|
||||||
|
await page.getByRole("button", { name: "mA" }).click();
|
||||||
|
await page.getByRole("button", { name: "mA" }).click();
|
||||||
|
await page
|
||||||
|
.locator("div")
|
||||||
|
.filter({ hasText: /^Minuten$/ })
|
||||||
|
.getByRole("spinbutton")
|
||||||
|
.click();
|
||||||
|
await expect(page.getByRole("button", { name: "Speichern" })).toBeVisible();
|
||||||
|
await page.getByRole("button", { name: "Modal schließen" }).click();
|
||||||
|
});
|
||||||
@@ -1,254 +0,0 @@
|
|||||||
import type { Page } from "@playwright/test";
|
|
||||||
import { expect } from "@playwright/test";
|
|
||||||
import { highlightAndExpectVisible } from "@playwright/utils/highlight";
|
|
||||||
import { navTest } from "@/playwright/tests/components/navigation/navTest";
|
|
||||||
import { headerTest } from "@/playwright/tests/components/header/headerTest";
|
|
||||||
import { footerTest } from "@/playwright/tests/components/footer/footerTest";
|
|
||||||
|
|
||||||
// Kombinierte Helper-Funktion: injiziert CSS (nur einmal), hebt hervor und prüft Sichtbarkeit
|
|
||||||
|
|
||||||
export async function runAnalogInputsTest(page: Page) {
|
|
||||||
await page.goto("/analogInputs");
|
|
||||||
//----------------------
|
|
||||||
await headerTest(page);
|
|
||||||
await navTest(page);
|
|
||||||
await footerTest(page);
|
|
||||||
await page.waitForTimeout(400);
|
|
||||||
//----------------------
|
|
||||||
await highlightAndExpectVisible(
|
|
||||||
page,
|
|
||||||
page.getByRole("heading", { name: "Messwerteingänge" }).nth(1)
|
|
||||||
);
|
|
||||||
await highlightAndExpectVisible(
|
|
||||||
page,
|
|
||||||
page.getByRole("cell", { name: "Eingang" })
|
|
||||||
);
|
|
||||||
// ...existing code...
|
|
||||||
|
|
||||||
await highlightAndExpectVisible(
|
|
||||||
page,
|
|
||||||
page.getByRole("cell", { name: "Messwert" })
|
|
||||||
);
|
|
||||||
|
|
||||||
await highlightAndExpectVisible(
|
|
||||||
page,
|
|
||||||
page.getByRole("cell", { name: "Einheit" })
|
|
||||||
);
|
|
||||||
|
|
||||||
await highlightAndExpectVisible(
|
|
||||||
page,
|
|
||||||
page.getByRole("cell", { name: "Bezeichnung" })
|
|
||||||
);
|
|
||||||
|
|
||||||
await highlightAndExpectVisible(
|
|
||||||
page,
|
|
||||||
page.getByRole("cell", { name: "Einstellungen" })
|
|
||||||
);
|
|
||||||
|
|
||||||
await highlightAndExpectVisible(
|
|
||||||
page,
|
|
||||||
page.getByRole("cell", { name: "Messkurve", exact: true })
|
|
||||||
);
|
|
||||||
|
|
||||||
await highlightAndExpectVisible(page, page.getByText("1", { exact: true }));
|
|
||||||
|
|
||||||
await highlightAndExpectVisible(page, page.getByText("2", { exact: true }));
|
|
||||||
|
|
||||||
await highlightAndExpectVisible(page, page.getByText("3", { exact: true }));
|
|
||||||
|
|
||||||
await highlightAndExpectVisible(
|
|
||||||
page,
|
|
||||||
page.getByRole("cell", { name: "4", exact: true }).locator("path")
|
|
||||||
);
|
|
||||||
|
|
||||||
await highlightAndExpectVisible(
|
|
||||||
page,
|
|
||||||
page.getByRole("cell", { name: "5", exact: true })
|
|
||||||
);
|
|
||||||
|
|
||||||
await highlightAndExpectVisible(page, page.getByText("6", { exact: true }));
|
|
||||||
|
|
||||||
await highlightAndExpectVisible(
|
|
||||||
page,
|
|
||||||
page.getByRole("cell", { name: "7", exact: true })
|
|
||||||
);
|
|
||||||
await expect(
|
|
||||||
page.getByRole("cell", { name: "8", exact: true })
|
|
||||||
).toBeVisible();
|
|
||||||
|
|
||||||
await highlightAndExpectVisible(
|
|
||||||
page,
|
|
||||||
page.getByRole("cell", { name: "8", exact: true })
|
|
||||||
);
|
|
||||||
await expect(page.locator(".border.p-2.text-center").first()).toBeVisible();
|
|
||||||
|
|
||||||
await highlightAndExpectVisible(
|
|
||||||
page,
|
|
||||||
page.locator(".border.p-2.text-center").first()
|
|
||||||
);
|
|
||||||
// Markiere die gesamte erste Datenzeile (Row mit "AE 1" falls vorhanden)
|
|
||||||
|
|
||||||
await highlightAndExpectVisible(
|
|
||||||
page,
|
|
||||||
page
|
|
||||||
.getByRole("row", { name: "2 5.67 °C Temperatur" })
|
|
||||||
.getByRole("button")
|
|
||||||
.first()
|
|
||||||
);
|
|
||||||
await expect(page.locator("tr:nth-child(3) > td:nth-child(5)")).toBeVisible();
|
|
||||||
|
|
||||||
await highlightAndExpectVisible(
|
|
||||||
page,
|
|
||||||
page.locator("tr:nth-child(3) > td:nth-child(5)")
|
|
||||||
);
|
|
||||||
await expect(
|
|
||||||
page
|
|
||||||
.getByRole("row", { name: "0.01 V AE 4 Messkurve anzeigen" })
|
|
||||||
.getByRole("button")
|
|
||||||
.first()
|
|
||||||
).toBeVisible();
|
|
||||||
|
|
||||||
await highlightAndExpectVisible(
|
|
||||||
page,
|
|
||||||
page
|
|
||||||
.getByRole("row", { name: "0.01 V AE 4 Messkurve anzeigen" })
|
|
||||||
.getByRole("button")
|
|
||||||
.first()
|
|
||||||
);
|
|
||||||
await expect(
|
|
||||||
page
|
|
||||||
.getByRole("row", { name: "8 -0.00 mA AE 8 Messkurve" })
|
|
||||||
.getByLabel("Messkurve anzeigen")
|
|
||||||
).toBeVisible();
|
|
||||||
|
|
||||||
await highlightAndExpectVisible(
|
|
||||||
page,
|
|
||||||
page
|
|
||||||
.getByRole("row", { name: "8 -0.00 mA AE 8 Messkurve" })
|
|
||||||
.getByLabel("Messkurve anzeigen")
|
|
||||||
);
|
|
||||||
|
|
||||||
// Einstellungen-Button in der ersten Datenzeile klicken und auf Modal warten
|
|
||||||
const firstRow = page.getByRole("row", { name: /1\s+.*AE\s*1/i });
|
|
||||||
const settingsButtonInRow = firstRow.getByRole("button").first();
|
|
||||||
await settingsButtonInRow.waitFor({ state: "visible", timeout: 10000 });
|
|
||||||
await settingsButtonInRow.click();
|
|
||||||
await expect(
|
|
||||||
page.getByRole("heading", { name: /Einstellungen Messwerteingang/ })
|
|
||||||
).toBeVisible({ timeout: 15000 });
|
|
||||||
|
|
||||||
await highlightAndExpectVisible(
|
|
||||||
page,
|
|
||||||
page.getByRole("heading", { name: /Einstellungen Messwerteingang/ })
|
|
||||||
);
|
|
||||||
|
|
||||||
await highlightAndExpectVisible(page, page.getByText("Bezeichnung:"));
|
|
||||||
await highlightAndExpectVisible(page, page.getByText("Offset:"), 5000);
|
|
||||||
await highlightAndExpectVisible(page, page.getByText("Faktor:"), 5000);
|
|
||||||
await highlightAndExpectVisible(page, page.getByText("Einheit:"), 5000);
|
|
||||||
await highlightAndExpectVisible(
|
|
||||||
page,
|
|
||||||
page.getByText("Speicherintervall:"),
|
|
||||||
5000
|
|
||||||
);
|
|
||||||
|
|
||||||
await highlightAndExpectVisible(
|
|
||||||
page,
|
|
||||||
page.getByRole("button", { name: "Speichern" })
|
|
||||||
);
|
|
||||||
await expect(
|
|
||||||
page.getByRole("button", { name: "Modal schließen" })
|
|
||||||
).toBeVisible();
|
|
||||||
|
|
||||||
await highlightAndExpectVisible(
|
|
||||||
page,
|
|
||||||
page.getByRole("button", { name: "Modal schließen" })
|
|
||||||
);
|
|
||||||
await expect(
|
|
||||||
page.getByText(
|
|
||||||
"Einstellungen Messwerteingang 1Bezeichnung:Offset:Faktor:Einheit:"
|
|
||||||
)
|
|
||||||
).toBeVisible();
|
|
||||||
|
|
||||||
await highlightAndExpectVisible(
|
|
||||||
page,
|
|
||||||
page.getByText(
|
|
||||||
"Einstellungen Messwerteingang 1Bezeichnung:Offset:Faktor:Einheit:"
|
|
||||||
)
|
|
||||||
);
|
|
||||||
|
|
||||||
await page.getByRole("button", { name: "Modal schließen" }).click();
|
|
||||||
await expect(
|
|
||||||
page
|
|
||||||
.getByRole("row", { name: "1 126.63 V AE 1 Messkurve" })
|
|
||||||
.getByLabel("Messkurve anzeigen")
|
|
||||||
).toBeVisible();
|
|
||||||
|
|
||||||
await page
|
|
||||||
.getByRole("row", { name: "1 126.63 V AE 1 Messkurve" })
|
|
||||||
.getByLabel("Messkurve anzeigen")
|
|
||||||
.click();
|
|
||||||
await expect(
|
|
||||||
page.getByText(
|
|
||||||
"Messkurve Messwerteingang 1Eingang 1VonBisAlle MesswerteDaten laden"
|
|
||||||
)
|
|
||||||
).toBeVisible();
|
|
||||||
|
|
||||||
await highlightAndExpectVisible(
|
|
||||||
page,
|
|
||||||
page.getByText(
|
|
||||||
"Messkurve Messwerteingang 1Eingang 1VonBisAlle MesswerteDaten laden"
|
|
||||||
)
|
|
||||||
);
|
|
||||||
|
|
||||||
await highlightAndExpectVisible(
|
|
||||||
page,
|
|
||||||
page.getByRole("heading", { name: "Messkurve Messwerteingang" })
|
|
||||||
);
|
|
||||||
|
|
||||||
await highlightAndExpectVisible(page, page.locator("canvas"));
|
|
||||||
|
|
||||||
await highlightAndExpectVisible(page, page.getByText("Eingang 1VonBisAlle"));
|
|
||||||
|
|
||||||
await highlightAndExpectVisible(
|
|
||||||
page,
|
|
||||||
page.getByRole("button", { name: "Daten laden" })
|
|
||||||
);
|
|
||||||
|
|
||||||
await highlightAndExpectVisible(
|
|
||||||
page,
|
|
||||||
page.getByRole("button", { name: "Alle Messwerte " })
|
|
||||||
);
|
|
||||||
await page.getByRole("button", { name: "Alle Messwerte " }).click();
|
|
||||||
await expect(page.getByRole("option", { name: "Stündlich" })).toBeVisible();
|
|
||||||
|
|
||||||
await page.getByRole("option", { name: "Stündlich" }).click();
|
|
||||||
await expect(page.getByRole("button", { name: "Stündlich" })).toBeVisible();
|
|
||||||
|
|
||||||
await page.getByRole("button", { name: "Stündlich" }).click();
|
|
||||||
await expect(page.getByRole("option", { name: "Täglich" })).toBeVisible();
|
|
||||||
|
|
||||||
await page.getByRole("option", { name: "Täglich" }).click();
|
|
||||||
await expect(page.getByRole("button", { name: "Fullscreen" })).toBeVisible();
|
|
||||||
|
|
||||||
await page.getByRole("button", { name: "Fullscreen" }).click();
|
|
||||||
await expect(
|
|
||||||
page.getByRole("button", { name: "Exit fullscreen" })
|
|
||||||
).toBeVisible();
|
|
||||||
|
|
||||||
await page.getByRole("button", { name: "Exit fullscreen" }).click();
|
|
||||||
await expect(page.getByRole("button", { name: "Fullscreen" })).toBeVisible();
|
|
||||||
|
|
||||||
await highlightAndExpectVisible(
|
|
||||||
page,
|
|
||||||
page.getByRole("button", { name: "Fullscreen" })
|
|
||||||
);
|
|
||||||
|
|
||||||
// Modal schließen nur, wenn noch vorhanden
|
|
||||||
const modalCloseBtn = page.getByRole("button", { name: "Modal schließen" });
|
|
||||||
if ((await modalCloseBtn.count()) > 0 && (await modalCloseBtn.isVisible())) {
|
|
||||||
await highlightAndExpectVisible(page, modalCloseBtn);
|
|
||||||
await expect(modalCloseBtn).toBeVisible();
|
|
||||||
await modalCloseBtn.click();
|
|
||||||
}
|
|
||||||
}
|
|
||||||
70
playwright/tests/pages/dashboard/dashboard.test.ts
Normal file
70
playwright/tests/pages/dashboard/dashboard.test.ts
Normal file
@@ -0,0 +1,70 @@
|
|||||||
|
import { test, expect } from "@playwright/test";
|
||||||
|
import { headerTest } from "@/playwright/tests/components/header/headerTest";
|
||||||
|
import { navTest } from "@/playwright/tests/components/navigation/navTest";
|
||||||
|
import { footerTest } from "@/playwright/tests/components/footer/footerTest";
|
||||||
|
|
||||||
|
test.use({
|
||||||
|
viewport: {
|
||||||
|
height: 900,
|
||||||
|
width: 1600,
|
||||||
|
},
|
||||||
|
});
|
||||||
|
|
||||||
|
test("Dashboard", async ({ page }) => {
|
||||||
|
await page.goto("/dashboard");
|
||||||
|
// Gemeinsame Layout-Checks
|
||||||
|
await headerTest(page);
|
||||||
|
await navTest(page);
|
||||||
|
await footerTest(page);
|
||||||
|
// Seitenspezifische Checks
|
||||||
|
await expect(page.getByRole("main").locator("svg").first()).toBeVisible();
|
||||||
|
await expect(
|
||||||
|
page.getByRole("heading", { name: "Letzten 20 Meldungen" })
|
||||||
|
).toBeVisible();
|
||||||
|
await expect(page.getByRole("cell", { name: "Prio" })).toBeVisible();
|
||||||
|
await expect(page.getByRole("cell", { name: "Zeitstempel" })).toBeVisible();
|
||||||
|
await expect(page.getByRole("cell", { name: "Quelle" })).toBeVisible();
|
||||||
|
await expect(page.getByRole("cell", { name: "Meldung" })).toBeVisible();
|
||||||
|
await expect(page.getByRole("cell", { name: "Status" })).toBeVisible();
|
||||||
|
await expect(
|
||||||
|
page.getByRole("heading", { name: "Versionsinformationen" })
|
||||||
|
).toBeVisible();
|
||||||
|
await expect(page.getByRole("main").locator("path").nth(2)).toBeVisible();
|
||||||
|
await expect(
|
||||||
|
page
|
||||||
|
.getByRole("listitem")
|
||||||
|
.filter({ hasText: "Webversion:" })
|
||||||
|
.locator("path")
|
||||||
|
).toBeVisible();
|
||||||
|
await expect(page.locator("div").filter({ hasText: /^1$/ })).toBeVisible();
|
||||||
|
await expect(page.getByText("8", { exact: true })).toBeVisible();
|
||||||
|
await expect(page.getByText("9", { exact: true })).toBeVisible();
|
||||||
|
await expect(
|
||||||
|
page.locator(
|
||||||
|
"div:nth-child(2) > .flex.gap-1 > div:nth-child(8) > .border > .bg-littwin-blue.flex-grow > div:nth-child(2)"
|
||||||
|
)
|
||||||
|
).toBeVisible();
|
||||||
|
await expect(page.getByText("17KÜ705FO")).toBeVisible();
|
||||||
|
await expect(
|
||||||
|
page.locator(
|
||||||
|
"div:nth-child(3) > .flex.gap-1 > div:nth-child(8) > .border > .bg-littwin-blue.flex-grow > div:nth-child(2)"
|
||||||
|
)
|
||||||
|
).toBeVisible();
|
||||||
|
await expect(page.getByText("25KÜ705FO")).toBeVisible();
|
||||||
|
await expect(page.getByText("32KÜ705FO")).toBeVisible();
|
||||||
|
await expect(page.getByRole("img", { name: "IP Address" })).toBeVisible();
|
||||||
|
await expect(page.getByText("IP-Adresse")).toBeVisible();
|
||||||
|
await expect(page.getByRole("main")).toContainText("10.10.0.243");
|
||||||
|
await expect(page.getByRole("main")).toContainText("255.255.255.0");
|
||||||
|
await expect(page.getByRole("main")).toContainText("10.10.0.1");
|
||||||
|
await page.getByText("Server betriebsbereit").click();
|
||||||
|
await page
|
||||||
|
.getByRole("row", { name: "2025-09-05 11:52:44" })
|
||||||
|
.getByRole("cell")
|
||||||
|
.first()
|
||||||
|
.click();
|
||||||
|
await expect(page.locator("tbody")).toContainText("2025-09-05 11:52:44");
|
||||||
|
await expect(page.locator("tbody")).toContainText("CableLine13");
|
||||||
|
await expect(page.locator("tbody")).toContainText("Isofehler gehend");
|
||||||
|
await expect(page.locator("tbody")).toContainText("0");
|
||||||
|
});
|
||||||
@@ -1,90 +0,0 @@
|
|||||||
import type { Page } from "@playwright/test";
|
|
||||||
import { highlightAndExpectVisible } from "@playwright/utils/highlight";
|
|
||||||
import { navTest } from "@/playwright/tests/components/navigation/navTest";
|
|
||||||
import { headerTest } from "@/playwright/tests/components/header/headerTest";
|
|
||||||
import { footerTest } from "@/playwright/tests/components/footer/footerTest";
|
|
||||||
|
|
||||||
export async function runDashboardTest(page: Page) {
|
|
||||||
await page.goto("/dashboard");
|
|
||||||
//----------------------
|
|
||||||
await headerTest(page);
|
|
||||||
await navTest(page);
|
|
||||||
await footerTest(page);
|
|
||||||
await page.waitForTimeout(400);
|
|
||||||
//----------------------
|
|
||||||
await highlightAndExpectVisible(
|
|
||||||
page,
|
|
||||||
page.getByRole("heading", { name: "Letzten 20 Meldungen" })
|
|
||||||
);
|
|
||||||
await highlightAndExpectVisible(
|
|
||||||
page,
|
|
||||||
page.getByRole("cell", { name: "Prio" })
|
|
||||||
);
|
|
||||||
await highlightAndExpectVisible(
|
|
||||||
page,
|
|
||||||
page.getByRole("cell", { name: "Zeitstempel" })
|
|
||||||
);
|
|
||||||
// ...existing code...
|
|
||||||
await highlightAndExpectVisible(
|
|
||||||
page,
|
|
||||||
page.getByRole("cell", { name: "Quelle" })
|
|
||||||
);
|
|
||||||
await highlightAndExpectVisible(
|
|
||||||
page,
|
|
||||||
page.getByRole("cell", { name: "Meldung" })
|
|
||||||
);
|
|
||||||
await highlightAndExpectVisible(
|
|
||||||
page,
|
|
||||||
page.getByRole("cell", { name: "Status" })
|
|
||||||
);
|
|
||||||
|
|
||||||
await highlightAndExpectVisible(
|
|
||||||
page,
|
|
||||||
page
|
|
||||||
.locator("div")
|
|
||||||
.filter({ hasText: /^2KÜ705FO$/ })
|
|
||||||
.first()
|
|
||||||
);
|
|
||||||
await highlightAndExpectVisible(
|
|
||||||
page,
|
|
||||||
page
|
|
||||||
.locator("div")
|
|
||||||
.filter({ hasText: /^3KÜ705FO$/ })
|
|
||||||
.first()
|
|
||||||
);
|
|
||||||
await highlightAndExpectVisible(
|
|
||||||
page,
|
|
||||||
page
|
|
||||||
.locator("div")
|
|
||||||
.filter({ hasText: /^32KÜ705FO$/ })
|
|
||||||
.first()
|
|
||||||
);
|
|
||||||
await highlightAndExpectVisible(
|
|
||||||
page,
|
|
||||||
page.getByRole("img", { name: "IP Address" })
|
|
||||||
);
|
|
||||||
await highlightAndExpectVisible(page, page.getByText("IP-Adresse"));
|
|
||||||
await highlightAndExpectVisible(page, page.getByRole("main"));
|
|
||||||
await highlightAndExpectVisible(page, page.getByText("10.10.0.243"));
|
|
||||||
await highlightAndExpectVisible(page, page.getByText("Subnet-Maske"));
|
|
||||||
await highlightAndExpectVisible(
|
|
||||||
page,
|
|
||||||
page.getByRole("img", { name: "subnet mask" })
|
|
||||||
);
|
|
||||||
await highlightAndExpectVisible(page, page.getByText("255.255.255.0"));
|
|
||||||
await highlightAndExpectVisible(page, page.getByText("Gateway"));
|
|
||||||
await highlightAndExpectVisible(
|
|
||||||
page,
|
|
||||||
page.getByRole("img", { name: "gateway" })
|
|
||||||
);
|
|
||||||
await highlightAndExpectVisible(page, page.getByText("10.10.0.1"));
|
|
||||||
await highlightAndExpectVisible(page, page.getByText("OPC-UA"));
|
|
||||||
await highlightAndExpectVisible(
|
|
||||||
page,
|
|
||||||
page.getByRole("paragraph").filter({ hasText: "Status" })
|
|
||||||
);
|
|
||||||
await highlightAndExpectVisible(
|
|
||||||
page,
|
|
||||||
page.getByText("Server betriebsbereit")
|
|
||||||
);
|
|
||||||
}
|
|
||||||
232
playwright/tests/pages/digitalInputs/digitalInputs.test.ts
Normal file
232
playwright/tests/pages/digitalInputs/digitalInputs.test.ts
Normal file
@@ -0,0 +1,232 @@
|
|||||||
|
import { test, expect } from "@playwright/test";
|
||||||
|
import { headerTest } from "@/playwright/tests/components/header/headerTest";
|
||||||
|
import { navTest } from "@/playwright/tests/components/navigation/navTest";
|
||||||
|
import { footerTest } from "@/playwright/tests/components/footer/footerTest";
|
||||||
|
|
||||||
|
test.use({
|
||||||
|
viewport: {
|
||||||
|
height: 1080,
|
||||||
|
width: 1920,
|
||||||
|
},
|
||||||
|
});
|
||||||
|
|
||||||
|
test("digitalInputs", async ({ page }) => {
|
||||||
|
await page.goto("/digitalInputs");
|
||||||
|
// Gemeinsame Layout-Checks
|
||||||
|
await headerTest(page);
|
||||||
|
await navTest(page);
|
||||||
|
await footerTest(page);
|
||||||
|
//Snapshot
|
||||||
|
await expect(page.getByRole("main")).toMatchAriaSnapshot(`
|
||||||
|
- heading "Meldungseingänge" [level=1]
|
||||||
|
- heading /Meldungseingänge 1 – \\d+/ [level=2]
|
||||||
|
- table:
|
||||||
|
- rowgroup:
|
||||||
|
- row "Eingang Zustand Bezeichnung Aktion":
|
||||||
|
- cell "Eingang"
|
||||||
|
- cell "Zustand"
|
||||||
|
- cell "Bezeichnung"
|
||||||
|
- cell "Aktion"
|
||||||
|
- rowgroup:
|
||||||
|
- row "1 ● DE 1":
|
||||||
|
- cell "1"
|
||||||
|
- cell "●"
|
||||||
|
- cell "DE 1"
|
||||||
|
- cell
|
||||||
|
- row "2 ● DE 2":
|
||||||
|
- cell "2"
|
||||||
|
- cell "●"
|
||||||
|
- cell "DE 2"
|
||||||
|
- cell
|
||||||
|
- row "3 ● DE 3":
|
||||||
|
- cell "3"
|
||||||
|
- cell "●"
|
||||||
|
- cell "DE 3"
|
||||||
|
- cell
|
||||||
|
- row "4 ● DE 4":
|
||||||
|
- cell "4"
|
||||||
|
- cell "●"
|
||||||
|
- cell "DE 4"
|
||||||
|
- cell
|
||||||
|
- row "5 ● DE 5":
|
||||||
|
- cell "5"
|
||||||
|
- cell "●"
|
||||||
|
- cell "DE 5"
|
||||||
|
- cell
|
||||||
|
- row "6 ● DE 6":
|
||||||
|
- cell "6"
|
||||||
|
- cell "●"
|
||||||
|
- cell "DE 6"
|
||||||
|
- cell
|
||||||
|
- row "7 ● DE 7":
|
||||||
|
- cell "7"
|
||||||
|
- cell "●"
|
||||||
|
- cell "DE 7"
|
||||||
|
- cell
|
||||||
|
- row "8 ● DE 8":
|
||||||
|
- cell "8"
|
||||||
|
- cell "●"
|
||||||
|
- cell "DE 8"
|
||||||
|
- cell
|
||||||
|
- row "9 ● DE 9":
|
||||||
|
- cell "9"
|
||||||
|
- cell "●"
|
||||||
|
- cell "DE 9"
|
||||||
|
- cell
|
||||||
|
- row /\\d+ ● DE \\d+/:
|
||||||
|
- cell /\\d+/
|
||||||
|
- cell "●"
|
||||||
|
- cell /DE \\d+/
|
||||||
|
- cell
|
||||||
|
- row /\\d+ ● DE \\d+/:
|
||||||
|
- cell /\\d+/
|
||||||
|
- cell "●"
|
||||||
|
- cell /DE \\d+/
|
||||||
|
- cell
|
||||||
|
- row /\\d+ ● DE \\d+/:
|
||||||
|
- cell /\\d+/
|
||||||
|
- cell "●"
|
||||||
|
- cell /DE \\d+/
|
||||||
|
- cell
|
||||||
|
- row /\\d+ ● DE \\d+/:
|
||||||
|
- cell /\\d+/
|
||||||
|
- cell "●"
|
||||||
|
- cell /DE \\d+/
|
||||||
|
- cell
|
||||||
|
- row /\\d+ ● DE \\d+/:
|
||||||
|
- cell /\\d+/
|
||||||
|
- cell "●"
|
||||||
|
- cell /DE \\d+/
|
||||||
|
- cell
|
||||||
|
- row /\\d+ ● DE \\d+/:
|
||||||
|
- cell /\\d+/
|
||||||
|
- cell "●"
|
||||||
|
- cell /DE \\d+/
|
||||||
|
- cell
|
||||||
|
- row /\\d+ ● DE \\d+/:
|
||||||
|
- cell /\\d+/
|
||||||
|
- cell "●"
|
||||||
|
- cell /DE \\d+/
|
||||||
|
- cell
|
||||||
|
- heading /Meldungseingänge \\d+ – \\d+/ [level=2]
|
||||||
|
- table:
|
||||||
|
- rowgroup:
|
||||||
|
- row "Eingang Zustand Bezeichnung Aktion":
|
||||||
|
- cell "Eingang"
|
||||||
|
- cell "Zustand"
|
||||||
|
- cell "Bezeichnung"
|
||||||
|
- cell "Aktion"
|
||||||
|
- rowgroup:
|
||||||
|
- row /\\d+ ● DE \\d+/:
|
||||||
|
- cell /\\d+/
|
||||||
|
- cell "●"
|
||||||
|
- cell /DE \\d+/
|
||||||
|
- cell
|
||||||
|
- row /\\d+ ● DE \\d+/:
|
||||||
|
- cell /\\d+/
|
||||||
|
- cell "●"
|
||||||
|
- cell /DE \\d+/
|
||||||
|
- cell
|
||||||
|
- row /\\d+ ● DE \\d+/:
|
||||||
|
- cell /\\d+/
|
||||||
|
- cell "●"
|
||||||
|
- cell /DE \\d+/
|
||||||
|
- cell
|
||||||
|
- row /\\d+ ● DE \\d+/:
|
||||||
|
- cell /\\d+/
|
||||||
|
- cell "●"
|
||||||
|
- cell /DE \\d+/
|
||||||
|
- cell
|
||||||
|
- row /\\d+ ● DE \\d+/:
|
||||||
|
- cell /\\d+/
|
||||||
|
- cell "●"
|
||||||
|
- cell /DE \\d+/
|
||||||
|
- cell
|
||||||
|
- row /\\d+ ● DE \\d+/:
|
||||||
|
- cell /\\d+/
|
||||||
|
- cell "●"
|
||||||
|
- cell /DE \\d+/
|
||||||
|
- cell
|
||||||
|
- row /\\d+ ● DE \\d+/:
|
||||||
|
- cell /\\d+/
|
||||||
|
- cell "●"
|
||||||
|
- cell /DE \\d+/
|
||||||
|
- cell
|
||||||
|
- row /\\d+ ● DE \\d+/:
|
||||||
|
- cell /\\d+/
|
||||||
|
- cell "●"
|
||||||
|
- cell /DE \\d+/
|
||||||
|
- cell
|
||||||
|
- row /\\d+ ● DE \\d+/:
|
||||||
|
- cell /\\d+/
|
||||||
|
- cell "●"
|
||||||
|
- cell /DE \\d+/
|
||||||
|
- cell
|
||||||
|
- row /\\d+ ● DE \\d+/:
|
||||||
|
- cell /\\d+/
|
||||||
|
- cell "●"
|
||||||
|
- cell /DE \\d+/
|
||||||
|
- cell
|
||||||
|
- row /\\d+ ● DE \\d+/:
|
||||||
|
- cell /\\d+/
|
||||||
|
- cell "●"
|
||||||
|
- cell /DE \\d+/
|
||||||
|
- cell
|
||||||
|
- row /\\d+ ● DE \\d+/:
|
||||||
|
- cell /\\d+/
|
||||||
|
- cell "●"
|
||||||
|
- cell /DE \\d+/
|
||||||
|
- cell
|
||||||
|
- row /\\d+ ● DE \\d+/:
|
||||||
|
- cell /\\d+/
|
||||||
|
- cell "●"
|
||||||
|
- cell /DE \\d+/
|
||||||
|
- cell
|
||||||
|
- row /\\d+ ● DE \\d+/:
|
||||||
|
- cell /\\d+/
|
||||||
|
- cell "●"
|
||||||
|
- cell /DE \\d+/
|
||||||
|
- cell
|
||||||
|
- row /\\d+ ● DE \\d+/:
|
||||||
|
- cell /\\d+/
|
||||||
|
- cell "●"
|
||||||
|
- cell /DE \\d+/
|
||||||
|
- cell
|
||||||
|
- row /\\d+ ● DE \\d+/:
|
||||||
|
- cell /\\d+/
|
||||||
|
- cell "●"
|
||||||
|
- cell /DE \\d+/
|
||||||
|
- cell
|
||||||
|
`);
|
||||||
|
//Snapshot
|
||||||
|
await page
|
||||||
|
.getByRole("row", { name: "1 ● DE 1", exact: true })
|
||||||
|
.getByRole("cell")
|
||||||
|
.nth(3)
|
||||||
|
.click();
|
||||||
|
await page
|
||||||
|
.getByRole("row", { name: "1 ● DE 1", exact: true })
|
||||||
|
.locator("svg")
|
||||||
|
.nth(1)
|
||||||
|
.click();
|
||||||
|
await expect(page.getByRole("main")).toMatchAriaSnapshot(`
|
||||||
|
- heading "Einstellungen Meldungseingang 1" [level=2]
|
||||||
|
- button "Modal schließen"
|
||||||
|
`);
|
||||||
|
await expect(page.getByRole("main")).toMatchAriaSnapshot(`
|
||||||
|
- text: "Bezeichnung:"
|
||||||
|
- textbox: DE 1
|
||||||
|
- text: "Invertierung:"
|
||||||
|
- switch [checked]
|
||||||
|
- text: "Ein Filterzeit:"
|
||||||
|
- spinbutton /Maximal \\d+ ms erlaubt/
|
||||||
|
- text: "ms Gewichtung:"
|
||||||
|
- spinbutton /Maximal \\d+ erlaubt/
|
||||||
|
- text: "Out of Service:"
|
||||||
|
- switch
|
||||||
|
- text: Aus
|
||||||
|
`);
|
||||||
|
await expect(page.getByRole("main")).toMatchAriaSnapshot(
|
||||||
|
`- button "Speichern"`
|
||||||
|
);
|
||||||
|
});
|
||||||
@@ -1,106 +0,0 @@
|
|||||||
import type { Page } from "@playwright/test";
|
|
||||||
import { highlightAndExpectVisible } from "@playwright/utils/highlight";
|
|
||||||
import { navTest } from "@/playwright/tests/components/navigation/navTest";
|
|
||||||
import { headerTest } from "@/playwright/tests/components/header/headerTest";
|
|
||||||
import { footerTest } from "@/playwright/tests/components/footer/footerTest";
|
|
||||||
|
|
||||||
export async function runDigitalInputsTest(page: Page) {
|
|
||||||
await page.goto("/digitalInputs");
|
|
||||||
//----------------------
|
|
||||||
await headerTest(page);
|
|
||||||
await navTest(page);
|
|
||||||
await footerTest(page);
|
|
||||||
await page.waitForTimeout(400);
|
|
||||||
//--------------------
|
|
||||||
|
|
||||||
await highlightAndExpectVisible(
|
|
||||||
page,
|
|
||||||
page.getByRole("heading", { name: "Meldungseingänge", exact: true })
|
|
||||||
);
|
|
||||||
await page.waitForTimeout(400);
|
|
||||||
await highlightAndExpectVisible(
|
|
||||||
page,
|
|
||||||
page.getByRole("heading", { name: "Meldungseingänge 1 –" }).locator("svg")
|
|
||||||
);
|
|
||||||
await page.waitForTimeout(400);
|
|
||||||
await highlightAndExpectVisible(
|
|
||||||
page,
|
|
||||||
page.getByRole("heading", { name: "Meldungseingänge 1 –" })
|
|
||||||
);
|
|
||||||
await page.waitForTimeout(400);
|
|
||||||
await highlightAndExpectVisible(
|
|
||||||
page,
|
|
||||||
page.getByRole("cell", { name: "Eingang" }).first()
|
|
||||||
);
|
|
||||||
await page.waitForTimeout(400);
|
|
||||||
await highlightAndExpectVisible(
|
|
||||||
page,
|
|
||||||
page.getByRole("cell", { name: "Zustand" }).first()
|
|
||||||
);
|
|
||||||
await page.waitForTimeout(400);
|
|
||||||
await highlightAndExpectVisible(
|
|
||||||
page,
|
|
||||||
page.getByRole("cell", { name: "Bezeichnung" }).first()
|
|
||||||
);
|
|
||||||
await page.waitForTimeout(400);
|
|
||||||
await highlightAndExpectVisible(
|
|
||||||
page,
|
|
||||||
page.getByRole("cell", { name: "Aktion" }).first()
|
|
||||||
);
|
|
||||||
await page.waitForTimeout(400);
|
|
||||||
await highlightAndExpectVisible(
|
|
||||||
page,
|
|
||||||
page.getByRole("cell", { name: "1", exact: true }).locator("svg")
|
|
||||||
);
|
|
||||||
await page.waitForTimeout(400);
|
|
||||||
await highlightAndExpectVisible(page, page.getByText("1", { exact: true }));
|
|
||||||
await page.waitForTimeout(400);
|
|
||||||
await highlightAndExpectVisible(
|
|
||||||
page,
|
|
||||||
page
|
|
||||||
.getByRole("row", { name: "1 ● DE 1", exact: true })
|
|
||||||
.getByRole("cell")
|
|
||||||
.nth(1)
|
|
||||||
);
|
|
||||||
await page.waitForTimeout(400);
|
|
||||||
await highlightAndExpectVisible(
|
|
||||||
page,
|
|
||||||
page.getByRole("row", { name: "1 ● DE 1", exact: true }).locator("span")
|
|
||||||
);
|
|
||||||
await page.waitForTimeout(400);
|
|
||||||
await highlightAndExpectVisible(
|
|
||||||
page,
|
|
||||||
page.getByRole("cell", { name: "DE 1", exact: true })
|
|
||||||
);
|
|
||||||
await page.waitForTimeout(400);
|
|
||||||
await highlightAndExpectVisible(
|
|
||||||
page,
|
|
||||||
page
|
|
||||||
.getByRole("row", { name: "1 ● DE 1", exact: true })
|
|
||||||
.locator("svg")
|
|
||||||
.nth(1)
|
|
||||||
);
|
|
||||||
await page.waitForTimeout(400);
|
|
||||||
await highlightAndExpectVisible(
|
|
||||||
page,
|
|
||||||
page.getByRole("cell", { name: "13", exact: true }).locator("svg")
|
|
||||||
);
|
|
||||||
await page.waitForTimeout(400);
|
|
||||||
await highlightAndExpectVisible(page, page.getByText("13", { exact: true }));
|
|
||||||
await page.waitForTimeout(400);
|
|
||||||
await highlightAndExpectVisible(
|
|
||||||
page,
|
|
||||||
page.getByRole("row", { name: "● DE 13" }).getByRole("cell").nth(1)
|
|
||||||
);
|
|
||||||
await page.waitForTimeout(400);
|
|
||||||
await highlightAndExpectVisible(
|
|
||||||
page,
|
|
||||||
page.getByRole("row", { name: "● DE 13" }).locator("span")
|
|
||||||
);
|
|
||||||
await page.waitForTimeout(400);
|
|
||||||
await highlightAndExpectVisible(
|
|
||||||
page,
|
|
||||||
page.getByRole("cell", { name: "DE 13" })
|
|
||||||
);
|
|
||||||
await page.waitForTimeout(1000);
|
|
||||||
}
|
|
||||||
144
playwright/tests/pages/digitalOutputs/digitalOutputs.test.ts
Normal file
144
playwright/tests/pages/digitalOutputs/digitalOutputs.test.ts
Normal file
@@ -0,0 +1,144 @@
|
|||||||
|
import { test, expect } from "@playwright/test";
|
||||||
|
import { headerTest } from "@/playwright/tests/components/header/headerTest";
|
||||||
|
import { navTest } from "@/playwright/tests/components/navigation/navTest";
|
||||||
|
import { footerTest } from "@/playwright/tests/components/footer/footerTest";
|
||||||
|
|
||||||
|
test.use({
|
||||||
|
viewport: {
|
||||||
|
height: 800,
|
||||||
|
width: 1280,
|
||||||
|
},
|
||||||
|
});
|
||||||
|
|
||||||
|
test("digitalOutputs", async ({ page }) => {
|
||||||
|
await page.goto("/dashboard");
|
||||||
|
// Gemeinsame Layout-Checks
|
||||||
|
await headerTest(page);
|
||||||
|
await navTest(page);
|
||||||
|
await footerTest(page);
|
||||||
|
await page.getByRole("link", { name: "Schaltausgänge" }).click();
|
||||||
|
await expect(page.locator("h1")).toBeVisible();
|
||||||
|
await expect(
|
||||||
|
page.locator("h2").filter({ hasText: "Schaltausgänge" }).locator("svg")
|
||||||
|
).toBeVisible();
|
||||||
|
await expect(
|
||||||
|
page.locator("h2").filter({ hasText: "Schaltausgänge" })
|
||||||
|
).toBeVisible();
|
||||||
|
await expect(
|
||||||
|
page.getByRole("cell", { name: "Ausgang", exact: true })
|
||||||
|
).toBeVisible();
|
||||||
|
await expect(page.getByRole("cell", { name: "Bezeichnung" })).toBeVisible();
|
||||||
|
await expect(page.getByRole("cell", { name: "Schalter" })).toBeVisible();
|
||||||
|
await expect(page.getByRole("cell", { name: "Aktion" })).toBeVisible();
|
||||||
|
await expect(
|
||||||
|
page.getByRole("cell", { name: "1", exact: true }).locator("svg")
|
||||||
|
).toBeVisible();
|
||||||
|
await expect(
|
||||||
|
page.getByRole("cell", { name: "1", exact: true })
|
||||||
|
).toBeVisible();
|
||||||
|
await expect(
|
||||||
|
page.getByRole("cell", { name: "2", exact: true })
|
||||||
|
).toBeVisible();
|
||||||
|
await expect(
|
||||||
|
page.getByRole("cell", { name: "3", exact: true })
|
||||||
|
).toBeVisible();
|
||||||
|
await expect(
|
||||||
|
page.getByRole("cell", { name: "4", exact: true })
|
||||||
|
).toBeVisible();
|
||||||
|
await expect(page.getByRole("cell", { name: "Ausgang1" })).toBeVisible();
|
||||||
|
await expect(page.getByRole("cell", { name: "Ausgang2" })).toBeVisible();
|
||||||
|
await expect(page.getByRole("cell", { name: "Ausgang3" })).toBeVisible();
|
||||||
|
await expect(page.getByRole("cell", { name: "Ausgang4" })).toBeVisible();
|
||||||
|
await expect(
|
||||||
|
page.getByRole("row", { name: "Ausgang1" }).getByRole("cell").nth(2)
|
||||||
|
).toBeVisible();
|
||||||
|
await expect(
|
||||||
|
page.getByRole("row", { name: "Ausgang2" }).getByRole("cell").nth(2)
|
||||||
|
).toBeVisible();
|
||||||
|
await expect(
|
||||||
|
page.getByRole("row", { name: "Ausgang3" }).getByRole("cell").nth(2)
|
||||||
|
).toBeVisible();
|
||||||
|
await expect(
|
||||||
|
page.getByRole("row", { name: "Ausgang4" }).getByRole("cell").nth(2)
|
||||||
|
).toBeVisible();
|
||||||
|
await expect(
|
||||||
|
page.getByRole("row", { name: "Ausgang1" }).getByRole("cell").nth(3)
|
||||||
|
).toBeVisible();
|
||||||
|
await expect(
|
||||||
|
page.getByRole("row", { name: "Ausgang2" }).getByRole("cell").nth(3)
|
||||||
|
).toBeVisible();
|
||||||
|
await expect(
|
||||||
|
page.getByRole("row", { name: "Ausgang3" }).getByRole("cell").nth(3)
|
||||||
|
).toBeVisible();
|
||||||
|
await expect(
|
||||||
|
page.getByRole("row", { name: "Ausgang4" }).getByRole("cell").nth(3)
|
||||||
|
).toBeVisible();
|
||||||
|
await page
|
||||||
|
.getByRole("row", { name: "Ausgang1" })
|
||||||
|
.locator("svg")
|
||||||
|
.nth(1)
|
||||||
|
.click();
|
||||||
|
await expect(page.getByRole("main")).toMatchAriaSnapshot(`
|
||||||
|
- heading "Einstellungen Schaltausgang 1" [level=2]
|
||||||
|
- button "Modal schließen"
|
||||||
|
`);
|
||||||
|
await expect(page.getByRole("main")).toMatchAriaSnapshot(`
|
||||||
|
- text: "Bezeichnung:"
|
||||||
|
- textbox "z. B. Licht Relais 1"
|
||||||
|
`);
|
||||||
|
await expect(page.getByRole("main")).toMatchAriaSnapshot(
|
||||||
|
`- button "Speichern"`
|
||||||
|
);
|
||||||
|
await page.getByRole("button", { name: "Modal schließen" }).click();
|
||||||
|
await page
|
||||||
|
.getByRole("row", { name: "Ausgang2" })
|
||||||
|
.locator("svg")
|
||||||
|
.nth(1)
|
||||||
|
.click();
|
||||||
|
await expect(page.getByRole("main")).toMatchAriaSnapshot(`
|
||||||
|
- heading "Einstellungen Schaltausgang 2" [level=2]
|
||||||
|
- button "Modal schließen"
|
||||||
|
`);
|
||||||
|
await expect(page.getByRole("main")).toMatchAriaSnapshot(`
|
||||||
|
- text: "Bezeichnung:"
|
||||||
|
- textbox "z. B. Licht Relais 1"
|
||||||
|
`);
|
||||||
|
await expect(page.getByRole("main")).toMatchAriaSnapshot(
|
||||||
|
`- button "Speichern"`
|
||||||
|
);
|
||||||
|
await page.getByRole("button", { name: "Modal schließen" }).click();
|
||||||
|
await page
|
||||||
|
.getByRole("row", { name: "Ausgang3" })
|
||||||
|
.locator("svg")
|
||||||
|
.nth(1)
|
||||||
|
.click();
|
||||||
|
await expect(page.getByRole("main")).toMatchAriaSnapshot(`
|
||||||
|
- heading "Einstellungen Schaltausgang 3" [level=2]
|
||||||
|
- button "Modal schließen"
|
||||||
|
`);
|
||||||
|
await expect(page.getByRole("main")).toMatchAriaSnapshot(`
|
||||||
|
- text: "Bezeichnung:"
|
||||||
|
- textbox "z. B. Licht Relais 1"
|
||||||
|
`);
|
||||||
|
await expect(page.getByRole("main")).toMatchAriaSnapshot(
|
||||||
|
`- button "Speichern"`
|
||||||
|
);
|
||||||
|
await page.getByRole("button", { name: "Modal schließen" }).click();
|
||||||
|
await page
|
||||||
|
.getByRole("row", { name: "Ausgang4" })
|
||||||
|
.locator("svg")
|
||||||
|
.nth(1)
|
||||||
|
.click();
|
||||||
|
await expect(page.getByRole("main")).toMatchAriaSnapshot(`
|
||||||
|
- heading "Einstellungen Schaltausgang 4" [level=2]
|
||||||
|
- button "Modal schließen"
|
||||||
|
`);
|
||||||
|
await expect(page.getByRole("main")).toMatchAriaSnapshot(`
|
||||||
|
- text: "Bezeichnung:"
|
||||||
|
- textbox "z. B. Licht Relais 1"
|
||||||
|
`);
|
||||||
|
await expect(page.getByRole("main")).toMatchAriaSnapshot(
|
||||||
|
`- button "Speichern"`
|
||||||
|
);
|
||||||
|
await page.getByRole("button", { name: "Modal schließen" }).click();
|
||||||
|
});
|
||||||
@@ -1,64 +0,0 @@
|
|||||||
import type { Page } from "@playwright/test";
|
|
||||||
import { highlightAndExpectVisible } from "@playwright/utils/highlight";
|
|
||||||
import { navTest } from "@/playwright/tests/components/navigation/navTest";
|
|
||||||
import { headerTest } from "@/playwright/tests/components/header/headerTest";
|
|
||||||
import { footerTest } from "@/playwright/tests/components/footer/footerTest";
|
|
||||||
|
|
||||||
export async function runDigitalOutputsTest(page: Page) {
|
|
||||||
await page.goto("/digitalOutputs");
|
|
||||||
//----------------------
|
|
||||||
await headerTest(page);
|
|
||||||
await navTest(page);
|
|
||||||
await footerTest(page);
|
|
||||||
// Wait a moment for initial redux fetch and render in slower CI environments
|
|
||||||
await page.waitForTimeout(400);
|
|
||||||
//----------------------
|
|
||||||
await highlightAndExpectVisible(page, page.locator("h1"));
|
|
||||||
page.locator("h1").click();
|
|
||||||
await highlightAndExpectVisible(
|
|
||||||
page,
|
|
||||||
page.locator("h2").filter({ hasText: "Schaltausgänge" })
|
|
||||||
);
|
|
||||||
|
|
||||||
page
|
|
||||||
.locator("h2")
|
|
||||||
.filter({ hasText: "Schaltausgänge" })
|
|
||||||
.locator("svg")
|
|
||||||
.click();
|
|
||||||
page.locator("h2").filter({ hasText: "Schaltausgänge" }).click();
|
|
||||||
// Wait for the outputs table to render and be visible
|
|
||||||
const table = page.locator("table");
|
|
||||||
await table.first().waitFor({ state: "visible", timeout: 15000 });
|
|
||||||
|
|
||||||
// Prefer robust selection: select the row by its first cell text matching the id "2"
|
|
||||||
const rowAusgang2 = table
|
|
||||||
.getByRole("row")
|
|
||||||
.filter({ has: page.getByRole("cell", { name: /^\s*2\s*$/ }) })
|
|
||||||
.first();
|
|
||||||
await rowAusgang2.waitFor({ state: "visible", timeout: 15000 });
|
|
||||||
await rowAusgang2.click();
|
|
||||||
await highlightAndExpectVisible(
|
|
||||||
page,
|
|
||||||
page.getByRole("cell", { name: "Schalter" })
|
|
||||||
);
|
|
||||||
await highlightAndExpectVisible(
|
|
||||||
page,
|
|
||||||
page.getByRole("cell", { name: "Schalter" })
|
|
||||||
);
|
|
||||||
await highlightAndExpectVisible(
|
|
||||||
page,
|
|
||||||
page.getByRole("cell", { name: "Aktion" })
|
|
||||||
);
|
|
||||||
// Interact with the switch icon within each row deterministically
|
|
||||||
for (const id of [1, 2, 3, 4]) {
|
|
||||||
const row = table
|
|
||||||
.getByRole("row")
|
|
||||||
.filter({
|
|
||||||
has: page.getByRole("cell", { name: new RegExp(`^\\s*${id}\\s*$`) }),
|
|
||||||
})
|
|
||||||
.first();
|
|
||||||
await row.waitFor({ state: "visible", timeout: 10000 });
|
|
||||||
const switchIcon = row.locator("td >> nth=2").locator("svg");
|
|
||||||
await switchIcon.first().click();
|
|
||||||
}
|
|
||||||
}
|
|
||||||
@@ -0,0 +1,104 @@
|
|||||||
|
import { test, expect } from "@playwright/test";
|
||||||
|
import { headerTest } from "@/playwright/tests/components/header/headerTest";
|
||||||
|
import { navTest } from "@/playwright/tests/components/navigation/navTest";
|
||||||
|
import { footerTest } from "@/playwright/tests/components/footer/footerTest";
|
||||||
|
|
||||||
|
test.use({
|
||||||
|
viewport: {
|
||||||
|
height: 900,
|
||||||
|
width: 1600,
|
||||||
|
},
|
||||||
|
});
|
||||||
|
|
||||||
|
test("Kabelüberwachung", async ({ page }) => {
|
||||||
|
await page.goto("/kabelueberwachung");
|
||||||
|
// Gemeinsame Layout-Checks
|
||||||
|
await headerTest(page);
|
||||||
|
await navTest(page);
|
||||||
|
await footerTest(page);
|
||||||
|
await expect(page.getByRole("button", { name: "Rack 1" })).toBeVisible();
|
||||||
|
await expect(page.getByRole("button", { name: "Rack 2" })).toBeVisible();
|
||||||
|
await expect(page.getByRole("button", { name: "Rack 3" })).toBeVisible();
|
||||||
|
await expect(page.getByRole("button", { name: "Rack 4" })).toBeVisible();
|
||||||
|
//--
|
||||||
|
await expect(page.getByRole("main")).toMatchAriaSnapshot(`
|
||||||
|
- text: "1"
|
||||||
|
- heading /KÜ\\d+-FO/ [level=3]
|
||||||
|
- button "⚙"
|
||||||
|
- text: "/Betrieb Alarm Erdschluss ISO: \\\\d+,\\\\d+ MOhm RSL: \\\\d+,\\\\d+ kOhm Kabel 1 V4\\\\.\\\\d+ Detailansicht/"
|
||||||
|
- button "ISO"
|
||||||
|
- button "RSL"
|
||||||
|
- button "TDR"
|
||||||
|
- button "KVZ"
|
||||||
|
`);
|
||||||
|
await expect(page.getByRole("main")).toMatchAriaSnapshot(`
|
||||||
|
- text: "2"
|
||||||
|
- heading /KÜ\\d+-FO/ [level=3]
|
||||||
|
- button "⚙"
|
||||||
|
- text: "/Betrieb Alarm Messpannung ISO: \\\\d+,\\\\d+ MOhm RSL: \\\\d+,\\\\d+ kOhm Kabel 2 V4\\\\.\\\\d+ Detailansicht/"
|
||||||
|
- button "ISO"
|
||||||
|
- button "RSL"
|
||||||
|
- button
|
||||||
|
- button "KVZ"
|
||||||
|
`);
|
||||||
|
await expect(page.getByRole("main")).toMatchAriaSnapshot(`
|
||||||
|
- text: "3"
|
||||||
|
- heading /KÜ\\d+-FO/ [level=3]
|
||||||
|
- button "⚙"
|
||||||
|
- text: "/Betrieb Alarm Erdschluss ISO: \\\\d+,\\\\d+ MOhm RSL: \\\\d+,\\\\d+ kOhm Kabel 3 V4\\\\.\\\\d+ Detailansicht/"
|
||||||
|
- button "ISO"
|
||||||
|
- button "RSL"
|
||||||
|
- button "TDR"
|
||||||
|
- button "KVZ"
|
||||||
|
`);
|
||||||
|
await expect(page.getByRole("main")).toMatchAriaSnapshot(`
|
||||||
|
- text: "4"
|
||||||
|
- heading /KÜ\\d+-FO/ [level=3]
|
||||||
|
- button "⚙"
|
||||||
|
- text: "/Betrieb Alarm Aderbruch ISO: \\\\d+,\\\\d+ MOhm RSL: \\\\d+,\\\\d+ kOhm Kabel 4 V4\\\\.\\\\d+ Detailansicht/"
|
||||||
|
- button "ISO"
|
||||||
|
- button "RSL"
|
||||||
|
- button "TDR"
|
||||||
|
- button "KVZ nicht verfügbar" [disabled]
|
||||||
|
`);
|
||||||
|
await expect(page.getByRole("main")).toMatchAriaSnapshot(`
|
||||||
|
- text: "5"
|
||||||
|
- heading /KÜ\\d+-FO/ [level=3]
|
||||||
|
- button "⚙"
|
||||||
|
- text: "/Betrieb Alarm ISO: \\\\d+,\\\\d+ MOhm RSL: \\\\d+,\\\\d+ kOhm Kabel 5 V4\\\\.\\\\d+ Detailansicht/"
|
||||||
|
- button "ISO"
|
||||||
|
- button "RSL"
|
||||||
|
- button "TDR"
|
||||||
|
- button "KVZ nicht verfügbar" [disabled]
|
||||||
|
`);
|
||||||
|
await expect(page.getByRole("main")).toMatchAriaSnapshot(`
|
||||||
|
- text: "6"
|
||||||
|
- heading /KÜ\\d+-FO/ [level=3]
|
||||||
|
- button "⚙"
|
||||||
|
- text: "/Betrieb Alarm ISO: \\\\d+,\\\\d+ MOhm RSL: \\\\d+,\\\\d+ kOhm Kabel 6 V4\\\\.\\\\d+ Detailansicht/"
|
||||||
|
- button "ISO"
|
||||||
|
- button "RSL"
|
||||||
|
- button "TDR"
|
||||||
|
- button "KVZ nicht verfügbar" [disabled]
|
||||||
|
`);
|
||||||
|
await expect(page.getByRole("main")).toMatchAriaSnapshot(`
|
||||||
|
- text: "7"
|
||||||
|
- heading /KÜ\\d+-FO/ [level=3]
|
||||||
|
- button "⚙"
|
||||||
|
- text: "/Betrieb Alarm Schleifenfehler ISO: \\\\d+,\\\\d+ MOhm RSL: \\\\d+,\\\\d+ kOhm Kabel 7 V4\\\\.\\\\d+ Detailansicht/"
|
||||||
|
- button "ISO"
|
||||||
|
- button "RSL"
|
||||||
|
- button "TDR"
|
||||||
|
- button "KVZ nicht verfügbar" [disabled]
|
||||||
|
`);
|
||||||
|
await expect(page.getByRole("main")).toMatchAriaSnapshot(`
|
||||||
|
- text: "8"
|
||||||
|
- heading /KÜ\\d+-FO/ [level=3]
|
||||||
|
- button "⚙"
|
||||||
|
- text: "/Betrieb Alarm Isolationsfehler ISO: \\\\d+,\\\\d+ MOhm RSL: \\\\d+,\\\\d+ kOhm Kabel_8 in Salzgitter bei Hannover Kabel_8 in Salzgitter bei Hannover V4\\\\.\\\\d+ Detailansicht/"
|
||||||
|
- button "ISO"
|
||||||
|
- button "RSL"
|
||||||
|
- button "TDR"
|
||||||
|
- button "KVZ nicht verfügbar" [disabled]
|
||||||
|
`);
|
||||||
|
});
|
||||||
@@ -1,366 +0,0 @@
|
|||||||
import type { Page } from "@playwright/test";
|
|
||||||
import { highlightAndExpectVisible } from "@playwright/utils/highlight";
|
|
||||||
import { navTest } from "@/playwright/tests/components/navigation/navTest";
|
|
||||||
import { headerTest } from "@/playwright/tests/components/header/headerTest";
|
|
||||||
import { footerTest } from "@/playwright/tests/components/footer/footerTest";
|
|
||||||
|
|
||||||
export async function runCableMonitoringTest(page: Page) {
|
|
||||||
await page.goto("/kabelueberwachung");
|
|
||||||
//----------------------
|
|
||||||
await headerTest(page);
|
|
||||||
await navTest(page);
|
|
||||||
await footerTest(page);
|
|
||||||
await page.waitForTimeout(400);
|
|
||||||
//----------------------
|
|
||||||
|
|
||||||
// Rack Buttons
|
|
||||||
for (const rack of [1, 2, 3, 4, 1]) {
|
|
||||||
await highlightAndExpectVisible(
|
|
||||||
page,
|
|
||||||
page.getByRole("button", { name: `Rack ${rack}` })
|
|
||||||
);
|
|
||||||
await page.waitForTimeout(400);
|
|
||||||
}
|
|
||||||
|
|
||||||
// Kabel 1
|
|
||||||
await highlightAndExpectVisible(page, page.getByText("1", { exact: true }));
|
|
||||||
await page.waitForTimeout(400);
|
|
||||||
|
|
||||||
await highlightAndExpectVisible(
|
|
||||||
page,
|
|
||||||
page.locator("div").filter({ hasText: /^8$/ })
|
|
||||||
);
|
|
||||||
await page.waitForTimeout(400);
|
|
||||||
|
|
||||||
await highlightAndExpectVisible(
|
|
||||||
page,
|
|
||||||
page
|
|
||||||
.locator("div")
|
|
||||||
.filter({ hasText: /^1KÜ705-FO⚙$/ })
|
|
||||||
.getByRole("heading")
|
|
||||||
);
|
|
||||||
await page.waitForTimeout(400);
|
|
||||||
|
|
||||||
await highlightAndExpectVisible(page, page.getByText("1", { exact: true }));
|
|
||||||
await page.waitForTimeout(400);
|
|
||||||
|
|
||||||
await highlightAndExpectVisible(
|
|
||||||
page,
|
|
||||||
page
|
|
||||||
.locator("div")
|
|
||||||
.filter({ hasText: /^1KÜ705-FO⚙$/ })
|
|
||||||
.getByRole("button")
|
|
||||||
);
|
|
||||||
await page.waitForTimeout(400);
|
|
||||||
|
|
||||||
await highlightAndExpectVisible(
|
|
||||||
page,
|
|
||||||
page.locator(".flex.flex-col > span").first()
|
|
||||||
);
|
|
||||||
await page.waitForTimeout(400);
|
|
||||||
|
|
||||||
await highlightAndExpectVisible(
|
|
||||||
page,
|
|
||||||
page.locator(".w-\\[0\\.625rem\\]").first()
|
|
||||||
);
|
|
||||||
await page.waitForTimeout(400);
|
|
||||||
|
|
||||||
await highlightAndExpectVisible(
|
|
||||||
page,
|
|
||||||
page.locator("span:nth-child(2)").first()
|
|
||||||
);
|
|
||||||
await page.waitForTimeout(400);
|
|
||||||
|
|
||||||
await highlightAndExpectVisible(
|
|
||||||
page,
|
|
||||||
page
|
|
||||||
.locator(".w-\\[0\\.625rem\\].h-\\[0\\.625rem\\].rounded-full.bg-red-500")
|
|
||||||
.first()
|
|
||||||
);
|
|
||||||
await page.waitForTimeout(400);
|
|
||||||
|
|
||||||
await highlightAndExpectVisible(page, page.getByText("Erdschluss").first());
|
|
||||||
await page.waitForTimeout(400);
|
|
||||||
|
|
||||||
await highlightAndExpectVisible(
|
|
||||||
page,
|
|
||||||
page.locator(".text-center > span:nth-child(2)").first()
|
|
||||||
);
|
|
||||||
await page.waitForTimeout(400);
|
|
||||||
|
|
||||||
await highlightAndExpectVisible(
|
|
||||||
page,
|
|
||||||
page.getByText(/^RSL: \d+,\d{3} kOhm$/).first()
|
|
||||||
);
|
|
||||||
await page.waitForTimeout(400);
|
|
||||||
|
|
||||||
// Use a unique locator to avoid strict mode violation (two elements contain text "Kabel 1").
|
|
||||||
// The card exposes a title attribute "Kabel 1", so prefer getByTitle for a single match.
|
|
||||||
await highlightAndExpectVisible(page, page.getByTitle("Kabel 1").first());
|
|
||||||
await page.waitForTimeout(400);
|
|
||||||
|
|
||||||
await highlightAndExpectVisible(
|
|
||||||
page,
|
|
||||||
page.locator(".text-black.text-\\[0\\.625rem\\].font-semibold").first()
|
|
||||||
);
|
|
||||||
await page.waitForTimeout(400);
|
|
||||||
|
|
||||||
await highlightAndExpectVisible(
|
|
||||||
page,
|
|
||||||
page.locator(".bg-littwin-blue.text-white.text-\\[0\\.625rem\\]").first()
|
|
||||||
);
|
|
||||||
await page.waitForTimeout(400);
|
|
||||||
|
|
||||||
await highlightAndExpectVisible(
|
|
||||||
page,
|
|
||||||
page.locator(".flex > button:nth-child(2)").first()
|
|
||||||
);
|
|
||||||
await page.waitForTimeout(400);
|
|
||||||
|
|
||||||
await highlightAndExpectVisible(
|
|
||||||
page,
|
|
||||||
page.locator(".bg-littwin-blue.text-white.cursor-pointer").first()
|
|
||||||
);
|
|
||||||
await page.waitForTimeout(400);
|
|
||||||
|
|
||||||
await highlightAndExpectVisible(page, page.getByText("V4.20"));
|
|
||||||
await page.waitForTimeout(400);
|
|
||||||
|
|
||||||
// Kabel 8
|
|
||||||
await highlightAndExpectVisible(page, page.getByText("8", { exact: true }));
|
|
||||||
await page.waitForTimeout(400);
|
|
||||||
|
|
||||||
await highlightAndExpectVisible(
|
|
||||||
page,
|
|
||||||
page
|
|
||||||
.locator("div")
|
|
||||||
.filter({ hasText: /^8KÜ705-FO⚙$/ })
|
|
||||||
.getByRole("heading")
|
|
||||||
);
|
|
||||||
await page.waitForTimeout(400);
|
|
||||||
|
|
||||||
await highlightAndExpectVisible(
|
|
||||||
page,
|
|
||||||
page
|
|
||||||
.locator("div")
|
|
||||||
.filter({ hasText: /^8KÜ705-FO⚙$/ })
|
|
||||||
.getByRole("button")
|
|
||||||
);
|
|
||||||
await page.waitForTimeout(400);
|
|
||||||
|
|
||||||
await highlightAndExpectVisible(
|
|
||||||
page,
|
|
||||||
page
|
|
||||||
.locator(
|
|
||||||
"div:nth-child(8) > .relative.bg-gray-300 > .relative.w-\\[7\\.075rem\\] > .flex.flex-col.mt-\\[0\\.625rem\\] > .flex.items-center.space-x-\\[0\\.25rem\\] > .flex.flex-col.items-start > span"
|
|
||||||
)
|
|
||||||
.first()
|
|
||||||
);
|
|
||||||
await page.waitForTimeout(400);
|
|
||||||
|
|
||||||
await highlightAndExpectVisible(
|
|
||||||
page,
|
|
||||||
page.locator(
|
|
||||||
"div:nth-child(8) > .relative.bg-gray-300 > .relative.w-\\[7\\.075rem\\] > .flex.flex-col.mt-\\[0\\.625rem\\] > .flex.items-center.space-x-\\[0\\.25rem\\] > .flex.flex-col.items-start > span:nth-child(2)"
|
|
||||||
)
|
|
||||||
);
|
|
||||||
await page.waitForTimeout(400);
|
|
||||||
|
|
||||||
await highlightAndExpectVisible(
|
|
||||||
page,
|
|
||||||
page.locator(
|
|
||||||
"div:nth-child(8) > .relative.bg-gray-300 > .relative.w-\\[7\\.075rem\\] > .flex.flex-col.mt-\\[0\\.625rem\\] > .flex.items-center.space-x-\\[0\\.25rem\\] > .flex.flex-col.items-center > .w-\\[0\\.625rem\\].h-\\[0\\.625rem\\].bg-green-500"
|
|
||||||
)
|
|
||||||
);
|
|
||||||
await page.waitForTimeout(400);
|
|
||||||
|
|
||||||
await highlightAndExpectVisible(
|
|
||||||
page,
|
|
||||||
page.locator(
|
|
||||||
"div:nth-child(8) > .relative.bg-gray-300 > .relative.w-\\[7\\.075rem\\] > .flex.flex-col.mt-\\[0\\.625rem\\] > .flex.items-center.space-x-\\[0\\.25rem\\] > .flex.flex-col.items-center > .w-\\[0\\.625rem\\].h-\\[0\\.625rem\\].rounded-full.bg-red-500"
|
|
||||||
)
|
|
||||||
);
|
|
||||||
await page.waitForTimeout(400);
|
|
||||||
|
|
||||||
await highlightAndExpectVisible(page, page.getByText("Isolationsfehler"));
|
|
||||||
await page.waitForTimeout(400);
|
|
||||||
|
|
||||||
await highlightAndExpectVisible(
|
|
||||||
page,
|
|
||||||
page.locator(
|
|
||||||
"div:nth-child(8) > .relative.bg-gray-300 > .relative.w-\\[7\\.075rem\\] > .relative.mt-\\[3\\.125rem\\] > .text-center > span:nth-child(2)"
|
|
||||||
)
|
|
||||||
);
|
|
||||||
await page.waitForTimeout(400);
|
|
||||||
|
|
||||||
await highlightAndExpectVisible(
|
|
||||||
page,
|
|
||||||
page.getByText(/^RSL: \d+,\d{3} kOhm$/).nth(3)
|
|
||||||
);
|
|
||||||
await page.waitForTimeout(400);
|
|
||||||
|
|
||||||
// For cable 8 the UI shows a long name and exposes it via title attribute; target it explicitly
|
|
||||||
await highlightAndExpectVisible(
|
|
||||||
page,
|
|
||||||
page.getByTitle("Kabel_8 in Salzgitter bei Hannover").first()
|
|
||||||
);
|
|
||||||
await page.waitForTimeout(400);
|
|
||||||
|
|
||||||
await page.waitForTimeout(400);
|
|
||||||
|
|
||||||
await highlightAndExpectVisible(
|
|
||||||
page,
|
|
||||||
page.locator(".bg-littwin-blue.text-white.text-\\[0\\.625rem\\]").first()
|
|
||||||
);
|
|
||||||
await page.waitForTimeout(400);
|
|
||||||
|
|
||||||
// ... (weitere Schritte können nach diesem Muster ergänzt werden)
|
|
||||||
|
|
||||||
// Beispiel für weitere Schritte aus dem Kommentar:
|
|
||||||
await highlightAndExpectVisible(
|
|
||||||
page,
|
|
||||||
page.locator(".bg-littwin-blue.text-white.text-\\[0\\.625rem\\]").first()
|
|
||||||
);
|
|
||||||
await page.waitForTimeout(400);
|
|
||||||
await page
|
|
||||||
.locator(".bg-littwin-blue.text-white.text-\\[0\\.625rem\\]")
|
|
||||||
.first()
|
|
||||||
.click();
|
|
||||||
await page.waitForTimeout(400);
|
|
||||||
|
|
||||||
await highlightAndExpectVisible(
|
|
||||||
page,
|
|
||||||
page.getByRole("heading", { name: "Isolationswiderstand" })
|
|
||||||
);
|
|
||||||
await page.waitForTimeout(400);
|
|
||||||
await page.getByRole("heading", { name: "Isolationswiderstand" }).click();
|
|
||||||
await page.waitForTimeout(400);
|
|
||||||
|
|
||||||
await highlightAndExpectVisible(page, page.getByText("KÜ 1"));
|
|
||||||
await page.waitForTimeout(400);
|
|
||||||
await page.getByText("KÜ 1").click();
|
|
||||||
await page.waitForTimeout(400);
|
|
||||||
|
|
||||||
await highlightAndExpectVisible(page, page.getByText("Von"));
|
|
||||||
await page.waitForTimeout(400);
|
|
||||||
await page.getByText("Von").click();
|
|
||||||
await page.waitForTimeout(400);
|
|
||||||
|
|
||||||
await highlightAndExpectVisible(
|
|
||||||
page,
|
|
||||||
page.locator("div").filter({ hasText: /^Von$/ }).getByRole("textbox")
|
|
||||||
);
|
|
||||||
await page.waitForTimeout(400);
|
|
||||||
await page
|
|
||||||
.locator("div")
|
|
||||||
.filter({ hasText: /^Von$/ })
|
|
||||||
.getByRole("textbox")
|
|
||||||
.click();
|
|
||||||
await page.waitForTimeout(400);
|
|
||||||
|
|
||||||
await highlightAndExpectVisible(page, page.getByText("Bis"));
|
|
||||||
await page.waitForTimeout(400);
|
|
||||||
await page.getByText("Bis").click();
|
|
||||||
await page.waitForTimeout(400);
|
|
||||||
|
|
||||||
await highlightAndExpectVisible(
|
|
||||||
page,
|
|
||||||
page.locator("div").filter({ hasText: /^Bis$/ }).getByRole("textbox")
|
|
||||||
);
|
|
||||||
await page.waitForTimeout(400);
|
|
||||||
await page
|
|
||||||
.locator("div")
|
|
||||||
.filter({ hasText: /^Bis$/ })
|
|
||||||
.getByRole("textbox")
|
|
||||||
.click();
|
|
||||||
await page.waitForTimeout(400);
|
|
||||||
|
|
||||||
await highlightAndExpectVisible(
|
|
||||||
page,
|
|
||||||
page.getByRole("button", { name: "Alle Messwerte" })
|
|
||||||
);
|
|
||||||
await page.waitForTimeout(400);
|
|
||||||
await page.getByRole("button", { name: "Alle Messwerte" }).click();
|
|
||||||
await page.waitForTimeout(400);
|
|
||||||
|
|
||||||
await highlightAndExpectVisible(
|
|
||||||
page,
|
|
||||||
page.getByRole("option", { name: "Stündliche Werte" })
|
|
||||||
);
|
|
||||||
await page.waitForTimeout(400);
|
|
||||||
await page.getByRole("option", { name: "Stündliche Werte" }).click();
|
|
||||||
await page.waitForTimeout(400);
|
|
||||||
|
|
||||||
await highlightAndExpectVisible(
|
|
||||||
page,
|
|
||||||
page.getByRole("button", { name: "Stündliche Werte" })
|
|
||||||
);
|
|
||||||
await page.waitForTimeout(400);
|
|
||||||
|
|
||||||
await highlightAndExpectVisible(
|
|
||||||
page,
|
|
||||||
page.getByRole("button", { name: "Daten laden" })
|
|
||||||
);
|
|
||||||
await page.waitForTimeout(400);
|
|
||||||
|
|
||||||
await highlightAndExpectVisible(page, page.locator("canvas"));
|
|
||||||
await page.waitForTimeout(400);
|
|
||||||
|
|
||||||
await highlightAndExpectVisible(
|
|
||||||
page,
|
|
||||||
page.getByRole("button", { name: "" })
|
|
||||||
);
|
|
||||||
await page.waitForTimeout(400);
|
|
||||||
|
|
||||||
await highlightAndExpectVisible(
|
|
||||||
page,
|
|
||||||
page.getByRole("button", { name: "" })
|
|
||||||
);
|
|
||||||
await page.waitForTimeout(400);
|
|
||||||
|
|
||||||
await highlightAndExpectVisible(
|
|
||||||
page,
|
|
||||||
page.getByRole("button", { name: "Messkurve" })
|
|
||||||
);
|
|
||||||
await page.waitForTimeout(400);
|
|
||||||
await page.getByRole("button", { name: "Messkurve" }).click();
|
|
||||||
await page.waitForTimeout(400);
|
|
||||||
|
|
||||||
await highlightAndExpectVisible(
|
|
||||||
page,
|
|
||||||
page.getByRole("option", { name: "Meldungen" })
|
|
||||||
);
|
|
||||||
await page.waitForTimeout(400);
|
|
||||||
await page.getByRole("option", { name: "Meldungen" }).click();
|
|
||||||
await page.waitForTimeout(400);
|
|
||||||
|
|
||||||
await highlightAndExpectVisible(
|
|
||||||
page,
|
|
||||||
page.getByRole("cell", { name: "Prio" })
|
|
||||||
);
|
|
||||||
await page.waitForTimeout(400);
|
|
||||||
|
|
||||||
await highlightAndExpectVisible(
|
|
||||||
page,
|
|
||||||
page.getByRole("cell", { name: "Zeitstempel" })
|
|
||||||
);
|
|
||||||
await page.waitForTimeout(400);
|
|
||||||
|
|
||||||
await highlightAndExpectVisible(
|
|
||||||
page,
|
|
||||||
page.getByRole("cell", { name: "Quelle" })
|
|
||||||
);
|
|
||||||
await page.waitForTimeout(400);
|
|
||||||
|
|
||||||
await highlightAndExpectVisible(
|
|
||||||
page,
|
|
||||||
page.getByRole("cell", { name: "Meldung" })
|
|
||||||
);
|
|
||||||
await page.waitForTimeout(400);
|
|
||||||
|
|
||||||
await highlightAndExpectVisible(
|
|
||||||
page,
|
|
||||||
page.getByRole("cell", { name: "Status" })
|
|
||||||
);
|
|
||||||
await page.waitForTimeout(400);
|
|
||||||
}
|
|
||||||
108
playwright/tests/pages/kabelueberwachung/modals/isoModal.test.ts
Normal file
108
playwright/tests/pages/kabelueberwachung/modals/isoModal.test.ts
Normal file
@@ -0,0 +1,108 @@
|
|||||||
|
import { test, expect } from "@playwright/test";
|
||||||
|
|
||||||
|
test.use({
|
||||||
|
viewport: {
|
||||||
|
height: 800,
|
||||||
|
width: 1280,
|
||||||
|
},
|
||||||
|
});
|
||||||
|
|
||||||
|
test("test", async ({ page }) => {
|
||||||
|
await page.goto("/kabelueberwachung");
|
||||||
|
await page.locator(".bg-littwin-blue.text-white").first().click();
|
||||||
|
await expect(page.getByText("IsolationswiderstandMesskurve")).toBeVisible();
|
||||||
|
await expect(page.getByRole("button", { name: "Messkurve " })).toBeVisible();
|
||||||
|
await expect(page.getByText("KÜ1")).toBeVisible();
|
||||||
|
await expect(page.getByText("Von")).toBeVisible();
|
||||||
|
await expect(
|
||||||
|
page.locator("div").filter({ hasText: /^Von$/ }).getByRole("textbox")
|
||||||
|
).toBeVisible();
|
||||||
|
await page
|
||||||
|
.locator("div")
|
||||||
|
.filter({ hasText: /^Von$/ })
|
||||||
|
.getByRole("textbox")
|
||||||
|
.click();
|
||||||
|
await expect(page.getByLabel("Sunday", { exact: true })).toBeVisible();
|
||||||
|
await expect(page.getByLabel("Saturday", { exact: true })).toBeVisible();
|
||||||
|
await page.getByRole("textbox").first().click();
|
||||||
|
await page.getByRole("textbox").first().click();
|
||||||
|
await page.getByText("IsolationswiderstandMesskurve").click();
|
||||||
|
await expect(page.getByText("Bis")).toBeVisible();
|
||||||
|
await expect(
|
||||||
|
page.locator("div").filter({ hasText: /^Bis$/ }).getByRole("textbox")
|
||||||
|
).toBeVisible();
|
||||||
|
await page
|
||||||
|
.locator("div")
|
||||||
|
.filter({ hasText: /^Bis$/ })
|
||||||
|
.getByRole("textbox")
|
||||||
|
.click();
|
||||||
|
await expect(page.getByLabel("Sunday", { exact: true })).toBeVisible();
|
||||||
|
await expect(page.getByLabel("Saturday", { exact: true })).toBeVisible();
|
||||||
|
await page.getByRole("textbox").nth(1).click();
|
||||||
|
await page.getByText("IsolationswiderstandMesskurve").click();
|
||||||
|
await expect(
|
||||||
|
page.getByRole("button", { name: "Alle Messwerte " })
|
||||||
|
).toBeVisible();
|
||||||
|
await page.getByRole("button", { name: "Alle Messwerte " }).click();
|
||||||
|
// await expect(page.getByRole("button", { name: "Daten laden" })).toBeVisible();
|
||||||
|
await page.getByRole("button", { name: "Alle Messwerte " }).click();
|
||||||
|
await expect(page.locator("canvas")).toBeVisible();
|
||||||
|
await page.getByRole("button", { name: "Alle Messwerte " }).click();
|
||||||
|
await page.getByRole("option", { name: "Stündlich" }).click();
|
||||||
|
await expect(page.getByRole("button", { name: "Stündlich " })).toBeVisible();
|
||||||
|
await page.getByRole("button", { name: "Stündlich " }).click();
|
||||||
|
await page.getByRole("option", { name: "Täglich" }).click();
|
||||||
|
await expect(page.getByRole("button", { name: "Täglich " })).toBeVisible();
|
||||||
|
await page.getByRole("button", { name: "Messkurve " }).click();
|
||||||
|
await page.getByRole("option", { name: "Meldungen" }).click();
|
||||||
|
await expect(page.getByRole("button", { name: "Meldungen " })).toBeVisible();
|
||||||
|
await expect(page.getByText("VonBisAnzeigen")).toBeVisible();
|
||||||
|
await page
|
||||||
|
.locator("div")
|
||||||
|
.filter({ hasText: /^Von$/ })
|
||||||
|
.getByRole("textbox")
|
||||||
|
.click();
|
||||||
|
await expect(page.getByRole("textbox").first()).toBeVisible();
|
||||||
|
await page
|
||||||
|
.locator("div")
|
||||||
|
.filter({ hasText: /^Bis$/ })
|
||||||
|
.getByRole("textbox")
|
||||||
|
.click();
|
||||||
|
await page.getByText("IsolationswiderstandMeldungen").click();
|
||||||
|
await expect(page.getByRole("button", { name: "Anzeigen" })).toBeVisible();
|
||||||
|
await expect(page.getByRole("cell", { name: "Prio" })).toBeVisible();
|
||||||
|
await expect(page.getByRole("cell", { name: "Zeitstempel" })).toBeVisible();
|
||||||
|
await expect(page.getByRole("cell", { name: "Quelle" })).toBeVisible();
|
||||||
|
await expect(page.getByRole("cell", { name: "Meldung" })).toBeVisible();
|
||||||
|
await expect(page.getByRole("cell", { name: "Status" })).toBeVisible();
|
||||||
|
await expect(
|
||||||
|
page
|
||||||
|
.getByRole("row", { name: "12:10:42 CableLine1 Modul online online" })
|
||||||
|
.getByRole("cell")
|
||||||
|
.first()
|
||||||
|
).toBeVisible();
|
||||||
|
await expect(
|
||||||
|
page
|
||||||
|
.getByRole("row", { name: "12:10:42 CableLine1 Aderbruch kommend 1" })
|
||||||
|
.getByRole("cell")
|
||||||
|
.first()
|
||||||
|
).toBeVisible();
|
||||||
|
await expect(
|
||||||
|
page
|
||||||
|
.getByRole("row", { name: "12:10:42 CableLine1 Aderbruch gehend 0" })
|
||||||
|
.getByRole("cell")
|
||||||
|
.first()
|
||||||
|
).toBeVisible();
|
||||||
|
await expect(
|
||||||
|
page
|
||||||
|
.getByRole("row", { name: "12:10:42 CableLine1 Isofehler gehend 0" })
|
||||||
|
.getByRole("cell")
|
||||||
|
.first()
|
||||||
|
).toBeVisible();
|
||||||
|
await page.getByRole("button", { name: "Vollbild" }).click();
|
||||||
|
await page.getByRole("button", { name: "Vollbild verlassen" }).click();
|
||||||
|
await page
|
||||||
|
.getByRole("dialog", { name: "Isolationswiderstand" })
|
||||||
|
.getByLabel("Schließen")
|
||||||
|
.click();
|
||||||
|
});
|
||||||
@@ -0,0 +1,81 @@
|
|||||||
|
import { test, expect } from "@playwright/test";
|
||||||
|
|
||||||
|
test.use({
|
||||||
|
viewport: {
|
||||||
|
height: 800,
|
||||||
|
width: 1280,
|
||||||
|
},
|
||||||
|
});
|
||||||
|
|
||||||
|
test("test", async ({ page }) => {
|
||||||
|
await page.goto("http://localhost:3000/kabelueberwachung");
|
||||||
|
await page.getByRole("button", { name: "KVZ", exact: true }).first().click();
|
||||||
|
await expect(
|
||||||
|
page
|
||||||
|
.getByRole("dialog", { name: "KVZ Zustände & Meldungen" })
|
||||||
|
.getByRole("banner")
|
||||||
|
).toBeVisible();
|
||||||
|
await expect(page.getByText("KÜ 1")).toBeVisible();
|
||||||
|
await expect(page.getByText("Von")).toBeVisible();
|
||||||
|
await expect(
|
||||||
|
page.locator("div").filter({ hasText: /^Von$/ }).getByRole("textbox")
|
||||||
|
).toBeVisible();
|
||||||
|
await page
|
||||||
|
.locator("div")
|
||||||
|
.filter({ hasText: /^Von$/ })
|
||||||
|
.getByRole("textbox")
|
||||||
|
.click();
|
||||||
|
await expect(page.getByLabel("Sunday", { exact: true })).toBeVisible();
|
||||||
|
await expect(page.getByLabel("Saturday", { exact: true })).toBeVisible();
|
||||||
|
await expect(page.getByText("Bis")).toBeVisible();
|
||||||
|
await page
|
||||||
|
.getByRole("dialog", { name: "KVZ Zustände & Meldungen" })
|
||||||
|
.getByRole("banner")
|
||||||
|
.click();
|
||||||
|
await expect(page.getByText("Bis")).toBeVisible();
|
||||||
|
await expect(
|
||||||
|
page.locator("div").filter({ hasText: /^Bis$/ }).getByRole("textbox")
|
||||||
|
).toBeVisible();
|
||||||
|
await page
|
||||||
|
.locator("div")
|
||||||
|
.filter({ hasText: /^Bis$/ })
|
||||||
|
.getByRole("textbox")
|
||||||
|
.click();
|
||||||
|
await expect(page.getByLabel("Sunday", { exact: true })).toBeVisible();
|
||||||
|
await expect(page.getByLabel("Saturday", { exact: true })).toBeVisible();
|
||||||
|
await page
|
||||||
|
.getByRole("dialog", { name: "KVZ Zustände & Meldungen" })
|
||||||
|
.getByRole("banner")
|
||||||
|
.click();
|
||||||
|
await expect(page.getByRole("button", { name: "Anzeigen" })).toBeVisible();
|
||||||
|
await expect(page.getByText("KVZ1")).toBeVisible();
|
||||||
|
await expect(page.getByTitle("Slot 0 LED1: Ein")).toBeVisible();
|
||||||
|
await expect(page.getByText("KVZ2", { exact: true })).toBeVisible();
|
||||||
|
await expect(page.getByTitle("Slot 0 LED2: Aus")).toBeVisible();
|
||||||
|
await expect(page.getByText("KVZ3", { exact: true })).toBeVisible();
|
||||||
|
await expect(page.getByTitle("Slot 0 LED3: Unbekannt")).toBeVisible();
|
||||||
|
await expect(page.getByText("KVZ4", { exact: true })).toBeVisible();
|
||||||
|
await expect(page.getByTitle("Slot 0 LED4: Ein")).toBeVisible();
|
||||||
|
await expect(page.getByRole("cell", { name: "Prio" })).toBeVisible();
|
||||||
|
await expect(page.getByRole("cell", { name: "Zeitstempel" })).toBeVisible();
|
||||||
|
await expect(page.getByRole("cell", { name: "Quelle" })).toBeVisible();
|
||||||
|
await expect(page.getByRole("cell", { name: "Meldung" })).toBeVisible();
|
||||||
|
await expect(page.getByRole("cell", { name: "Status" })).toBeVisible();
|
||||||
|
await expect(
|
||||||
|
page
|
||||||
|
.getByRole("row", { name: "12:10:42 CableLine1 Modul online online" })
|
||||||
|
.getByRole("cell")
|
||||||
|
.first()
|
||||||
|
).toBeVisible();
|
||||||
|
await expect(
|
||||||
|
page
|
||||||
|
.getByRole("row", { name: "12:10:42 CableLine1 Aderbruch kommend 1" })
|
||||||
|
.getByRole("cell")
|
||||||
|
.first()
|
||||||
|
).toBeVisible();
|
||||||
|
await page.getByRole("button", { name: "Vollbild" }).click();
|
||||||
|
await page
|
||||||
|
.getByRole("dialog", { name: "KVZ Zustände & Meldungen" })
|
||||||
|
.getByLabel("Schließen")
|
||||||
|
.click();
|
||||||
|
});
|
||||||
@@ -0,0 +1,90 @@
|
|||||||
|
import { test, expect } from "@playwright/test";
|
||||||
|
|
||||||
|
test.use({
|
||||||
|
viewport: {
|
||||||
|
height: 800,
|
||||||
|
width: 1280,
|
||||||
|
},
|
||||||
|
});
|
||||||
|
|
||||||
|
test("test", async ({ page }) => {
|
||||||
|
await page.goto("http://localhost:3000/kabelueberwachung");
|
||||||
|
await page.locator(".flex > button:nth-child(2)").first().click();
|
||||||
|
await expect(page.getByLabel("Schleifenwiderstand").getByRole("banner"))
|
||||||
|
.toMatchAriaSnapshot(`
|
||||||
|
- banner:
|
||||||
|
- heading "Schleifenwiderstand" [level=3]
|
||||||
|
- button "Vollbild"
|
||||||
|
- button "Schließen"
|
||||||
|
- button "Messkurve "
|
||||||
|
`);
|
||||||
|
await expect(page.getByLabel("Schleifenwiderstand")).toMatchAriaSnapshot(`
|
||||||
|
- text: KÜ 1 Von
|
||||||
|
- textbox: /\\d+\\.\\d+\\.\\d+/
|
||||||
|
- text: Bis
|
||||||
|
- textbox: /\\d+\\.\\d+\\.\\d+/
|
||||||
|
- button "Alle Messwerte "
|
||||||
|
- button "RSL Messung starten"
|
||||||
|
- button "Daten laden"
|
||||||
|
`);
|
||||||
|
await page.getByRole("button", { name: "Alle Messwerte " }).click();
|
||||||
|
await page.getByRole("option", { name: "Stündlich" }).click();
|
||||||
|
await expect(page.getByLabel("Schleifenwiderstand")).toMatchAriaSnapshot(`
|
||||||
|
- text: KÜ 1 Von
|
||||||
|
- textbox: /\\d+\\.\\d+\\.\\d+/
|
||||||
|
- text: Bis
|
||||||
|
- textbox: /\\d+\\.\\d+\\.\\d+/
|
||||||
|
- button "Stündlich "
|
||||||
|
- button "RSL Messung starten"
|
||||||
|
- button "Daten laden"
|
||||||
|
`);
|
||||||
|
await page.getByRole("button", { name: "Stündlich " }).click();
|
||||||
|
await page.getByRole("option", { name: "Täglich" }).click();
|
||||||
|
await expect(page.getByLabel("Schleifenwiderstand")).toMatchAriaSnapshot(`
|
||||||
|
- text: KÜ 1 Von
|
||||||
|
- textbox: /\\d+\\.\\d+\\.\\d+/
|
||||||
|
- text: Bis
|
||||||
|
- textbox: /\\d+\\.\\d+\\.\\d+/
|
||||||
|
- button "Täglich "
|
||||||
|
- button "RSL Messung starten"
|
||||||
|
- button "Daten laden"
|
||||||
|
`);
|
||||||
|
await page.locator("canvas").click({
|
||||||
|
position: {
|
||||||
|
x: 128,
|
||||||
|
y: 41,
|
||||||
|
},
|
||||||
|
});
|
||||||
|
await expect(page.locator("canvas")).toBeVisible();
|
||||||
|
await page.getByRole("button", { name: "Messkurve " }).click();
|
||||||
|
await page.getByRole("option", { name: "Meldungen" }).click();
|
||||||
|
await expect(page.getByText("SchleifenwiderstandMeldungen")).toBeVisible();
|
||||||
|
await expect(page.getByText("Von")).toBeVisible();
|
||||||
|
await expect(
|
||||||
|
page.locator("div").filter({ hasText: /^Von$/ }).getByRole("textbox")
|
||||||
|
).toBeVisible();
|
||||||
|
await expect(page.getByText("Bis")).toBeVisible();
|
||||||
|
await expect(
|
||||||
|
page.locator("div").filter({ hasText: /^Bis$/ }).getByRole("textbox")
|
||||||
|
).toBeVisible();
|
||||||
|
await expect(page.getByRole("button", { name: "Anzeigen" })).toBeVisible();
|
||||||
|
await expect(
|
||||||
|
page
|
||||||
|
.getByLabel("Schleifenwiderstand")
|
||||||
|
.locator("div")
|
||||||
|
.filter({
|
||||||
|
hasText:
|
||||||
|
"PrioZeitstempelQuelleMeldungStatus03.09.2025, 12:10:42CableLine1Modul",
|
||||||
|
})
|
||||||
|
.nth(4)
|
||||||
|
).toBeVisible();
|
||||||
|
await page.getByRole("cell", { name: "Prio" }).click();
|
||||||
|
await page.getByRole("cell", { name: "Zeitstempel" }).click();
|
||||||
|
await page.getByRole("cell", { name: "Quelle" }).click();
|
||||||
|
await page.getByRole("cell", { name: "Meldung" }).click();
|
||||||
|
await page.getByRole("cell", { name: "Status" }).click();
|
||||||
|
await page
|
||||||
|
.getByRole("dialog", { name: "Schleifenwiderstand" })
|
||||||
|
.getByLabel("Schließen")
|
||||||
|
.click();
|
||||||
|
});
|
||||||
@@ -0,0 +1,64 @@
|
|||||||
|
import { test, expect } from "@playwright/test";
|
||||||
|
|
||||||
|
test.use({
|
||||||
|
viewport: {
|
||||||
|
height: 800,
|
||||||
|
width: 1280,
|
||||||
|
},
|
||||||
|
});
|
||||||
|
|
||||||
|
test("test", async ({ page }) => {
|
||||||
|
await page.goto("http://localhost:3000/kabelueberwachung");
|
||||||
|
await page
|
||||||
|
.locator(".bg-littwin-blue.text-white.cursor-pointer")
|
||||||
|
.first()
|
||||||
|
.click();
|
||||||
|
await expect(page.getByLabel("TDR Messung").getByRole("banner"))
|
||||||
|
.toMatchAriaSnapshot(`
|
||||||
|
- banner:
|
||||||
|
- heading "TDR-Messung" [level=3]
|
||||||
|
- button "Vollbild"
|
||||||
|
- button "Schließen"
|
||||||
|
- button "Messkurve "
|
||||||
|
`);
|
||||||
|
await expect(page.getByText("KÜ1")).toBeVisible();
|
||||||
|
await expect(
|
||||||
|
page.getByRole("button", { name: "TDR-Kurve als Referenz" })
|
||||||
|
).toBeVisible();
|
||||||
|
await expect(
|
||||||
|
page.getByRole("button", { name: "TDR-Messung starten" })
|
||||||
|
).toBeVisible();
|
||||||
|
await expect(
|
||||||
|
page.getByRole("button", { name: "27.03.2025, 23:42:41 –" })
|
||||||
|
).toBeVisible();
|
||||||
|
await expect(page.locator("canvas")).toBeVisible();
|
||||||
|
await page.getByRole("button", { name: "Messkurve " }).click();
|
||||||
|
await page.getByRole("option", { name: "Meldungen" }).click();
|
||||||
|
await expect(page.getByText("Von")).toBeVisible();
|
||||||
|
await expect(
|
||||||
|
page.locator("div").filter({ hasText: /^Von$/ }).getByRole("textbox")
|
||||||
|
).toBeVisible();
|
||||||
|
await expect(page.getByText("Bis")).toBeVisible();
|
||||||
|
await expect(
|
||||||
|
page.locator("div").filter({ hasText: /^Bis$/ }).getByRole("textbox")
|
||||||
|
).toBeVisible();
|
||||||
|
await expect(page.getByRole("button", { name: "Anzeigen" })).toBeVisible();
|
||||||
|
await page.getByRole("cell", { name: "Prio" }).click();
|
||||||
|
await page.getByRole("cell", { name: "Zeitstempel" }).click();
|
||||||
|
await page.getByRole("cell", { name: "Quelle" }).click();
|
||||||
|
await page.getByRole("cell", { name: "Meldung" }).click();
|
||||||
|
await page.getByRole("cell", { name: "Status" }).click();
|
||||||
|
await page
|
||||||
|
.getByLabel("TDR Messung")
|
||||||
|
.locator("div")
|
||||||
|
.filter({
|
||||||
|
hasText:
|
||||||
|
"PrioZeitstempelQuelleMeldungStatus03.09.2025, 12:10:42CableLine1Modul",
|
||||||
|
})
|
||||||
|
.nth(3)
|
||||||
|
.click();
|
||||||
|
await page
|
||||||
|
.getByRole("dialog", { name: "TDR Messung" })
|
||||||
|
.getByLabel("Schließen")
|
||||||
|
.click();
|
||||||
|
});
|
||||||
@@ -0,0 +1,91 @@
|
|||||||
|
import { test, expect } from "@playwright/test";
|
||||||
|
|
||||||
|
test.use({
|
||||||
|
viewport: {
|
||||||
|
height: 800,
|
||||||
|
width: 1280,
|
||||||
|
},
|
||||||
|
});
|
||||||
|
|
||||||
|
test("test", async ({ page }) => {
|
||||||
|
await page.goto("kabelueberwachung");
|
||||||
|
await page
|
||||||
|
.locator("div")
|
||||||
|
.filter({ hasText: /^1KÜ705-FO⚙$/ })
|
||||||
|
.getByRole("button")
|
||||||
|
.click();
|
||||||
|
await expect(
|
||||||
|
page.locator("div").filter({ hasText: /^Einstellungen KÜ 1$/ })
|
||||||
|
).toBeVisible();
|
||||||
|
await expect(page.getByText("AllgemeinTDRKVzKnotenpunkte")).toBeVisible();
|
||||||
|
await expect(page.getByText("Kabelbezeichnung:")).toBeVisible();
|
||||||
|
await expect(
|
||||||
|
page.getByRole("textbox", { name: "Feld kann nicht bearbeitet" })
|
||||||
|
).toBeVisible();
|
||||||
|
await expect(page.getByText("Kabelname:")).toBeVisible();
|
||||||
|
await expect(
|
||||||
|
page
|
||||||
|
.locator("div")
|
||||||
|
.filter({ hasText: /^Kabelname:$/ })
|
||||||
|
.getByRole("textbox")
|
||||||
|
).toBeVisible();
|
||||||
|
await expect(page.getByText("Speicherintervall:")).toBeVisible();
|
||||||
|
await expect(
|
||||||
|
page
|
||||||
|
.locator("div")
|
||||||
|
.filter({ hasText: /^Minuten$/ })
|
||||||
|
.getByRole("spinbutton")
|
||||||
|
).toBeVisible();
|
||||||
|
await expect(
|
||||||
|
page.getByRole("heading", { name: "Isolationsmessung" })
|
||||||
|
).toBeVisible();
|
||||||
|
await expect(
|
||||||
|
page
|
||||||
|
.locator("div")
|
||||||
|
.filter({ hasText: /^Grenzwert:MOhm$/ })
|
||||||
|
.locator("label")
|
||||||
|
).toBeVisible();
|
||||||
|
await expect(
|
||||||
|
page
|
||||||
|
.locator("div")
|
||||||
|
.filter({ hasText: /^MOhm$/ })
|
||||||
|
.getByRole("spinbutton")
|
||||||
|
).toBeVisible();
|
||||||
|
await expect(page.getByText("Verzögerung:")).toBeVisible();
|
||||||
|
await expect(
|
||||||
|
page
|
||||||
|
.locator("div")
|
||||||
|
.filter({ hasText: /^Sekunden$/ })
|
||||||
|
.getByRole("spinbutton")
|
||||||
|
).toBeVisible();
|
||||||
|
await expect(
|
||||||
|
page.getByRole("heading", { name: "Schleifenmessung" })
|
||||||
|
).toBeVisible();
|
||||||
|
await expect(
|
||||||
|
page
|
||||||
|
.locator("div")
|
||||||
|
.filter({ hasText: /^Grenzwert:kOhm$/ })
|
||||||
|
.locator("label")
|
||||||
|
).toBeVisible();
|
||||||
|
await expect(
|
||||||
|
page
|
||||||
|
.locator("div")
|
||||||
|
.filter({ hasText: /^kOhm$/ })
|
||||||
|
.getByRole("spinbutton")
|
||||||
|
).toBeVisible();
|
||||||
|
await expect(page.getByText("Schleifenmessintervall:")).toBeVisible();
|
||||||
|
await expect(
|
||||||
|
page
|
||||||
|
.locator("div")
|
||||||
|
.filter({ hasText: /^Stunden$/ })
|
||||||
|
.getByRole("spinbutton")
|
||||||
|
).toBeVisible();
|
||||||
|
await expect(
|
||||||
|
page.getByRole("button", { name: "Display einschalten" })
|
||||||
|
).toBeVisible();
|
||||||
|
await expect(page.getByRole("button", { name: "Speichern" })).toBeVisible();
|
||||||
|
await expect(
|
||||||
|
page.getByRole("button", { name: "Modal schließen" })
|
||||||
|
).toBeVisible();
|
||||||
|
await page.getByRole("button", { name: "Modal schließen" }).click();
|
||||||
|
});
|
||||||
@@ -0,0 +1,26 @@
|
|||||||
|
import { test, expect } from "@playwright/test";
|
||||||
|
|
||||||
|
test.use({
|
||||||
|
viewport: {
|
||||||
|
height: 800,
|
||||||
|
width: 1280,
|
||||||
|
},
|
||||||
|
});
|
||||||
|
|
||||||
|
test("test", async ({ page }) => {
|
||||||
|
await page.goto("http://localhost:3000/kabelueberwachung");
|
||||||
|
await page
|
||||||
|
.locator("div")
|
||||||
|
.filter({ hasText: /^1KÜ705-FO⚙$/ })
|
||||||
|
.getByRole("button")
|
||||||
|
.click();
|
||||||
|
await page.getByRole("button", { name: "KVz", exact: true }).click();
|
||||||
|
await page
|
||||||
|
.locator("div")
|
||||||
|
.filter({
|
||||||
|
hasText: /^Nur Admin-Benutzer können diese Einstellungen ändern\.$/,
|
||||||
|
})
|
||||||
|
.nth(1)
|
||||||
|
.click();
|
||||||
|
await page.getByRole("button", { name: "Modal schließen" }).click();
|
||||||
|
});
|
||||||
@@ -0,0 +1,35 @@
|
|||||||
|
import { test, expect } from "@playwright/test";
|
||||||
|
|
||||||
|
test.use({
|
||||||
|
viewport: {
|
||||||
|
height: 800,
|
||||||
|
width: 1280,
|
||||||
|
},
|
||||||
|
});
|
||||||
|
|
||||||
|
test("test", async ({ page }) => {
|
||||||
|
await page.goto("http://localhost:3000/kabelueberwachung");
|
||||||
|
await page
|
||||||
|
.locator("div")
|
||||||
|
.filter({ hasText: /^1KÜ705-FO⚙$/ })
|
||||||
|
.getByRole("button")
|
||||||
|
.click();
|
||||||
|
await page.getByRole("button", { name: "Knotenpunkte" }).click();
|
||||||
|
await page.getByText("Knoten 1:").click();
|
||||||
|
await page
|
||||||
|
.locator("div")
|
||||||
|
.filter({ hasText: /^Knoten 1:↳ Verbindung:m$/ })
|
||||||
|
.locator("span")
|
||||||
|
.first()
|
||||||
|
.click();
|
||||||
|
await page.getByText("Knoten 2:").click();
|
||||||
|
await page
|
||||||
|
.locator("div")
|
||||||
|
.filter({ hasText: /^Knoten 2:↳ Verbindung:m$/ })
|
||||||
|
.locator("span")
|
||||||
|
.first()
|
||||||
|
.click();
|
||||||
|
await page.getByText("Knoten 3:").click();
|
||||||
|
await page.getByText("Knoten 10:").click();
|
||||||
|
await page.getByRole("button", { name: "Modal schließen" }).click();
|
||||||
|
});
|
||||||
@@ -0,0 +1,24 @@
|
|||||||
|
import { test, expect } from "@playwright/test";
|
||||||
|
|
||||||
|
test.use({
|
||||||
|
viewport: {
|
||||||
|
height: 800,
|
||||||
|
width: 1280,
|
||||||
|
},
|
||||||
|
});
|
||||||
|
|
||||||
|
test("test", async ({ page }) => {
|
||||||
|
await page.goto("http://localhost:3000/kabelueberwachung");
|
||||||
|
await page
|
||||||
|
.locator("div")
|
||||||
|
.filter({ hasText: /^1KÜ705-FO⚙$/ })
|
||||||
|
.getByRole("button")
|
||||||
|
.click();
|
||||||
|
await page
|
||||||
|
.getByLabel("Einstellungen KÜ")
|
||||||
|
.getByRole("button", { name: "TDR" })
|
||||||
|
.click();
|
||||||
|
await expect(
|
||||||
|
page.getByText("TDR Dämpfung:dBGeschwindigkeit:m/µsTrigger:Speichern")
|
||||||
|
).toBeVisible();
|
||||||
|
});
|
||||||
84
playwright/tests/pages/meldungen/meldungen.test.ts
Normal file
84
playwright/tests/pages/meldungen/meldungen.test.ts
Normal file
@@ -0,0 +1,84 @@
|
|||||||
|
import { test, expect } from "@playwright/test";
|
||||||
|
import { headerTest } from "@/playwright/tests/components/header/headerTest";
|
||||||
|
import { navTest } from "@/playwright/tests/components/navigation/navTest";
|
||||||
|
import { footerTest } from "@/playwright/tests/components/footer/footerTest";
|
||||||
|
|
||||||
|
test.use({
|
||||||
|
viewport: {
|
||||||
|
height: 800,
|
||||||
|
width: 1280,
|
||||||
|
},
|
||||||
|
});
|
||||||
|
|
||||||
|
test("Berichte/Meldungen", async ({ page }) => {
|
||||||
|
await page.goto("/meldungen");
|
||||||
|
// Gemeinsame Layout-Checks
|
||||||
|
await headerTest(page);
|
||||||
|
await navTest(page);
|
||||||
|
await footerTest(page);
|
||||||
|
await expect(page.getByRole("heading", { name: "Berichte" })).toBeVisible();
|
||||||
|
await expect(page.getByText("Von")).toBeVisible();
|
||||||
|
await expect(
|
||||||
|
page.locator("div").filter({ hasText: /^Von$/ }).getByRole("textbox")
|
||||||
|
).toBeVisible();
|
||||||
|
await expect(page.getByText("Bis")).toBeVisible();
|
||||||
|
await expect(
|
||||||
|
page.locator("div").filter({ hasText: /^Bis$/ }).getByRole("textbox")
|
||||||
|
).toBeVisible();
|
||||||
|
await expect(page.getByRole("button", { name: "Anzeigen" })).toBeVisible();
|
||||||
|
await expect(
|
||||||
|
page.getByRole("button", { name: "Alle Quellen" })
|
||||||
|
).toBeVisible();
|
||||||
|
await page
|
||||||
|
.locator("div")
|
||||||
|
.filter({ hasText: /^Von$/ })
|
||||||
|
.getByRole("textbox")
|
||||||
|
.click();
|
||||||
|
await expect(page.getByLabel("Sunday", { exact: true })).toBeVisible();
|
||||||
|
await expect(page.getByLabel("Monday", { exact: true })).toBeVisible();
|
||||||
|
await expect(page.getByLabel("Tuesday", { exact: true })).toBeVisible();
|
||||||
|
await expect(page.getByLabel("Wednesday", { exact: true })).toBeVisible();
|
||||||
|
await expect(page.getByLabel("Thursday", { exact: true })).toBeVisible();
|
||||||
|
await expect(page.getByLabel("Friday", { exact: true })).toBeVisible();
|
||||||
|
await expect(page.getByLabel("Saturday", { exact: true })).toBeVisible();
|
||||||
|
await page
|
||||||
|
.locator("div")
|
||||||
|
.filter({ hasText: /^Von$/ })
|
||||||
|
.getByRole("textbox")
|
||||||
|
.click();
|
||||||
|
await page
|
||||||
|
.locator("div")
|
||||||
|
.filter({ hasText: /^Von$/ })
|
||||||
|
.getByRole("textbox")
|
||||||
|
.click();
|
||||||
|
await page.getByRole("heading", { name: "Berichte" }).click();
|
||||||
|
await page
|
||||||
|
.locator("div")
|
||||||
|
.filter({ hasText: /^Bis$/ })
|
||||||
|
.getByRole("textbox")
|
||||||
|
.click();
|
||||||
|
await expect(page.getByLabel("Sunday", { exact: true })).toBeVisible();
|
||||||
|
await page.getByLabel("Monday", { exact: true }).click();
|
||||||
|
await page.getByLabel("Tuesday", { exact: true }).click();
|
||||||
|
await page.getByLabel("Wednesday", { exact: true }).click();
|
||||||
|
await page.getByLabel("Thursday", { exact: true }).click();
|
||||||
|
await page.getByLabel("Friday", { exact: true }).click();
|
||||||
|
await page.getByLabel("Saturday", { exact: true }).click();
|
||||||
|
await page.getByRole("heading", { name: "Berichte" }).click();
|
||||||
|
await page.getByRole("button", { name: "Alle Quellen" }).click();
|
||||||
|
await page.getByRole("button", { name: "Alle Quellen" }).click();
|
||||||
|
await page.getByRole("cell", { name: "Prio" }).click();
|
||||||
|
await page.getByRole("cell", { name: "Zeitstempel" }).click();
|
||||||
|
await page.getByRole("cell", { name: "Quelle" }).click();
|
||||||
|
await page.getByRole("cell", { name: "Meldung" }).click();
|
||||||
|
await page.getByRole("cell", { name: "Status" }).click();
|
||||||
|
await expect(
|
||||||
|
page.getByRole("row", { name: "05.09.2025, 11:52:44" }).locator("div")
|
||||||
|
).toBeVisible();
|
||||||
|
await expect(
|
||||||
|
page
|
||||||
|
.getByRole("row", { name: "05.09.2025, 11:51:14" })
|
||||||
|
.getByRole("cell")
|
||||||
|
.first()
|
||||||
|
).toBeVisible();
|
||||||
|
});
|
||||||
@@ -1,124 +0,0 @@
|
|||||||
import type { Page } from "@playwright/test";
|
|
||||||
import { highlightAndExpectVisible } from "@playwright/utils/highlight";
|
|
||||||
import { navTest } from "@/playwright/tests/components/navigation/navTest";
|
|
||||||
import { headerTest } from "@/playwright/tests/components/header/headerTest";
|
|
||||||
import { footerTest } from "@/playwright/tests/components/footer/footerTest";
|
|
||||||
|
|
||||||
export async function runMeldungenTest(page: Page) {
|
|
||||||
await page.goto("/meldungen");
|
|
||||||
//----------------------
|
|
||||||
await headerTest(page);
|
|
||||||
await navTest(page);
|
|
||||||
await footerTest(page);
|
|
||||||
await page.waitForTimeout(400);
|
|
||||||
//----------------------
|
|
||||||
|
|
||||||
// Berichte Heading
|
|
||||||
const berichteHeading = page.getByRole("heading", { name: "Berichte" });
|
|
||||||
await highlightAndExpectVisible(page, berichteHeading);
|
|
||||||
await berichteHeading.click();
|
|
||||||
await page.waitForTimeout(100);
|
|
||||||
|
|
||||||
// Von
|
|
||||||
const vonText = page.getByText("Von");
|
|
||||||
await highlightAndExpectVisible(page, vonText);
|
|
||||||
await vonText.click();
|
|
||||||
await page.waitForTimeout(50);
|
|
||||||
|
|
||||||
const vonTextbox = page
|
|
||||||
.locator("div")
|
|
||||||
.filter({ hasText: /^Von$/ })
|
|
||||||
.getByRole("textbox");
|
|
||||||
await highlightAndExpectVisible(page, vonTextbox);
|
|
||||||
await vonTextbox.click();
|
|
||||||
await page.waitForTimeout(50);
|
|
||||||
|
|
||||||
/* const julyDate = page
|
|
||||||
.getByLabel("Choose Date")
|
|
||||||
.locator("div")
|
|
||||||
.filter({ hasText: "July" })
|
|
||||||
.first();
|
|
||||||
await highlightAndExpectVisible(page, julyDate);
|
|
||||||
await expect(julyDate).toBeVisible(); */
|
|
||||||
await page.waitForTimeout(50);
|
|
||||||
|
|
||||||
await vonTextbox.click();
|
|
||||||
await page.waitForTimeout(50);
|
|
||||||
|
|
||||||
const bisTextbox = page
|
|
||||||
.locator("div")
|
|
||||||
.filter({ hasText: /^Bis$/ })
|
|
||||||
.getByRole("textbox");
|
|
||||||
await highlightAndExpectVisible(page, bisTextbox);
|
|
||||||
await bisTextbox.click();
|
|
||||||
await page.waitForTimeout(50);
|
|
||||||
|
|
||||||
/* const augustDate = page
|
|
||||||
.getByLabel("Choose Date")
|
|
||||||
.locator("div")
|
|
||||||
.filter({ hasText: "August" })
|
|
||||||
.first();
|
|
||||||
await highlightAndExpectVisible(page, augustDate);
|
|
||||||
await expect(augustDate).toBeVisible();
|
|
||||||
await page.waitForTimeout(50); */
|
|
||||||
|
|
||||||
await bisTextbox.click();
|
|
||||||
await page.waitForTimeout(50);
|
|
||||||
|
|
||||||
await highlightAndExpectVisible(page, berichteHeading);
|
|
||||||
await berichteHeading.click();
|
|
||||||
await page.waitForTimeout(50);
|
|
||||||
|
|
||||||
// Buttons sichtbar
|
|
||||||
/* const anzeigenBtn = page.getByRole("button", { name: "Anzeigen" });
|
|
||||||
await highlightAndExpectVisible(page, anzeigenBtn);
|
|
||||||
await expect(anzeigenBtn).toBeVisible();
|
|
||||||
await page.waitForTimeout(50); */
|
|
||||||
|
|
||||||
const alleQuellenBtn = page.getByRole("button", { name: "Alle Quellen" });
|
|
||||||
await highlightAndExpectVisible(page, alleQuellenBtn);
|
|
||||||
// await expect(alleQuellenBtn).toBeVisible();
|
|
||||||
await alleQuellenBtn.click();
|
|
||||||
await page.waitForTimeout(50);
|
|
||||||
await alleQuellenBtn.click();
|
|
||||||
await page.waitForTimeout(50);
|
|
||||||
|
|
||||||
// Tabellenzellen
|
|
||||||
const tableCells = [
|
|
||||||
page.getByRole("cell", { name: "Prio" }),
|
|
||||||
page.getByRole("cell", { name: "Zeitstempel" }),
|
|
||||||
page.getByRole("cell", { name: "Quelle" }),
|
|
||||||
page.getByRole("cell", { name: "Meldung" }),
|
|
||||||
page.getByRole("cell", { name: "Status" }),
|
|
||||||
];
|
|
||||||
for (const cell of tableCells) {
|
|
||||||
await highlightAndExpectVisible(page, cell);
|
|
||||||
await cell.click();
|
|
||||||
await page.waitForTimeout(30);
|
|
||||||
}
|
|
||||||
|
|
||||||
// Interact with the first few data rows generically instead of hardcoded timestamps
|
|
||||||
const table = page.locator("table");
|
|
||||||
await table.first().waitFor({ state: "visible", timeout: 15000 });
|
|
||||||
const rows = table.locator("tbody tr");
|
|
||||||
const rowCount = await rows.count();
|
|
||||||
const maxRows = Math.min(5, rowCount);
|
|
||||||
for (let i = 0; i < maxRows; i++) {
|
|
||||||
const row = rows.nth(i);
|
|
||||||
const firstCell = row.locator("td").first();
|
|
||||||
await highlightAndExpectVisible(page, firstCell);
|
|
||||||
await firstCell.click();
|
|
||||||
// click a couple of other cells if present
|
|
||||||
const secondCell = row.locator("td").nth(1);
|
|
||||||
if (await secondCell.count()) {
|
|
||||||
await highlightAndExpectVisible(page, secondCell);
|
|
||||||
await secondCell.click();
|
|
||||||
}
|
|
||||||
const thirdCell = row.locator("td").nth(2);
|
|
||||||
if (await thirdCell.count()) {
|
|
||||||
await highlightAndExpectVisible(page, thirdCell);
|
|
||||||
await thirdCell.click();
|
|
||||||
}
|
|
||||||
await page.waitForTimeout(20);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
141
playwright/tests/pages/settingsPage/settingsPage.test.ts
Normal file
141
playwright/tests/pages/settingsPage/settingsPage.test.ts
Normal file
@@ -0,0 +1,141 @@
|
|||||||
|
import { test, expect } from "@playwright/test";
|
||||||
|
import { headerTest } from "@/playwright/tests/components/header/headerTest";
|
||||||
|
import { navTest } from "@/playwright/tests/components/navigation/navTest";
|
||||||
|
import { footerTest } from "@/playwright/tests/components/footer/footerTest";
|
||||||
|
|
||||||
|
test.use({
|
||||||
|
viewport: {
|
||||||
|
height: 800,
|
||||||
|
width: 1280,
|
||||||
|
},
|
||||||
|
});
|
||||||
|
|
||||||
|
test("Einstellungen", async ({ page }) => {
|
||||||
|
await page.goto("/einstellungen");
|
||||||
|
// Gemeinsame Layout-Checks
|
||||||
|
await headerTest(page);
|
||||||
|
await navTest(page);
|
||||||
|
await footerTest(page);
|
||||||
|
await page.getByRole("button", { name: "Allgemeine Einstellungen" }).click();
|
||||||
|
await expect(
|
||||||
|
page.getByRole("heading", { name: "Allgemeine Einstellungen" })
|
||||||
|
).toBeVisible();
|
||||||
|
await expect(page.getByText("Name:")).toBeVisible();
|
||||||
|
await expect(
|
||||||
|
page
|
||||||
|
.locator("div")
|
||||||
|
.filter({ hasText: /^Name:$/ })
|
||||||
|
.getByRole("textbox")
|
||||||
|
).toBeVisible();
|
||||||
|
await expect(page.getByText("MAC Adresse 1:")).toBeVisible();
|
||||||
|
await expect(
|
||||||
|
page
|
||||||
|
.locator("div")
|
||||||
|
.filter({ hasText: /^MAC Adresse 1:$/ })
|
||||||
|
.getByRole("textbox")
|
||||||
|
).toHaveValue("0 48 86 81 46 143");
|
||||||
|
await expect(page.getByText("Systemzeit:")).toBeVisible();
|
||||||
|
await expect(
|
||||||
|
page
|
||||||
|
.locator("div")
|
||||||
|
.filter({ hasText: /^Systemzeit übernehmen$/ })
|
||||||
|
.getByRole("textbox")
|
||||||
|
).toHaveValue("23.10.24 15:10:28");
|
||||||
|
await expect(
|
||||||
|
page.getByRole("button", { name: "Systemzeit übernehmen" })
|
||||||
|
).toBeVisible();
|
||||||
|
await expect(page.getByText("IP:")).toBeVisible();
|
||||||
|
await expect(
|
||||||
|
page.locator("div").filter({ hasText: /^IP:$/ }).getByRole("textbox")
|
||||||
|
).toBeVisible();
|
||||||
|
await expect(page.getByText("Subnet:")).toBeVisible();
|
||||||
|
await expect(
|
||||||
|
page
|
||||||
|
.locator("div")
|
||||||
|
.filter({ hasText: /^Subnet:$/ })
|
||||||
|
.getByRole("textbox")
|
||||||
|
).toBeVisible();
|
||||||
|
await expect(page.getByText("Gateway:")).toBeVisible();
|
||||||
|
await expect(
|
||||||
|
page
|
||||||
|
.locator("div")
|
||||||
|
.filter({ hasText: /^Gateway:$/ })
|
||||||
|
.getByRole("textbox")
|
||||||
|
).toBeVisible();
|
||||||
|
await expect(
|
||||||
|
page.getByRole("button", { name: "Neustart CPL" })
|
||||||
|
).toBeVisible();
|
||||||
|
await expect(page.getByRole("button", { name: "Speichern" })).toBeVisible();
|
||||||
|
await page.getByRole("button", { name: "OPCUA" }).click();
|
||||||
|
await expect(
|
||||||
|
page.getByRole("img", { name: "OPCUA Logo" }).first()
|
||||||
|
).toBeVisible();
|
||||||
|
await expect(
|
||||||
|
page.getByRole("img", { name: "OPCUA Logo" }).nth(1)
|
||||||
|
).toBeVisible();
|
||||||
|
await expect(page.getByText("Server Status:")).toBeVisible();
|
||||||
|
await expect(page.getByRole("button", { name: "Aktiviert" })).toBeVisible();
|
||||||
|
await expect(page.getByText("1")).toBeVisible();
|
||||||
|
await expect(page.getByText("Nodeset Name")).toBeVisible();
|
||||||
|
await expect(page.getByText("OPCUA Zustand")).toBeVisible();
|
||||||
|
await expect(page.getByRole("textbox")).toHaveValue(
|
||||||
|
"CPL V4 OPC UA Application Deutsche Bahn"
|
||||||
|
);
|
||||||
|
await expect(page.getByText("Aktuelle OPC-Clients")).toBeVisible();
|
||||||
|
await expect(page.getByText("0", { exact: true })).toBeVisible();
|
||||||
|
await page.getByRole("button", { name: "Datenbank" }).click();
|
||||||
|
await expect(
|
||||||
|
page.getByRole("heading", { name: "Datenbank Einstellungen" })
|
||||||
|
).toBeVisible();
|
||||||
|
await expect(
|
||||||
|
page.getByRole("button", { name: "Meldungen löschen" })
|
||||||
|
).toBeVisible();
|
||||||
|
await expect(
|
||||||
|
page.getByRole("button", { name: "Messwerte Logger löschen" })
|
||||||
|
).toBeVisible();
|
||||||
|
await page.getByRole("button", { name: "NTP" }).click();
|
||||||
|
await expect(
|
||||||
|
page.getByRole("heading", { name: "NTP Einstellungen" })
|
||||||
|
).toBeVisible();
|
||||||
|
await expect(page.getByText("NTP Server 1")).toBeVisible();
|
||||||
|
await expect(
|
||||||
|
page
|
||||||
|
.locator("div")
|
||||||
|
.filter({ hasText: /^NTP Server 1$/ })
|
||||||
|
.getByRole("textbox")
|
||||||
|
).toBeVisible();
|
||||||
|
await expect(page.getByText("NTP Server 2")).toBeVisible();
|
||||||
|
await expect(
|
||||||
|
page
|
||||||
|
.locator("div")
|
||||||
|
.filter({ hasText: /^NTP Server 2$/ })
|
||||||
|
.getByRole("textbox")
|
||||||
|
).toBeVisible();
|
||||||
|
await expect(page.getByText("NTP Server 3")).toBeVisible();
|
||||||
|
await expect(
|
||||||
|
page
|
||||||
|
.locator("div")
|
||||||
|
.filter({ hasText: /^NTP Server 3$/ })
|
||||||
|
.getByRole("textbox")
|
||||||
|
).toBeVisible();
|
||||||
|
await expect(page.getByText("Zeitzone")).toBeVisible();
|
||||||
|
await expect(
|
||||||
|
page
|
||||||
|
.locator("div")
|
||||||
|
.filter({ hasText: /^Zeitzone$/ })
|
||||||
|
.getByRole("textbox")
|
||||||
|
).toBeVisible();
|
||||||
|
await expect(page.getByText("NTP aktiv:")).toBeVisible();
|
||||||
|
await expect(page.getByRole("button", { name: "Speichern" })).toBeVisible();
|
||||||
|
await page.getByRole("button", { name: "Benutzerverwaltung" }).click();
|
||||||
|
await expect(
|
||||||
|
page.getByRole("heading", { name: "Login Admin-Bereich" })
|
||||||
|
).toBeVisible();
|
||||||
|
await expect(
|
||||||
|
page.getByRole("textbox", { name: "Benutzername" })
|
||||||
|
).toBeVisible();
|
||||||
|
await expect(page.getByRole("textbox", { name: "Passwort" })).toBeVisible();
|
||||||
|
await expect(
|
||||||
|
page.getByRole("button", { name: "Admin anmelden" })
|
||||||
|
).toBeVisible();
|
||||||
|
});
|
||||||
@@ -1,175 +0,0 @@
|
|||||||
import type { Page } from "@playwright/test";
|
|
||||||
import { highlightAndExpectVisible } from "@playwright/utils/highlight";
|
|
||||||
import { navTest } from "@/playwright/tests/components/navigation/navTest";
|
|
||||||
import { headerTest } from "@/playwright/tests/components/header/headerTest";
|
|
||||||
import { footerTest } from "@/playwright/tests/components/footer/footerTest";
|
|
||||||
|
|
||||||
export async function runSettingsPageTest(page: Page) {
|
|
||||||
await page.goto("/einstellungen");
|
|
||||||
//----------------------
|
|
||||||
await headerTest(page);
|
|
||||||
await navTest(page);
|
|
||||||
await footerTest(page);
|
|
||||||
await page.waitForTimeout(400);
|
|
||||||
//----------------------
|
|
||||||
//await page.getByRole("button").filter({ hasText: /^$/ }).click();
|
|
||||||
await page.getByRole("button", { name: "Allgemeine Einstellungen" }).click();
|
|
||||||
await highlightAndExpectVisible(
|
|
||||||
page,
|
|
||||||
page.getByRole("button", { name: "Allgemeine Einstellungen" })
|
|
||||||
);
|
|
||||||
await highlightAndExpectVisible(
|
|
||||||
page,
|
|
||||||
page.getByRole("heading", { name: "Allgemeine Einstellungen" })
|
|
||||||
);
|
|
||||||
await highlightAndExpectVisible(page, page.getByText("Name:"));
|
|
||||||
await highlightAndExpectVisible(
|
|
||||||
page,
|
|
||||||
page
|
|
||||||
.locator("div")
|
|
||||||
.filter({ hasText: /^Name:$/ })
|
|
||||||
.getByRole("textbox")
|
|
||||||
);
|
|
||||||
await highlightAndExpectVisible(page, page.getByText("MAC Adresse 1:"));
|
|
||||||
await highlightAndExpectVisible(
|
|
||||||
page,
|
|
||||||
page.getByRole("button", { name: "Systemzeit übernehmen" })
|
|
||||||
);
|
|
||||||
await highlightAndExpectVisible(page, page.getByText("IP:"));
|
|
||||||
await highlightAndExpectVisible(
|
|
||||||
page,
|
|
||||||
page.locator("div").filter({ hasText: /^IP:$/ }).getByRole("textbox")
|
|
||||||
);
|
|
||||||
await highlightAndExpectVisible(page, page.getByText("Subnet:"));
|
|
||||||
await highlightAndExpectVisible(
|
|
||||||
page,
|
|
||||||
page
|
|
||||||
.locator("div")
|
|
||||||
.filter({ hasText: /^Subnet:$/ })
|
|
||||||
.getByRole("textbox")
|
|
||||||
);
|
|
||||||
await highlightAndExpectVisible(page, page.getByText("Gateway:"));
|
|
||||||
await highlightAndExpectVisible(
|
|
||||||
page,
|
|
||||||
page
|
|
||||||
.locator("div")
|
|
||||||
.filter({ hasText: /^Gateway:$/ })
|
|
||||||
.getByRole("textbox")
|
|
||||||
);
|
|
||||||
await highlightAndExpectVisible(
|
|
||||||
page,
|
|
||||||
page.getByRole("button", { name: "Neustart CPL" })
|
|
||||||
);
|
|
||||||
await highlightAndExpectVisible(
|
|
||||||
page,
|
|
||||||
page.getByRole("button", { name: "Speichern" })
|
|
||||||
);
|
|
||||||
await page.getByRole("button", { name: "OPCUA" }).click();
|
|
||||||
await highlightAndExpectVisible(
|
|
||||||
page,
|
|
||||||
page.getByRole("img", { name: "OPCUA Logo" }).first()
|
|
||||||
);
|
|
||||||
await highlightAndExpectVisible(
|
|
||||||
page,
|
|
||||||
page.getByRole("img", { name: "OPCUA Logo" }).nth(1)
|
|
||||||
);
|
|
||||||
await highlightAndExpectVisible(page, page.getByText("Server Status:"));
|
|
||||||
await highlightAndExpectVisible(
|
|
||||||
page,
|
|
||||||
page.getByRole("button", { name: "Aktiviert" })
|
|
||||||
);
|
|
||||||
await highlightAndExpectVisible(page, page.getByText("OPCUA Zustand"));
|
|
||||||
await highlightAndExpectVisible(page, page.getByText("1"));
|
|
||||||
await highlightAndExpectVisible(page, page.getByText("Nodeset Name"));
|
|
||||||
await highlightAndExpectVisible(page, page.getByText("Aktuelle OPC-Clients"));
|
|
||||||
await highlightAndExpectVisible(page, page.getByText("0", { exact: true }));
|
|
||||||
await page.getByRole("button", { name: "Datenbank" }).click();
|
|
||||||
await highlightAndExpectVisible(
|
|
||||||
page,
|
|
||||||
page.getByRole("heading", { name: "Datenbank Einstellungen" })
|
|
||||||
);
|
|
||||||
await highlightAndExpectVisible(
|
|
||||||
page,
|
|
||||||
page.getByRole("button", { name: "Meldungen löschen" })
|
|
||||||
);
|
|
||||||
await highlightAndExpectVisible(
|
|
||||||
page,
|
|
||||||
page.getByRole("button", { name: "Messwerte Logger löschen" })
|
|
||||||
);
|
|
||||||
await page.getByRole("button", { name: "NTP" }).click();
|
|
||||||
await highlightAndExpectVisible(
|
|
||||||
page,
|
|
||||||
page.getByRole("heading", { name: "NTP Einstellungen" })
|
|
||||||
);
|
|
||||||
await highlightAndExpectVisible(page, page.getByText("NTP Server 1"));
|
|
||||||
await highlightAndExpectVisible(
|
|
||||||
page,
|
|
||||||
page
|
|
||||||
.locator("div")
|
|
||||||
.filter({ hasText: /^NTP Server 1$/ })
|
|
||||||
.getByRole("textbox")
|
|
||||||
);
|
|
||||||
await highlightAndExpectVisible(page, page.getByText("NTP Server 3"));
|
|
||||||
await highlightAndExpectVisible(
|
|
||||||
page,
|
|
||||||
page
|
|
||||||
.locator("div")
|
|
||||||
.filter({ hasText: /^NTP Server 3$/ })
|
|
||||||
.getByRole("textbox")
|
|
||||||
);
|
|
||||||
await highlightAndExpectVisible(page, page.getByText("NTP Server 2"));
|
|
||||||
await highlightAndExpectVisible(
|
|
||||||
page,
|
|
||||||
page
|
|
||||||
.locator("div")
|
|
||||||
.filter({ hasText: /^NTP Server 2$/ })
|
|
||||||
.getByRole("textbox")
|
|
||||||
);
|
|
||||||
await highlightAndExpectVisible(page, page.getByText("Zeitzone"));
|
|
||||||
await highlightAndExpectVisible(
|
|
||||||
page,
|
|
||||||
page
|
|
||||||
.locator("div")
|
|
||||||
.filter({ hasText: /^Zeitzone$/ })
|
|
||||||
.getByRole("textbox")
|
|
||||||
);
|
|
||||||
await highlightAndExpectVisible(page, page.getByText("NTP aktiv:"));
|
|
||||||
await highlightAndExpectVisible(page, page.getByRole("checkbox"));
|
|
||||||
await highlightAndExpectVisible(
|
|
||||||
page,
|
|
||||||
page.getByRole("button", { name: "Speichern" })
|
|
||||||
);
|
|
||||||
await page.getByRole("button", { name: "Benutzerverwaltung" }).click();
|
|
||||||
await highlightAndExpectVisible(
|
|
||||||
page,
|
|
||||||
page.getByRole("heading", { name: "Login Admin-Bereich" })
|
|
||||||
);
|
|
||||||
await highlightAndExpectVisible(
|
|
||||||
page,
|
|
||||||
page.getByRole("textbox", { name: "Benutzername" })
|
|
||||||
);
|
|
||||||
await highlightAndExpectVisible(
|
|
||||||
page,
|
|
||||||
page.getByRole("textbox", { name: "Passwort" })
|
|
||||||
);
|
|
||||||
await highlightAndExpectVisible(
|
|
||||||
page,
|
|
||||||
page.getByRole("button", { name: "Admin anmelden" })
|
|
||||||
);
|
|
||||||
await page.getByRole("textbox", { name: "Benutzername" }).click();
|
|
||||||
await page.getByRole("textbox", { name: "Benutzername" }).fill("admin");
|
|
||||||
await page.getByRole("textbox", { name: "Passwort" }).click();
|
|
||||||
await page.getByRole("textbox", { name: "Passwort" }).fill("admin");
|
|
||||||
await page.getByRole("button", { name: "Admin anmelden" }).click();
|
|
||||||
await highlightAndExpectVisible(page, page.getByText("Login erfolgreich!"));
|
|
||||||
await highlightAndExpectVisible(
|
|
||||||
page,
|
|
||||||
page.getByRole("button", { name: "Abmelden" })
|
|
||||||
);
|
|
||||||
await highlightAndExpectVisible(
|
|
||||||
page,
|
|
||||||
page.getByRole("button", { name: "Admin anmelden" })
|
|
||||||
);
|
|
||||||
}
|
|
||||||
|
|
||||||
//---------------------------------------------------------------------
|
|
||||||
104
playwright/tests/pages/system/system.test.ts
Normal file
104
playwright/tests/pages/system/system.test.ts
Normal file
@@ -0,0 +1,104 @@
|
|||||||
|
import { test, expect } from "@playwright/test";
|
||||||
|
import { headerTest } from "@/playwright/tests/components/header/headerTest";
|
||||||
|
import { navTest } from "@/playwright/tests/components/navigation/navTest";
|
||||||
|
import { footerTest } from "@/playwright/tests/components/footer/footerTest";
|
||||||
|
|
||||||
|
test.use({
|
||||||
|
viewport: {
|
||||||
|
height: 800,
|
||||||
|
width: 1280,
|
||||||
|
},
|
||||||
|
});
|
||||||
|
|
||||||
|
test("System", async ({ page }) => {
|
||||||
|
await page.goto("/system");
|
||||||
|
// Gemeinsame Layout-Checks
|
||||||
|
await headerTest(page);
|
||||||
|
await navTest(page);
|
||||||
|
await footerTest(page);
|
||||||
|
await expect(
|
||||||
|
page.getByRole("heading", { name: "System Spannungen &" })
|
||||||
|
).toBeVisible();
|
||||||
|
await expect(page.getByRole("heading", { name: "+15V" })).toBeVisible();
|
||||||
|
await expect(page.getByText("15.06 VDetailansicht")).toBeVisible();
|
||||||
|
await expect(page.getByRole("heading", { name: "+5V" })).toBeVisible();
|
||||||
|
await expect(page.getByText("4.98 VDetailansicht")).toBeVisible();
|
||||||
|
await expect(page.getByRole("heading", { name: "-15V" })).toBeVisible();
|
||||||
|
await expect(page.getByText("-15.09 VDetailansicht")).toBeVisible();
|
||||||
|
await expect(page.getByRole("heading", { name: "-96V" })).toBeVisible();
|
||||||
|
await expect(page.getByText("-96.48 VDetailansicht")).toBeVisible();
|
||||||
|
await expect(page.getByRole("heading", { name: "ADC Temp" })).toBeVisible();
|
||||||
|
await expect(page.getByText("59.78 °CDetailansicht")).toBeVisible();
|
||||||
|
await expect(page.getByRole("heading", { name: "CPU Temp" })).toBeVisible();
|
||||||
|
await expect(page.getByText("56.92 °CDetailansicht")).toBeVisible();
|
||||||
|
await expect(page.getByRole("img").nth(2)).toBeVisible();
|
||||||
|
await expect(page.getByRole("img").nth(3)).toBeVisible();
|
||||||
|
await page
|
||||||
|
.getByRole("paragraph")
|
||||||
|
.filter({ hasText: "15.06 VDetailansicht" })
|
||||||
|
.getByRole("button")
|
||||||
|
.click();
|
||||||
|
await expect(page.getByRole("dialog")).toMatchAriaSnapshot(`
|
||||||
|
- 'heading "Detailansicht: +15V" [level=2]'
|
||||||
|
- button "Vollbild"
|
||||||
|
- button "Modal schließen"
|
||||||
|
`);
|
||||||
|
await expect(page.getByRole("dialog")).toMatchAriaSnapshot(`
|
||||||
|
- text: Von
|
||||||
|
- textbox: /\\d+\\.\\d+\\.\\d+/
|
||||||
|
- text: Bis
|
||||||
|
- textbox: /\\d+\\.\\d+\\.\\d+/
|
||||||
|
- text: "Zeitraum:"
|
||||||
|
- button "Alle Messwerte":
|
||||||
|
- img
|
||||||
|
- button "Daten laden"
|
||||||
|
`);
|
||||||
|
await expect(page.getByRole("dialog")).toMatchAriaSnapshot(`
|
||||||
|
- text: Von
|
||||||
|
- textbox: /\\d+\\.\\d+\\.\\d+/
|
||||||
|
- text: Bis
|
||||||
|
- textbox: /\\d+\\.\\d+\\.\\d+/
|
||||||
|
- text: "Zeitraum:"
|
||||||
|
- button "Alle Messwerte":
|
||||||
|
- img
|
||||||
|
- button "Daten laden"
|
||||||
|
- img
|
||||||
|
`);
|
||||||
|
await page.getByRole("button", { name: "Alle Messwerte" }).click();
|
||||||
|
await page.getByRole("option", { name: "Stündlich" }).click();
|
||||||
|
await expect(page.getByRole("button", { name: "Stündlich" })).toBeVisible();
|
||||||
|
await page.getByRole("button", { name: "Stündlich" }).click();
|
||||||
|
await page.getByRole("option", { name: "Täglich" }).click();
|
||||||
|
await expect(page.getByRole("button", { name: "Täglich" })).toBeVisible();
|
||||||
|
await page
|
||||||
|
.locator("div")
|
||||||
|
.filter({ hasText: /^Von$/ })
|
||||||
|
.getByRole("textbox")
|
||||||
|
.click();
|
||||||
|
await expect(page.getByLabel("Sunday", { exact: true })).toBeVisible();
|
||||||
|
await expect(page.getByLabel("Monday", { exact: true })).toBeVisible();
|
||||||
|
await expect(page.getByLabel("Tuesday", { exact: true })).toBeVisible();
|
||||||
|
await expect(page.getByLabel("Wednesday", { exact: true })).toBeVisible();
|
||||||
|
await expect(page.getByLabel("Thursday", { exact: true })).toBeVisible();
|
||||||
|
await expect(page.getByLabel("Friday", { exact: true })).toBeVisible();
|
||||||
|
await expect(page.getByLabel("Saturday", { exact: true })).toBeVisible();
|
||||||
|
await page
|
||||||
|
.locator("div")
|
||||||
|
.filter({ hasText: /^Bis$/ })
|
||||||
|
.getByRole("textbox")
|
||||||
|
.click();
|
||||||
|
await expect(page.getByLabel("Sunday", { exact: true })).toBeVisible();
|
||||||
|
await expect(page.getByLabel("Monday", { exact: true })).toBeVisible();
|
||||||
|
await expect(page.getByLabel("Tuesday", { exact: true })).toBeVisible();
|
||||||
|
await expect(page.getByLabel("Wednesday", { exact: true })).toBeVisible();
|
||||||
|
await expect(page.getByLabel("Thursday", { exact: true })).toBeVisible();
|
||||||
|
await expect(page.getByLabel("Friday", { exact: true })).toBeVisible();
|
||||||
|
await expect(page.getByLabel("Saturday", { exact: true })).toBeVisible();
|
||||||
|
await page
|
||||||
|
.locator("header")
|
||||||
|
.filter({ hasText: "Detailansicht: +15V" })
|
||||||
|
.click();
|
||||||
|
await page.getByRole("button", { name: "Vollbild" }).click();
|
||||||
|
await page.getByRole("button", { name: "Vollbild verlassen" }).click();
|
||||||
|
await page.getByRole("button", { name: "Modal schließen" }).click();
|
||||||
|
});
|
||||||
@@ -1,109 +0,0 @@
|
|||||||
import type { Page } from "@playwright/test";
|
|
||||||
import { highlightAndExpectVisible } from "@playwright/utils/highlight";
|
|
||||||
import { navTest } from "@/playwright/tests/components/navigation/navTest";
|
|
||||||
import { headerTest } from "@/playwright/tests/components/header/headerTest";
|
|
||||||
import { footerTest } from "@/playwright/tests/components/footer/footerTest";
|
|
||||||
|
|
||||||
export async function runSystemTest(page: Page) {
|
|
||||||
await page.goto("/system");
|
|
||||||
//----------------------
|
|
||||||
await headerTest(page);
|
|
||||||
await navTest(page);
|
|
||||||
await footerTest(page);
|
|
||||||
await page.waitForTimeout(400);
|
|
||||||
//----------------------
|
|
||||||
// System Spannungen &
|
|
||||||
const systemSpannung = page.getByRole("heading", {
|
|
||||||
name: "System Spannungen &",
|
|
||||||
});
|
|
||||||
await highlightAndExpectVisible(page, systemSpannung);
|
|
||||||
await systemSpannung.click();
|
|
||||||
await page.waitForTimeout(100);
|
|
||||||
|
|
||||||
// +15V
|
|
||||||
const plus15V = page.getByRole("heading", { name: "+15V" });
|
|
||||||
await highlightAndExpectVisible(page, plus15V);
|
|
||||||
await plus15V.click();
|
|
||||||
await page.waitForTimeout(100);
|
|
||||||
|
|
||||||
// 15.06 VDetailansicht
|
|
||||||
const v15Detail = page.getByText("15.06 VDetailansicht");
|
|
||||||
await highlightAndExpectVisible(page, v15Detail);
|
|
||||||
await v15Detail.click();
|
|
||||||
await page.waitForTimeout(100);
|
|
||||||
|
|
||||||
// +5V
|
|
||||||
const plus5V = page.getByRole("heading", { name: "+5V" });
|
|
||||||
await highlightAndExpectVisible(page, plus5V);
|
|
||||||
await plus5V.click();
|
|
||||||
await page.waitForTimeout(100);
|
|
||||||
|
|
||||||
// 4.98 VDetailansicht
|
|
||||||
const v5Detail = page.getByText("4.98 VDetailansicht");
|
|
||||||
await highlightAndExpectVisible(page, v5Detail);
|
|
||||||
await v5Detail.click();
|
|
||||||
await page.waitForTimeout(100);
|
|
||||||
|
|
||||||
// -15V
|
|
||||||
const minus15V = page.getByRole("heading", { name: "-15V" });
|
|
||||||
await highlightAndExpectVisible(page, minus15V);
|
|
||||||
await minus15V.click();
|
|
||||||
await page.waitForTimeout(100);
|
|
||||||
|
|
||||||
// -15.09 VDetailansicht
|
|
||||||
const vMinus15Detail = page.getByText("-15.09 VDetailansicht");
|
|
||||||
await highlightAndExpectVisible(page, vMinus15Detail);
|
|
||||||
await vMinus15Detail.click();
|
|
||||||
await page.waitForTimeout(100);
|
|
||||||
|
|
||||||
// -98V
|
|
||||||
const minus98V = page.getByRole("heading", { name: "-98V" });
|
|
||||||
await highlightAndExpectVisible(page, minus98V);
|
|
||||||
await minus98V.click();
|
|
||||||
await page.waitForTimeout(100);
|
|
||||||
|
|
||||||
// -96.48 VDetailansicht
|
|
||||||
const vMinus98Detail = page.getByText("-96.48 VDetailansicht");
|
|
||||||
await highlightAndExpectVisible(page, vMinus98Detail);
|
|
||||||
await vMinus98Detail.click();
|
|
||||||
await page.waitForTimeout(100);
|
|
||||||
|
|
||||||
// ADC Temp
|
|
||||||
const adcTemp = page.getByRole("heading", { name: "ADC Temp" });
|
|
||||||
await highlightAndExpectVisible(page, adcTemp);
|
|
||||||
await adcTemp.click();
|
|
||||||
await page.waitForTimeout(100);
|
|
||||||
|
|
||||||
// 59.78 °CDetailansicht
|
|
||||||
const adcTempDetail = page.getByText("59.78 °CDetailansicht");
|
|
||||||
await highlightAndExpectVisible(page, adcTempDetail);
|
|
||||||
await adcTempDetail.click();
|
|
||||||
await page.waitForTimeout(100);
|
|
||||||
|
|
||||||
// CPU Temp
|
|
||||||
const cpuTemp = page.getByRole("heading", { name: "CPU Temp" });
|
|
||||||
await highlightAndExpectVisible(page, cpuTemp);
|
|
||||||
await cpuTemp.click();
|
|
||||||
await page.waitForTimeout(100);
|
|
||||||
await highlightAndExpectVisible(page, cpuTemp);
|
|
||||||
await cpuTemp.click();
|
|
||||||
await page.waitForTimeout(100);
|
|
||||||
|
|
||||||
// 56.92 °CDetailansicht
|
|
||||||
const cpuTempDetail = page.getByText("56.92 °CDetailansicht");
|
|
||||||
await highlightAndExpectVisible(page, cpuTempDetail);
|
|
||||||
await cpuTempDetail.click();
|
|
||||||
await page.waitForTimeout(100);
|
|
||||||
|
|
||||||
// img nth(2)
|
|
||||||
const img2 = page.getByRole("img").nth(2);
|
|
||||||
await highlightAndExpectVisible(page, img2);
|
|
||||||
await img2.click({ position: { x: 72, y: 53 } });
|
|
||||||
await page.waitForTimeout(100);
|
|
||||||
|
|
||||||
// img nth(3)
|
|
||||||
const img3 = page.getByRole("img").nth(3);
|
|
||||||
await highlightAndExpectVisible(page, img3);
|
|
||||||
await img3.click({ position: { x: 272, y: 93 } });
|
|
||||||
await page.waitForTimeout(100);
|
|
||||||
}
|
|
||||||
BIN
public/images/Logo back.png
Normal file
BIN
public/images/Logo back.png
Normal file
Binary file not shown.
|
After Width: | Height: | Size: 6.4 KiB |
Binary file not shown.
|
Before Width: | Height: | Size: 6.4 KiB After Width: | Height: | Size: 522 KiB |
BIN
public/images/Logo2.png
Normal file
BIN
public/images/Logo2.png
Normal file
Binary file not shown.
|
After Width: | Height: | Size: 9.8 KiB |
BIN
public/images/Logo3.png
Normal file
BIN
public/images/Logo3.png
Normal file
Binary file not shown.
|
After Width: | Height: | Size: 1.8 MiB |
@@ -11,7 +11,7 @@ const initialState: VoltagesState = {
|
|||||||
"+5V": 0,
|
"+5V": 0,
|
||||||
"+15V": 0,
|
"+15V": 0,
|
||||||
"-15V": 0,
|
"-15V": 0,
|
||||||
"-98V": 0,
|
"-96V": 0,
|
||||||
"ADC Temp": 0,
|
"ADC Temp": 0,
|
||||||
"CPU Temp": 0,
|
"CPU Temp": 0,
|
||||||
},
|
},
|
||||||
|
|||||||
@@ -9,7 +9,7 @@ export const getSystemspannung98VminusThunk = createAsyncThunk(
|
|||||||
return { typ, data };
|
return { typ, data };
|
||||||
} catch (error) {
|
} catch (error) {
|
||||||
console.error("Fehler in getSystemspannung98VminusThunk:", error);
|
console.error("Fehler in getSystemspannung98VminusThunk:", error);
|
||||||
return thunkAPI.rejectWithValue("Fehler beim Laden der -98V-Daten");
|
return thunkAPI.rejectWithValue("Fehler beim Laden der -96V-Daten");
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
);
|
);
|
||||||
|
|||||||
@@ -807,7 +807,7 @@ const server = http.createServer(async (req, res) => {
|
|||||||
// Service commands: history data via DIA0/DIA1/DIA2 for Analog Inputs and System Spannungen/Temperaturen
|
// Service commands: history data via DIA0/DIA1/DIA2 for Analog Inputs and System Spannungen/Temperaturen
|
||||||
// Examples:
|
// Examples:
|
||||||
// - Analog: seite.ACP&DIA1=YYYY;MM;DD;YYYY;MM;DD;1xx;1 where 1xx is 100 + (eingang-1)
|
// - Analog: seite.ACP&DIA1=YYYY;MM;DD;YYYY;MM;DD;1xx;1 where 1xx is 100 + (eingang-1)
|
||||||
// - System: seite.ACP&DIA1=YYYY;MM;DD;YYYY;MM;DD;108;1 (+15V), 110 (+5V), 114 (-15V), 115 (-98V), 116 (ADC Temp), 117 (CPU Temp)
|
// - System: seite.ACP&DIA1=YYYY;MM;DD;YYYY;MM;DD;108;1 (+15V), 110 (+5V), 114 (-15V), 115 (-96V), 116 (ADC Temp), 117 (CPU Temp)
|
||||||
if (/^seite\.ACP/i.test(q) && /DIA[0-2]=/i.test(q)) {
|
if (/^seite\.ACP/i.test(q) && /DIA[0-2]=/i.test(q)) {
|
||||||
try {
|
try {
|
||||||
const m = q.match(/(DIA[0-2])=([^&]+)/i);
|
const m = q.match(/(DIA[0-2])=([^&]+)/i);
|
||||||
|
|||||||
@@ -37,7 +37,7 @@ export const fetchSystemVoltTempService = async () => {
|
|||||||
"+15V": rawData[0],
|
"+15V": rawData[0],
|
||||||
"+5V": rawData[1],
|
"+5V": rawData[1],
|
||||||
"-15V": rawData[2],
|
"-15V": rawData[2],
|
||||||
"-98V": rawData[3],
|
"-96V": rawData[3],
|
||||||
"ADC Temp": rawData[4], // Achtung: Hier 'ADC Temp' anstatt "Temperatur AD Wandler"
|
"ADC Temp": rawData[4], // Achtung: Hier 'ADC Temp' anstatt "Temperatur AD Wandler"
|
||||||
"CPU Temp": rawData[5], // 'CPU Temp' anstatt "Temperatur CPU"
|
"CPU Temp": rawData[5], // 'CPU Temp' anstatt "Temperatur CPU"
|
||||||
};
|
};
|
||||||
|
|||||||
@@ -1,5 +1,5 @@
|
|||||||
/**
|
/**
|
||||||
* Holt Messwerte für -98V aus der passenden JSON-Datei über die API
|
* Holt Messwerte für -96V aus der passenden JSON-Datei über die API
|
||||||
* @param type - Typ der Daten: DIA0 = alle, DIA1 = stündlich, DIA2 = täglich
|
* @param type - Typ der Daten: DIA0 = alle, DIA1 = stündlich, DIA2 = täglich
|
||||||
*/
|
*/
|
||||||
export const fetchSystemspannung98VminusService = async (
|
export const fetchSystemspannung98VminusService = async (
|
||||||
@@ -8,7 +8,7 @@ export const fetchSystemspannung98VminusService = async (
|
|||||||
try {
|
try {
|
||||||
const isDev = process.env.NODE_ENV === "development";
|
const isDev = process.env.NODE_ENV === "development";
|
||||||
|
|
||||||
const channel = 115; // 115 = -98V laut Spezifikation
|
const channel = 115; // 115 = -96V laut Spezifikation
|
||||||
// Dynamisch: to = heute, from = 30 Tage zurück
|
// Dynamisch: to = heute, from = 30 Tage zurück
|
||||||
const getDateParts = (date: Date) => {
|
const getDateParts = (date: Date) => {
|
||||||
const y = date.getFullYear();
|
const y = date.getFullYear();
|
||||||
@@ -28,7 +28,7 @@ export const fetchSystemspannung98VminusService = async (
|
|||||||
|
|
||||||
console.log("[Service] fetchSystemspannung98VminusService", path);
|
console.log("[Service] fetchSystemspannung98VminusService", path);
|
||||||
const res = await fetch(path);
|
const res = await fetch(path);
|
||||||
if (!res.ok) throw new Error("❌ Fehler beim Abrufen der -98V-Daten");
|
if (!res.ok) throw new Error("❌ Fehler beim Abrufen der -96V-Daten");
|
||||||
|
|
||||||
return await res.json();
|
return await res.json();
|
||||||
} catch (err) {
|
} catch (err) {
|
||||||
|
|||||||
@@ -72,6 +72,15 @@ body {
|
|||||||
.text-balance {
|
.text-balance {
|
||||||
text-wrap: balance;
|
text-wrap: balance;
|
||||||
}
|
}
|
||||||
|
/* Generic focus ring */
|
||||||
|
.focus-ring {
|
||||||
|
outline: 2px solid var(--color-ring);
|
||||||
|
outline-offset: 2px;
|
||||||
|
}
|
||||||
|
.focus-ring:focus-visible {
|
||||||
|
outline: 2px solid var(--color-ring);
|
||||||
|
outline-offset: 2px;
|
||||||
|
}
|
||||||
/* Semantic shortcut utilities */
|
/* Semantic shortcut utilities */
|
||||||
.bg-background {
|
.bg-background {
|
||||||
background-color: var(--color-background);
|
background-color: var(--color-background);
|
||||||
@@ -229,6 +238,207 @@ body {
|
|||||||
opacity: 0.6;
|
opacity: 0.6;
|
||||||
cursor: not-allowed;
|
cursor: not-allowed;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/* Icon button (used in modals/toolbars) */
|
||||||
|
.icon-btn {
|
||||||
|
display: inline-flex;
|
||||||
|
align-items: center;
|
||||||
|
justify-content: center;
|
||||||
|
width: 2rem;
|
||||||
|
height: 2rem;
|
||||||
|
border-radius: 0.375rem;
|
||||||
|
color: var(--color-fg-muted);
|
||||||
|
transition: background-color 0.15s ease, color 0.15s ease;
|
||||||
|
}
|
||||||
|
.icon-btn:hover {
|
||||||
|
background: var(--color-surface-alt);
|
||||||
|
color: var(--color-fg);
|
||||||
|
}
|
||||||
|
.icon-btn:focus-visible {
|
||||||
|
outline: 2px solid var(--color-ring);
|
||||||
|
outline-offset: 2px;
|
||||||
|
}
|
||||||
|
|
||||||
|
/* Toolbar container */
|
||||||
|
.toolbar {
|
||||||
|
display: flex;
|
||||||
|
gap: 0.5rem;
|
||||||
|
align-items: center;
|
||||||
|
background: var(--color-surface-alt);
|
||||||
|
border: 1px solid var(--color-border);
|
||||||
|
border-radius: 0.5rem;
|
||||||
|
padding: 0.5rem;
|
||||||
|
}
|
||||||
|
|
||||||
|
/* Tab buttons (settings modal, etc.) */
|
||||||
|
.tab-btn {
|
||||||
|
position: relative;
|
||||||
|
font-weight: 600;
|
||||||
|
font-size: 0.75rem; /* text-xs */
|
||||||
|
padding: 0.375rem 0.875rem;
|
||||||
|
border-radius: 0.375rem 0.375rem 0 0;
|
||||||
|
transition: color 0.15s ease, background-color 0.15s ease;
|
||||||
|
}
|
||||||
|
.tab-btn-active {
|
||||||
|
background: var(--color-surface);
|
||||||
|
color: var(--color-accent);
|
||||||
|
box-shadow: 0 1px 2px rgba(0, 0, 0, 0.08);
|
||||||
|
}
|
||||||
|
.tab-btn:not(.tab-btn-active) {
|
||||||
|
color: var(--color-fg-muted);
|
||||||
|
}
|
||||||
|
.tab-btn:not(.tab-btn-active):hover {
|
||||||
|
color: var(--color-fg);
|
||||||
|
}
|
||||||
|
|
||||||
|
/* Modal panel base */
|
||||||
|
.modal-panel {
|
||||||
|
background: var(--color-surface);
|
||||||
|
color: var(--color-fg);
|
||||||
|
border: 1px solid var(--color-border);
|
||||||
|
border-radius: 0.75rem;
|
||||||
|
box-shadow: 0 8px 28px -6px rgba(0, 0, 0, 0.35),
|
||||||
|
0 4px 12px rgba(0, 0, 0, 0.15);
|
||||||
|
padding: 0;
|
||||||
|
display: flex;
|
||||||
|
flex-direction: column;
|
||||||
|
}
|
||||||
|
.modal-header {
|
||||||
|
display: flex;
|
||||||
|
align-items: center;
|
||||||
|
justify-content: space-between;
|
||||||
|
padding: 0.75rem 0.875rem;
|
||||||
|
background: var(--color-surface-alt);
|
||||||
|
border-bottom: 1px solid var(--color-border);
|
||||||
|
border-radius: 0.75rem 0.75rem 0 0;
|
||||||
|
}
|
||||||
|
.modal-footer {
|
||||||
|
padding: 0.75rem 1rem;
|
||||||
|
background: var(--color-surface-alt);
|
||||||
|
border-top: 1px solid var(--color-border);
|
||||||
|
border-radius: 0 0 0.75rem 0.75rem;
|
||||||
|
}
|
||||||
|
.modal-body-scroll {
|
||||||
|
overflow-y: auto;
|
||||||
|
scrollbar-width: thin;
|
||||||
|
}
|
||||||
|
.modal-body-scroll::-webkit-scrollbar {
|
||||||
|
width: 8px;
|
||||||
|
}
|
||||||
|
.modal-body-scroll::-webkit-scrollbar-track {
|
||||||
|
background: transparent;
|
||||||
|
}
|
||||||
|
.modal-body-scroll::-webkit-scrollbar-thumb {
|
||||||
|
background: var(--color-border);
|
||||||
|
border-radius: 4px;
|
||||||
|
}
|
||||||
|
.modal-body-scroll::-webkit-scrollbar-thumb:hover {
|
||||||
|
background: var(--color-fg-muted);
|
||||||
|
}
|
||||||
|
|
||||||
|
/* Dropdown (Listbox) styling helpers */
|
||||||
|
.dropdown-surface {
|
||||||
|
background: var(--color-surface);
|
||||||
|
color: var(--color-fg);
|
||||||
|
border: 1px solid var(--color-border);
|
||||||
|
border-radius: 0.5rem;
|
||||||
|
padding: 0.25rem 0.5rem;
|
||||||
|
font-size: 0.8125rem;
|
||||||
|
}
|
||||||
|
.dropdown-options {
|
||||||
|
background: var(--color-surface);
|
||||||
|
border: 1px solid var(--color-border);
|
||||||
|
border-radius: 0.5rem;
|
||||||
|
box-shadow: 0 4px 14px rgba(0, 0, 0, 0.25);
|
||||||
|
}
|
||||||
|
.dropdown-option-active {
|
||||||
|
background: var(--color-accent);
|
||||||
|
color: #fff;
|
||||||
|
}
|
||||||
|
.dropdown-option-hover:not(.dropdown-option-active) {
|
||||||
|
background: var(--color-surface-alt);
|
||||||
|
}
|
||||||
|
/* Data table helpers */
|
||||||
|
.data-table-wrapper {
|
||||||
|
@media (prefers-reduced-motion: no-preference) {
|
||||||
|
transition: background-color 0.2s ease, color 0.2s ease;
|
||||||
|
}
|
||||||
|
background: var(--color-surface-alt);
|
||||||
|
border: 1px solid var(--color-border);
|
||||||
|
border-radius: 0.5rem;
|
||||||
|
padding: 0.25rem 0.5rem 0.75rem;
|
||||||
|
}
|
||||||
|
.data-table {
|
||||||
|
width: 100%;
|
||||||
|
border-collapse: separate;
|
||||||
|
border-spacing: 0;
|
||||||
|
font-size: 0.75rem; /* text-xs */
|
||||||
|
}
|
||||||
|
.data-table th,
|
||||||
|
.data-table td {
|
||||||
|
padding: 0.4rem 0.6rem;
|
||||||
|
border-right: 1px solid var(--color-border);
|
||||||
|
border-bottom: 1px solid var(--color-border);
|
||||||
|
vertical-align: middle;
|
||||||
|
}
|
||||||
|
.data-table th:last-child,
|
||||||
|
.data-table td:last-child {
|
||||||
|
border-right: none;
|
||||||
|
}
|
||||||
|
.data-table thead th {
|
||||||
|
position: sticky;
|
||||||
|
top: 0;
|
||||||
|
z-index: 5;
|
||||||
|
background: var(--color-surface-alt);
|
||||||
|
font-weight: 600;
|
||||||
|
font-size: 0.72rem; /* improved readability */
|
||||||
|
color: var(--color-fg-muted);
|
||||||
|
-webkit-font-smoothing: antialiased;
|
||||||
|
-moz-osx-font-smoothing: grayscale;
|
||||||
|
text-rendering: optimizeLegibility;
|
||||||
|
letter-spacing: 0.5px;
|
||||||
|
}
|
||||||
|
.data-table tbody tr:hover {
|
||||||
|
background: color-mix(in srgb, var(--color-surface-alt) 92%, transparent);
|
||||||
|
}
|
||||||
|
.data-table tbody tr:nth-child(even) {
|
||||||
|
background: color-mix(in srgb, var(--color-surface) 94%, transparent);
|
||||||
|
}
|
||||||
|
.data-table tbody tr:nth-child(even):hover {
|
||||||
|
background: color-mix(in srgb, var(--color-surface-alt) 85%, transparent);
|
||||||
|
}
|
||||||
|
.data-table tbody td {
|
||||||
|
font-size: 0.75rem;
|
||||||
|
}
|
||||||
|
.data-table thead th:first-child {
|
||||||
|
border-top-left-radius: 0.35rem;
|
||||||
|
}
|
||||||
|
.data-table thead th:last-child {
|
||||||
|
border-top-right-radius: 0.35rem;
|
||||||
|
}
|
||||||
|
.prio-dot {
|
||||||
|
width: 0.85rem;
|
||||||
|
height: 0.85rem;
|
||||||
|
border-radius: 0.25rem;
|
||||||
|
box-shadow: 0 0 0 1px var(--color-border);
|
||||||
|
}
|
||||||
|
.table-scroll-region {
|
||||||
|
scrollbar-width: thin;
|
||||||
|
}
|
||||||
|
.table-scroll-region::-webkit-scrollbar {
|
||||||
|
height: 10px;
|
||||||
|
width: 10px;
|
||||||
|
}
|
||||||
|
.table-scroll-region::-webkit-scrollbar-track {
|
||||||
|
background: transparent;
|
||||||
|
}
|
||||||
|
.table-scroll-region::-webkit-scrollbar-thumb {
|
||||||
|
background: var(--color-border);
|
||||||
|
border-radius: 6px;
|
||||||
|
}
|
||||||
|
.table-scroll-region::-webkit-scrollbar-thumb:hover {
|
||||||
|
background: var(--color-fg-muted);
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
/* Form elements use tokens */
|
/* Form elements use tokens */
|
||||||
|
|||||||
50
styles/muiTheme.ts
Normal file
50
styles/muiTheme.ts
Normal file
@@ -0,0 +1,50 @@
|
|||||||
|
import { createTheme } from '@mui/material/styles';
|
||||||
|
|
||||||
|
// Map existing CSS variable tokens to MUI palette via getComputedStyle at runtime (for SSR fallback provide defaults)
|
||||||
|
const cssVar = (name: string, fallback: string) => {
|
||||||
|
if (typeof window === 'undefined') return fallback;
|
||||||
|
const v = getComputedStyle(document.documentElement).getPropertyValue(name).trim();
|
||||||
|
return v || fallback;
|
||||||
|
};
|
||||||
|
|
||||||
|
export const buildTheme = () => {
|
||||||
|
const mode: 'light' | 'dark' = (typeof document !== 'undefined' && document.documentElement.classList.contains('dark')) ? 'dark' : 'light';
|
||||||
|
const primaryMain = cssVar('--color-accent', '#00aeef');
|
||||||
|
const bgDefault = cssVar('--color-background', mode === 'dark' ? '#0f1115' : '#f1f5f9');
|
||||||
|
const bgPaper = cssVar('--color-surface', mode === 'dark' ? '#1c232d' : '#ffffff');
|
||||||
|
const textPrimary = cssVar('--color-fg', mode === 'dark' ? '#e2e8f0' : '#0f172a');
|
||||||
|
const textSecondary = cssVar('--color-fg-muted', mode === 'dark' ? '#94a3b8' : '#475569');
|
||||||
|
|
||||||
|
return createTheme({
|
||||||
|
palette: {
|
||||||
|
mode,
|
||||||
|
primary: { main: primaryMain },
|
||||||
|
background: { default: bgDefault, paper: bgPaper },
|
||||||
|
text: { primary: textPrimary, secondary: textSecondary },
|
||||||
|
divider: cssVar('--color-border', mode === 'dark' ? '#334155' : '#e2e8f0'),
|
||||||
|
},
|
||||||
|
shape: { borderRadius: 8 },
|
||||||
|
typography: {
|
||||||
|
fontFamily: 'Roboto, Arial, Helvetica, sans-serif',
|
||||||
|
button: { textTransform: 'none', fontWeight: 600 },
|
||||||
|
fontSize: 14,
|
||||||
|
},
|
||||||
|
components: {
|
||||||
|
MuiButton: {
|
||||||
|
styleOverrides: {
|
||||||
|
root: { borderRadius: 8 },
|
||||||
|
containedPrimary: {
|
||||||
|
boxShadow: '0 2px 4px rgba(0,0,0,0.3)',
|
||||||
|
}
|
||||||
|
}
|
||||||
|
},
|
||||||
|
MuiPaper: {
|
||||||
|
styleOverrides: {
|
||||||
|
root: { backgroundImage: 'none' }
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
});
|
||||||
|
};
|
||||||
|
|
||||||
|
export const muiTheme = buildTheme();
|
||||||
Reference in New Issue
Block a user