Compare commits
56 Commits
c03802e97f
...
3edb8a053c
| 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 | ||
|
|
001b237dd7 | ||
|
|
af21b180f1 | ||
|
|
fefff9419d | ||
|
|
27c60c6742 | ||
|
|
c8ec763aac | ||
|
|
d163df0d96 | ||
|
|
12d3a17f60 | ||
|
|
f3339ccafd | ||
|
|
fab8a02ce9 | ||
|
|
eb0585072d | ||
|
|
a596422056 | ||
|
|
531fa93b70 | ||
|
|
72341abb23 | ||
|
|
9c218b2a1d | ||
|
|
d38d3191c5 | ||
|
|
112f537904 | ||
|
|
25b6c5c3b0 | ||
|
|
398d13bf1b | ||
|
|
91b7c8d40f | ||
|
|
7dfef4b16a | ||
|
|
0397f23196 | ||
|
|
0865d61450 | ||
|
|
2eb8f3a255 | ||
|
|
22321a7ac9 |
@@ -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.855
|
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.855
|
NEXT_PUBLIC_APP_VERSION=1.6.913
|
||||||
NEXT_PUBLIC_CPL_MODE=production
|
NEXT_PUBLIC_CPL_MODE=production
|
||||||
797
CHANGELOG.md
797
CHANGELOG.md
File diff suppressed because it is too large
Load Diff
100
Jenkinsfile
vendored
100
Jenkinsfile
vendored
@@ -1,65 +1,95 @@
|
|||||||
pipeline {
|
pipeline {
|
||||||
agent any
|
agent any
|
||||||
tools { nodejs 'node20' } // exakt der Name aus "Manage Jenkins → Tools"
|
|
||||||
|
environment {
|
||||||
|
CI = "true"
|
||||||
|
NODE_ENV = "production"
|
||||||
|
NEXT_TELEMETRY_DISABLED = "1"
|
||||||
|
PORT = "3000"
|
||||||
|
}
|
||||||
|
|
||||||
|
options {
|
||||||
|
timestamps()
|
||||||
|
}
|
||||||
|
|
||||||
stages {
|
stages {
|
||||||
stage('Versions') {
|
stage('Checkout') {
|
||||||
steps { sh 'node -v && npm -v' }
|
steps {
|
||||||
|
checkout scm
|
||||||
|
sh '''
|
||||||
|
set -eux
|
||||||
|
git status --short || true
|
||||||
|
# Submodule & LFS falls vorhanden
|
||||||
|
git submodule update --init --recursive || true
|
||||||
|
git lfs install || true
|
||||||
|
git lfs fetch || true
|
||||||
|
git lfs checkout || true
|
||||||
|
'''
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
stage('Verify mocks') {
|
stage('verify-mocks') {
|
||||||
steps {
|
steps {
|
||||||
sh '''
|
sh '''
|
||||||
set -euxo pipefail
|
set -eux
|
||||||
npm ci
|
docker run --rm -v "$PWD":/ws -w /ws \
|
||||||
echo "=== git ls-files ==="
|
mcr.microsoft.com/playwright:v1.54.2-jammy bash -lc "
|
||||||
git ls-files | grep -i "^mocks/device-cgi-simulator/SERVICE/systemMockData.js" || true
|
pwd
|
||||||
echo "=== ls -la ==="
|
node -v && npm -v
|
||||||
|
npm ci --ignore-scripts
|
||||||
|
echo '=== git ls-files ==='
|
||||||
|
git ls-files | grep -i '^mocks/device-cgi-simulator/SERVICE/systemMockData.js' || true
|
||||||
|
echo '=== ls -la ==='
|
||||||
ls -la mocks/device-cgi-simulator/SERVICE || true
|
ls -la mocks/device-cgi-simulator/SERVICE || true
|
||||||
echo "=== file exists? ==="
|
echo '=== file exists? ==='
|
||||||
test -f mocks/device-cgi-simulator/SERVICE/systemMockData.js && echo "FOUND" || (echo "MISSING" && exit 1)
|
test -f mocks/device-cgi-simulator/SERVICE/systemMockData.js && echo 'FOUND' || (echo 'MISSING' && exit 1)
|
||||||
|
"
|
||||||
'''
|
'''
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
stage('Build & E2E (chromium)') {
|
stage('e2e-dev') {
|
||||||
environment {
|
|
||||||
CI = 'true'
|
|
||||||
NODE_ENV = 'production'
|
|
||||||
NEXT_TELEMETRY_DISABLED = '1'
|
|
||||||
PORT = '3000'
|
|
||||||
}
|
|
||||||
steps {
|
steps {
|
||||||
sh '''
|
sh '''
|
||||||
set -euxo pipefail
|
set -eux
|
||||||
# Install devDependencies as well (rimraf, cross-env, etc.)
|
docker run --rm -v "$PWD":/ws -w /ws -p 3000:3000 \
|
||||||
|
mcr.microsoft.com/playwright:v1.54.2-jammy bash -lc "
|
||||||
|
node -v && npm -v
|
||||||
env npm_config_production=false npm ci
|
env npm_config_production=false npm ci
|
||||||
|
|
||||||
# Build Next.js
|
|
||||||
npm run build
|
npm run build
|
||||||
|
|
||||||
# Start local static simulator in background
|
|
||||||
npm run server:sim &
|
npm run server:sim &
|
||||||
|
# Auf Port 3000 warten
|
||||||
# Ensure Playwright browsers and OS deps are installed (best-effort)
|
node -e \\"const http=require('http');let n=120;function ping(){http.get('http://localhost:3000',res=>{console.log('Server is up');process.exit(0)}).on('error',()=>{if(n--<=0){console.error('Server did not start');process.exit(1)}setTimeout(ping,1000)});}ping();\\"
|
||||||
npx playwright install-deps || true
|
|
||||||
npx playwright install
|
|
||||||
|
|
||||||
# Wait until simulator responds on port 3000 (no curl dependency)
|
|
||||||
node -e "const http=require('http');let n=120;function ping(){http.get('http://localhost:3000',res=>{console.log('Server is up');process.exit(0)}).on('error',()=>{if(n--<=0){console.error('Server did not start');process.exit(1)}setTimeout(ping,1000)});}ping();"
|
|
||||||
|
|
||||||
# Run tests (chromium only to match Woodpecker)
|
|
||||||
npx playwright test --project=chromium
|
npx playwright test --project=chromium
|
||||||
|
"
|
||||||
'''
|
'''
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
post {
|
post {
|
||||||
success {
|
success {
|
||||||
sh 'curl -d "Tests erfolgreich in Jenkins" https://ntfy.sh/OEOr8DNB0aT2mXWg231PeEEKwvuzt86qgM8ezQmgfcX9ZIlZ35'
|
sh '''
|
||||||
|
docker run --rm curlimages/curl:8.9.1 \
|
||||||
|
-d "Tests erfolgreich in Jenkins" \
|
||||||
|
https://ntfy.sh/OEOr8DNB0aT2mXWg231PeEEKwvuzt86qgM8ezQmgfcX9ZIlZ35
|
||||||
|
'''
|
||||||
}
|
}
|
||||||
failure {
|
failure {
|
||||||
sh 'curl -d "Tests fehlgeschlagen in Jenkins" https://ntfy.sh/OEOr8DNB0aT2mXWg231PeEEKwvuzt86qgM8ezQmgfcX9ZIlZ35'
|
sh '''
|
||||||
|
docker run --rm curlimages/curl:8.9.1 \
|
||||||
|
-d "Tests fehlgeschlagen in Jenkins" \
|
||||||
|
https://ntfy.sh/OEOr8DNB0aT2mXWg231PeEEKwvuzt86qgM8ezQmgfcX9Z35
|
||||||
|
'''
|
||||||
|
}
|
||||||
|
always {
|
||||||
|
script {
|
||||||
|
if (fileExists('playwright-report')) {
|
||||||
|
archiveArtifacts artifacts: 'playwright-report/**', onlyIfSuccessful: false
|
||||||
|
} else {
|
||||||
|
echo 'Kein playwright-report gefunden.'
|
||||||
|
}
|
||||||
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -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"
|
||||||
}`}
|
}`}
|
||||||
|
|||||||
@@ -1,13 +1,16 @@
|
|||||||
"use client";
|
"use client";
|
||||||
import React from "react";
|
import React from "react";
|
||||||
import { useAppDispatch } from "@/redux/store";
|
import { useAppDispatch } from "@/redux/store";
|
||||||
import { setEvents } from "@/redux/slices/deviceEventsSlice";
|
import {
|
||||||
|
setEvents,
|
||||||
|
initPersistedTimings,
|
||||||
|
} from "@/redux/slices/deviceEventsSlice";
|
||||||
|
|
||||||
declare global {
|
declare global {
|
||||||
interface Window {
|
interface Window {
|
||||||
loopMeasurementEvent?: number[];
|
loopMeasurementEvent?: number[];
|
||||||
tdrMeasurementEvent?: number[];
|
tdrMeasurementEvent?: number[];
|
||||||
alignmentEvent?: number[];
|
comparisonEvent?: number[]; // renamed from alignmentEvent
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -18,6 +21,25 @@ export default function DeviceEventsBridge() {
|
|||||||
|
|
||||||
React.useEffect(() => {
|
React.useEffect(() => {
|
||||||
let lastSig = "";
|
let lastSig = "";
|
||||||
|
// Hydrate persisted timings once
|
||||||
|
try {
|
||||||
|
const raw =
|
||||||
|
typeof window !== "undefined" &&
|
||||||
|
localStorage.getItem("deviceEventsTimingsV1");
|
||||||
|
if (raw) {
|
||||||
|
const parsed = JSON.parse(raw);
|
||||||
|
dispatch(
|
||||||
|
initPersistedTimings({
|
||||||
|
loop: parsed.loop,
|
||||||
|
tdr: parsed.tdr,
|
||||||
|
compare: parsed.compare || parsed.align,
|
||||||
|
})
|
||||||
|
);
|
||||||
|
}
|
||||||
|
} catch (e) {
|
||||||
|
// eslint-disable-next-line no-console
|
||||||
|
console.warn("DeviceEventsBridge hydration failed", e);
|
||||||
|
}
|
||||||
const readAndDispatch = () => {
|
const readAndDispatch = () => {
|
||||||
const ksx = Array.isArray(window.loopMeasurementEvent)
|
const ksx = Array.isArray(window.loopMeasurementEvent)
|
||||||
? window.loopMeasurementEvent
|
? window.loopMeasurementEvent
|
||||||
@@ -25,8 +47,8 @@ export default function DeviceEventsBridge() {
|
|||||||
const ksy = Array.isArray(window.tdrMeasurementEvent)
|
const ksy = Array.isArray(window.tdrMeasurementEvent)
|
||||||
? window.tdrMeasurementEvent
|
? window.tdrMeasurementEvent
|
||||||
: undefined;
|
: undefined;
|
||||||
const ksz = Array.isArray(window.alignmentEvent)
|
const ksz = Array.isArray(window.comparisonEvent)
|
||||||
? window.alignmentEvent
|
? window.comparisonEvent
|
||||||
: undefined;
|
: undefined;
|
||||||
// Build a stable signature of first 32 values per array
|
// Build a stable signature of first 32 values per array
|
||||||
const to32 = (a?: number[]) => {
|
const to32 = (a?: number[]) => {
|
||||||
|
|||||||
@@ -5,14 +5,14 @@ import { useAppSelector } from "@/redux/store";
|
|||||||
export default function GlobalActivityOverlay() {
|
export default function GlobalActivityOverlay() {
|
||||||
const anyLoop = useAppSelector((s) => s.deviceEvents.anyLoopActive);
|
const anyLoop = useAppSelector((s) => s.deviceEvents.anyLoopActive);
|
||||||
const anyTdr = useAppSelector((s) => s.deviceEvents.anyTdrActive);
|
const anyTdr = useAppSelector((s) => s.deviceEvents.anyTdrActive);
|
||||||
const anyAlign = useAppSelector((s) => s.deviceEvents.anyAlignmentActive);
|
const anyCompare = useAppSelector((s) => s.deviceEvents.anyComparisonActive);
|
||||||
const ksx = useAppSelector((s) => s.deviceEvents.ksx);
|
const ksx = useAppSelector((s) => s.deviceEvents.ksx);
|
||||||
const ksy = useAppSelector((s) => s.deviceEvents.ksy);
|
const ksy = useAppSelector((s) => s.deviceEvents.ksy);
|
||||||
const ksz = useAppSelector((s) => s.deviceEvents.ksz);
|
const ksz = useAppSelector((s) => s.deviceEvents.ksz);
|
||||||
const loopStartedAt = useAppSelector((s) => s.deviceEvents.loopStartedAt);
|
const loopStartedAt = useAppSelector((s) => s.deviceEvents.loopStartedAt);
|
||||||
const tdrStartedAt = useAppSelector((s) => s.deviceEvents.tdrStartedAt);
|
const tdrStartedAt = useAppSelector((s) => s.deviceEvents.tdrStartedAt);
|
||||||
const alignmentStartedAt = useAppSelector(
|
const comparisonStartedAt = useAppSelector(
|
||||||
(s) => s.deviceEvents.alignmentStartedAt
|
(s) => s.deviceEvents.comparisonStartedAt
|
||||||
);
|
);
|
||||||
|
|
||||||
const fmt = (arr: number[]) =>
|
const fmt = (arr: number[]) =>
|
||||||
@@ -24,13 +24,13 @@ export default function GlobalActivityOverlay() {
|
|||||||
// Simple 1s ticker so progress bars advance while overlay is shown
|
// Simple 1s ticker so progress bars advance while overlay is shown
|
||||||
const [now, setNow] = useState<number>(Date.now());
|
const [now, setNow] = useState<number>(Date.now());
|
||||||
useEffect(() => {
|
useEffect(() => {
|
||||||
const active = anyLoop || anyTdr || anyAlign;
|
const active = anyLoop || anyTdr || anyCompare;
|
||||||
if (!active) return;
|
if (!active) return;
|
||||||
const id = setInterval(() => setNow(Date.now()), 1000);
|
const id = setInterval(() => setNow(Date.now()), 1000);
|
||||||
return () => clearInterval(id);
|
return () => clearInterval(id);
|
||||||
}, [anyLoop, anyTdr, anyAlign]);
|
}, [anyLoop, anyTdr, anyCompare]);
|
||||||
|
|
||||||
const active = anyLoop || anyTdr || anyAlign;
|
const active = anyLoop || anyTdr || anyCompare;
|
||||||
if (!active) return null;
|
if (!active) return null;
|
||||||
|
|
||||||
const clamp = (v: number, min = 0, max = 1) =>
|
const clamp = (v: number, min = 0, max = 1) =>
|
||||||
@@ -102,13 +102,13 @@ export default function GlobalActivityOverlay() {
|
|||||||
</div>
|
</div>
|
||||||
)}
|
)}
|
||||||
|
|
||||||
{anyAlign && (
|
{anyCompare && (
|
||||||
<div>
|
<div>
|
||||||
<div className="text-sm text-gray-800 mb-1">
|
<div className="text-sm text-gray-800 mb-1">
|
||||||
Abgleich läuft… (KÜ: {fmt(ksz)}) kann bis zu 10 Minuten dauern
|
Comparison läuft… (KÜ: {fmt(ksz)}) kann bis zu 10 Minuten dauern
|
||||||
</div>
|
</div>
|
||||||
{(() => {
|
{(() => {
|
||||||
const { pct } = compute(alignmentStartedAt, ALIGN_MS);
|
const { pct } = compute(comparisonStartedAt, ALIGN_MS);
|
||||||
return (
|
return (
|
||||||
<div>
|
<div>
|
||||||
<div className="h-2 w-full bg-gray-200 rounded overflow-hidden">
|
<div className="h-2 w-full bg-gray-200 rounded overflow-hidden">
|
||||||
|
|||||||
@@ -55,45 +55,57 @@ function Footer() {
|
|||||||
}, [showSlider]);
|
}, [showSlider]);
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<footer className="relative bg-gray-300 p-4 xl:p-2 2xl:p-4 overflow-hidden text-black laptop:h-[5vh] ">
|
<footer className="relative bg-[var(--color-surface-alt)] border-t border-base p-4 xl:p-2 2xl:p-4 overflow-hidden text-[var(--color-fg)] laptop:h-[5vh] theme-transition">
|
||||||
<div className="container mx-auto flex justify-between">
|
<div className="container mx-auto flex justify-between">
|
||||||
<div className="flex flex-row space-x-2">
|
<div className="flex flex-row space-x-2 items-center">
|
||||||
<Icon
|
<Icon
|
||||||
icon="material-symbols:factory-outline"
|
icon="material-symbols:factory-outline"
|
||||||
className="text-xl text-blue-400"
|
className="text-xl text-accent"
|
||||||
/>
|
/>
|
||||||
<p className="text-sm">Littwin Systemtechnik GmbH & Co. KG</p>
|
<p className="text-sm text-fg-muted">
|
||||||
|
Littwin Systemtechnik GmbH & Co. KG
|
||||||
|
</p>
|
||||||
</div>
|
</div>
|
||||||
<div className="flex flex-row space-x-2">
|
<div className="flex flex-row space-x-2 items-center">
|
||||||
<Icon icon="charm:phone" className="text-xl text-blue-400" />
|
<Icon icon="charm:phone" className="text-xl text-accent" />
|
||||||
<p className="text-sm">Telefon: 04402 972577-0</p>
|
<p className="text-sm text-fg-muted">Telefon: 04402 972577-0</p>
|
||||||
</div>
|
</div>
|
||||||
<div className="flex flex-row space-x-2">
|
<div className="flex flex-row space-x-2 items-center">
|
||||||
<Icon icon="mdi:email-outline" className="text-xl text-blue-400" />
|
<Icon icon="mdi:email-outline" className="text-xl text-accent" />
|
||||||
<p className="text-sm">kontakt@littwin-systemtechnik.de</p>
|
<p className="text-sm text-fg-muted">
|
||||||
|
kontakt@littwin-systemtechnik.de
|
||||||
|
</p>
|
||||||
</div>
|
</div>
|
||||||
<div
|
<div
|
||||||
className="flex flex-row space-x-2 cursor-pointer"
|
className="flex flex-row space-x-2 cursor-pointer items-center group"
|
||||||
onClick={() => setShowSlider(true)}
|
onClick={() => setShowSlider(true)}
|
||||||
>
|
>
|
||||||
<Icon icon="bi:book" className="text-xl text-blue-400" />
|
<Icon
|
||||||
<p className="text-sm">Handbücher</p>
|
icon="bi:book"
|
||||||
|
className="text-xl text-accent group-hover:brightness-110 transition"
|
||||||
|
/>
|
||||||
|
<p className="text-sm text-fg-muted group-hover:text-[var(--color-fg)] transition">
|
||||||
|
Handbücher
|
||||||
|
</p>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
<div
|
<div
|
||||||
ref={sliderRef}
|
ref={sliderRef}
|
||||||
className={`fixed top-0 right-0 w-64 h-full bg-white shadow-lg transform transition-transform duration-300 ${
|
className={`fixed top-0 right-0 w-64 h-full bg-[var(--color-surface)] border-l border-base shadow-lg transform transition-transform duration-300 ${
|
||||||
showSlider ? "translate-x-0" : "translate-x-full"
|
showSlider ? "translate-x-0" : "translate-x-full"
|
||||||
}`}
|
}`}
|
||||||
>
|
>
|
||||||
<div className="p-4 flex justify-between items-center border-b">
|
<div className="p-4 flex justify-between items-center border-b border-base">
|
||||||
<h3 className="text-lg font-semibold">PDF Handbücher</h3>
|
<h3 className="text-base font-semibold text-[var(--color-fg)]">
|
||||||
|
PDF Handbücher
|
||||||
|
</h3>
|
||||||
<button
|
<button
|
||||||
className="text-gray-500 hover:text-gray-800"
|
className="text-[var(--color-muted)] hover:text-[var(--color-fg)] transition"
|
||||||
onClick={() => setShowSlider(false)}
|
onClick={() => setShowSlider(false)}
|
||||||
|
aria-label="Schließen"
|
||||||
>
|
>
|
||||||
<Icon icon="carbon:close" className="text-2xl" />
|
<Icon icon="carbon:close" className="text-xl" />
|
||||||
</button>
|
</button>
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
@@ -102,7 +114,7 @@ function Footer() {
|
|||||||
{pdfFiles.map((fileName) => (
|
{pdfFiles.map((fileName) => (
|
||||||
<li
|
<li
|
||||||
key={fileName}
|
key={fileName}
|
||||||
className="cursor-pointer text-blue-500 hover:underline mb-2"
|
className="cursor-pointer text-accent hover:underline mb-2 text-sm"
|
||||||
onClick={() => loadPDF(fileName)}
|
onClick={() => loadPDF(fileName)}
|
||||||
>
|
>
|
||||||
{fileName}
|
{fileName}
|
||||||
|
|||||||
@@ -1,5 +1,5 @@
|
|||||||
"use client"; // components/Header.jsx
|
"use client"; // components/Header.jsx
|
||||||
import React, { useState, useEffect } from "react";
|
import React, { useState, useEffect, useRef, useCallback } from "react";
|
||||||
import { Icon } from "@iconify/react";
|
import { Icon } from "@iconify/react";
|
||||||
import Image from "next/image";
|
import Image from "next/image";
|
||||||
import { useRouter } from "next/router";
|
import { useRouter } from "next/router";
|
||||||
@@ -15,16 +15,18 @@ function Header() {
|
|||||||
const router = useRouter();
|
const router = useRouter();
|
||||||
const [showSettingsModal, setShowSettingsModal] = useState(false);
|
const [showSettingsModal, setShowSettingsModal] = useState(false);
|
||||||
const [isAdminLoggedIn, setIsAdminLoggedIn] = useState(false);
|
const [isAdminLoggedIn, setIsAdminLoggedIn] = useState(false);
|
||||||
|
const autoLogoutTimerRef = useRef<ReturnType<typeof setTimeout> | null>(null);
|
||||||
// Removed duplicate declaration of deviceName
|
// Removed duplicate declaration of deviceName
|
||||||
|
|
||||||
const handleCloseSettingsModal = () => setShowSettingsModal(false);
|
const handleCloseSettingsModal = () => setShowSettingsModal(false);
|
||||||
|
|
||||||
const handleLogout = () => {
|
const handleLogout = useCallback(() => {
|
||||||
sessionStorage.removeItem("token"); // Token entfernen
|
sessionStorage.removeItem("token"); // Token entfernen
|
||||||
localStorage.setItem("isAdminLoggedIn", "false"); // Admin-Status entfernen
|
localStorage.setItem("isAdminLoggedIn", "false"); // Admin-Status entfernen
|
||||||
|
localStorage.removeItem("adminLoginTime"); // Login-Zeitpunkt entfernen
|
||||||
setIsAdminLoggedIn(false); // Zustand sofort aktualisieren
|
setIsAdminLoggedIn(false); // Zustand sofort aktualisieren
|
||||||
router.push("/offline.html"); // Weiterleitung
|
router.push("/offline.html"); // Weiterleitung
|
||||||
};
|
}, [router]);
|
||||||
|
|
||||||
useEffect(() => {
|
useEffect(() => {
|
||||||
// Initialer Check beim Laden der Komponente
|
// Initialer Check beim Laden der Komponente
|
||||||
@@ -43,6 +45,56 @@ function Header() {
|
|||||||
clearInterval(interval); // Intervall stoppen, wenn die Komponente entladen wird
|
clearInterval(interval); // Intervall stoppen, wenn die Komponente entladen wird
|
||||||
};
|
};
|
||||||
}, [isAdminLoggedIn]);
|
}, [isAdminLoggedIn]);
|
||||||
|
|
||||||
|
// Auto-Logout nach 1 Minute (Test): nutzt adminLoginTime aus localStorage
|
||||||
|
useEffect(() => {
|
||||||
|
// Timer bereinigen, wenn sich der Status ändert
|
||||||
|
if (autoLogoutTimerRef.current) {
|
||||||
|
clearTimeout(autoLogoutTimerRef.current);
|
||||||
|
autoLogoutTimerRef.current = null;
|
||||||
|
}
|
||||||
|
|
||||||
|
if (!isAdminLoggedIn) return;
|
||||||
|
|
||||||
|
const iso = localStorage.getItem("adminLoginTime");
|
||||||
|
const loginTime = iso ? new Date(iso).getTime() : Date.now();
|
||||||
|
if (!iso) {
|
||||||
|
// Falls älterer Login ohne Zeitstempel, setze jetzt
|
||||||
|
try {
|
||||||
|
localStorage.setItem(
|
||||||
|
"adminLoginTime",
|
||||||
|
new Date(loginTime).toISOString()
|
||||||
|
);
|
||||||
|
} catch {
|
||||||
|
void 0; // ignore write errors (e.g., storage disabled)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// 1 Minute ab Login (60_000 ms), eine Stunde (3_600_000 ms) im Produktivbetrieb
|
||||||
|
const target = loginTime + 3_600_000;
|
||||||
|
const delay = Math.max(0, target - Date.now());
|
||||||
|
|
||||||
|
// Fallback: wenn Datum in Vergangenheit (z.B. Uhrzeit geändert), sofort abmelden
|
||||||
|
autoLogoutTimerRef.current = setTimeout(() => {
|
||||||
|
// Versuche den Button zu klicken, falls vorhanden
|
||||||
|
const btn = document.querySelector<HTMLButtonElement>(
|
||||||
|
'button[aria-label="Abmelden"]'
|
||||||
|
);
|
||||||
|
if (btn) {
|
||||||
|
btn.click();
|
||||||
|
} else {
|
||||||
|
// Fallback direkt
|
||||||
|
handleLogout();
|
||||||
|
}
|
||||||
|
}, delay);
|
||||||
|
|
||||||
|
return () => {
|
||||||
|
if (autoLogoutTimerRef.current) {
|
||||||
|
clearTimeout(autoLogoutTimerRef.current);
|
||||||
|
autoLogoutTimerRef.current = null;
|
||||||
|
}
|
||||||
|
};
|
||||||
|
}, [isAdminLoggedIn, handleLogout]);
|
||||||
//----------------------------------------------------------------
|
//----------------------------------------------------------------
|
||||||
const dispatch = useDispatch<AppDispatch>();
|
const dispatch = useDispatch<AppDispatch>();
|
||||||
|
|
||||||
@@ -57,27 +109,49 @@ function Header() {
|
|||||||
}, [deviceName, dispatch]);
|
}, [deviceName, dispatch]);
|
||||||
//----------------------------------------------------------------
|
//----------------------------------------------------------------
|
||||||
|
|
||||||
// Dark/Light Mode Toggle
|
// Dark/Light Mode Toggle (persisted)
|
||||||
const [isDark, setIsDark] = useState(false);
|
const [isDark, setIsDark] = useState(false);
|
||||||
|
// Initial state from html class / localStorage (set by _document script before hydration)
|
||||||
useEffect(() => {
|
useEffect(() => {
|
||||||
if (typeof window !== "undefined") {
|
if (typeof window === "undefined") return;
|
||||||
|
const html = document.documentElement;
|
||||||
|
const stored = localStorage.getItem("theme");
|
||||||
|
const active = stored ? stored === "dark" : html.classList.contains("dark");
|
||||||
|
setIsDark(active);
|
||||||
|
}, []);
|
||||||
|
|
||||||
|
useEffect(() => {
|
||||||
|
if (typeof window === "undefined") return;
|
||||||
const html = document.documentElement;
|
const html = document.documentElement;
|
||||||
if (isDark) {
|
if (isDark) {
|
||||||
html.classList.add("dark");
|
html.classList.add("dark");
|
||||||
|
localStorage.setItem("theme", "dark");
|
||||||
} else {
|
} else {
|
||||||
html.classList.remove("dark");
|
html.classList.remove("dark");
|
||||||
}
|
localStorage.setItem("theme", "light");
|
||||||
}
|
}
|
||||||
}, [isDark]);
|
}, [isDark]);
|
||||||
|
|
||||||
|
// Keyboard shortcut Alt + D to toggle theme
|
||||||
|
useEffect(() => {
|
||||||
|
const handler = (e: KeyboardEvent) => {
|
||||||
|
if (e.altKey && (e.key === "d" || e.key === "D")) {
|
||||||
|
e.preventDefault();
|
||||||
|
setIsDark((d) => !d);
|
||||||
|
}
|
||||||
|
};
|
||||||
|
window.addEventListener("keydown", handler);
|
||||||
|
return () => window.removeEventListener("keydown", handler);
|
||||||
|
}, []);
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<header className="bg-gray-300 dark:bg-gray-800 flex justify-between items-center w-full h-[13vh] laptop:h-[10vh] relative text-black dark:text-white ">
|
<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
|
||||||
}}
|
}}
|
||||||
@@ -86,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}
|
||||||
/>
|
/>
|
||||||
@@ -102,10 +176,10 @@ function Header() {
|
|||||||
priority
|
priority
|
||||||
/>
|
/>
|
||||||
<div className="flex flex-col leading-tight whitespace-nowrap">
|
<div className="flex flex-col leading-tight whitespace-nowrap">
|
||||||
<h2 className="text-xl laptop:text-base xl:text-lg font-bold text-gray-900 dark:text-gray-100">
|
<h2 className="text-xl laptop:text-base xl:text-lg font-bold text-[var(--color-fg)]">
|
||||||
Meldestation
|
Meldestation
|
||||||
</h2>
|
</h2>
|
||||||
<p className="text-gray-600 dark:text-gray-300 text-lg laptop:text-sm xl:text-base truncate max-w-[20vw]">
|
<p className="text-[var(--color-fg-muted)] text-lg laptop:text-sm xl:text-base truncate max-w-[20vw]">
|
||||||
{deviceName}
|
{deviceName}
|
||||||
</p>
|
</p>
|
||||||
</div>
|
</div>
|
||||||
@@ -117,7 +191,7 @@ function Header() {
|
|||||||
<button
|
<button
|
||||||
aria-label={isDark ? "Light Mode" : "Dark Mode"}
|
aria-label={isDark ? "Light Mode" : "Dark Mode"}
|
||||||
onClick={() => setIsDark((d) => !d)}
|
onClick={() => setIsDark((d) => !d)}
|
||||||
className="rounded-full p-2 bg-gray-200 dark:bg-gray-700 hover:bg-gray-300 dark:hover:bg-gray-600 transition"
|
className="rounded-full p-2 bg-[var(--color-surface-alt)]/80 hover:bg-[var(--color-surface-alt)] dark:bg-[var(--color-surface-alt)]/60 dark:hover:bg-[var(--color-surface-alt)] transition border border-[var(--color-border)]"
|
||||||
title={isDark ? "Light Mode" : "Dark Mode"}
|
title={isDark ? "Light Mode" : "Dark Mode"}
|
||||||
>
|
>
|
||||||
{isDark ? (
|
{isDark ? (
|
||||||
@@ -139,7 +213,8 @@ function Header() {
|
|||||||
<div className="flex items-center justify-end w-1/4 space-x-1">
|
<div className="flex items-center justify-end w-1/4 space-x-1">
|
||||||
<button
|
<button
|
||||||
onClick={handleLogout}
|
onClick={handleLogout}
|
||||||
className="bg-littwin-blue text-white px-4 py-2 rounded"
|
aria-label="Abmelden"
|
||||||
|
className="px-4 py-2 rounded bg-[var(--color-accent)] text-white hover:brightness-110 shadow-sm focus:outline-none focus:ring-2 focus:ring-[var(--color-ring)] focus:ring-offset-2 focus:ring-offset-[var(--color-background)] transition"
|
||||||
>
|
>
|
||||||
Abmelden
|
Abmelden
|
||||||
</button>
|
</button>
|
||||||
@@ -149,7 +224,7 @@ function Header() {
|
|||||||
|
|
||||||
{/* Warnhinweis, wenn der Admin angemeldet ist */}
|
{/* Warnhinweis, wenn der Admin angemeldet ist */}
|
||||||
{isAdminLoggedIn && (
|
{isAdminLoggedIn && (
|
||||||
<div className="absolute top-0 left-1/2 transform -translate-x-1/2 w-full xl:w-1/4 2xl:w-1/4 h-8 bg-yellow-400 text-center py-2 text-black font-bold">
|
<div className="absolute top-0 left-1/2 transform -translate-x-1/2 w-full xl:w-1/4 2xl:w-1/4 h-8 bg-[var(--color-warning)] text-center py-2 text-black font-bold tracking-wide">
|
||||||
Admin-Modus aktiv
|
Admin-Modus aktiv
|
||||||
</div>
|
</div>
|
||||||
)}
|
)}
|
||||||
|
|||||||
@@ -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,38 +35,26 @@ 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) */}
|
|
||||||
<div
|
|
||||||
style={{
|
|
||||||
position: "absolute",
|
|
||||||
top: "0.625rem",
|
|
||||||
right: "0.625rem",
|
|
||||||
display: "flex",
|
|
||||||
gap: "0.75rem",
|
|
||||||
}}
|
}}
|
||||||
>
|
>
|
||||||
|
{/* Header */}
|
||||||
|
<header className="flex items-center justify-between px-6 py-4 border-b border-base select-none">
|
||||||
|
<h2 className="text-base font-bold">
|
||||||
|
Messkurve Messwerteingang {selectedId ?? "–"}
|
||||||
|
</h2>
|
||||||
|
<div className="flex items-center gap-3">
|
||||||
<button
|
<button
|
||||||
onClick={() => setIsFullscreen((v) => !v)}
|
onClick={() => setIsFullscreen((v) => !v)}
|
||||||
style={{
|
className="icon-btn text-xl"
|
||||||
background: "transparent",
|
aria-label={isFullscreen ? "Vollbild verlassen" : "Vollbild"}
|
||||||
border: "none",
|
type="button"
|
||||||
fontSize: "1.5rem",
|
title={isFullscreen ? "Vollbild verlassen" : "Vollbild"}
|
||||||
cursor: "pointer",
|
|
||||||
}}
|
|
||||||
title={isFullscreen ? "Exit fullscreen" : "Fullscreen"}
|
|
||||||
aria-label={isFullscreen ? "Exit fullscreen" : "Fullscreen"}
|
|
||||||
>
|
>
|
||||||
<i
|
<i
|
||||||
className={
|
className={
|
||||||
@@ -74,45 +62,25 @@ export default function AnalogInputsChartModal({
|
|||||||
? "bi bi-fullscreen-exit"
|
? "bi bi-fullscreen-exit"
|
||||||
: "bi bi-arrows-fullscreen"
|
: "bi bi-arrows-fullscreen"
|
||||||
}
|
}
|
||||||
></i>
|
/>
|
||||||
</button>
|
</button>
|
||||||
<button
|
<button
|
||||||
onClick={() => dispatch(setIsChartModalOpen(false))}
|
onClick={() => dispatch(setIsChartModalOpen(false))}
|
||||||
style={{
|
className="icon-btn text-2xl"
|
||||||
background: "transparent",
|
|
||||||
border: "none",
|
|
||||||
fontSize: "1.5rem",
|
|
||||||
cursor: "pointer",
|
|
||||||
}}
|
|
||||||
title="Schließen"
|
|
||||||
aria-label="Modal schließen"
|
aria-label="Modal schließen"
|
||||||
|
type="button"
|
||||||
|
title="Schließen"
|
||||||
>
|
>
|
||||||
<i className="bi bi-x-circle-fill"></i>
|
<i className="bi bi-x-circle-fill" />
|
||||||
</button>
|
</button>
|
||||||
</div>
|
</div>
|
||||||
|
</header>
|
||||||
|
|
||||||
{/* Title row (align like IsoChartView) */}
|
{/* Body */}
|
||||||
<div className="flex justify-between items-center mb-2 pr-24">
|
<div className="flex-1 min-h-0 px-4 pt-3 pb-4 bg-[var(--color-surface)]">
|
||||||
<Dialog.Title className="text-lg font-semibold text-gray-700">
|
|
||||||
Messkurve Messwerteingang {selectedId ?? "–"}
|
|
||||||
</Dialog.Title>
|
|
||||||
</div>
|
|
||||||
|
|
||||||
{/* Chart container (structure similar to IsoChartView) */}
|
|
||||||
<div
|
|
||||||
style={{
|
|
||||||
flex: 1,
|
|
||||||
display: "flex",
|
|
||||||
flexDirection: "column",
|
|
||||||
height: "90%",
|
|
||||||
}}
|
|
||||||
>
|
|
||||||
{/* Optional: place an action bar here if needed */}
|
|
||||||
<div style={{ flex: 1, height: "90%" }}>
|
|
||||||
<AnalogInputsChart loading={loading} setLoading={setLoading} />
|
<AnalogInputsChart loading={loading} setLoading={setLoading} />
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
|
||||||
</Dialog.Panel>
|
</Dialog.Panel>
|
||||||
</div>
|
</div>
|
||||||
</Dialog>
|
</Dialog>
|
||||||
|
|||||||
@@ -108,64 +108,58 @@ 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">Bezeichnung:</span>
|
<span className="font-normal text-fg-secondary">Bezeichnung:</span>
|
||||||
<input
|
<input
|
||||||
type="text"
|
type="text"
|
||||||
className="w-full border rounded px-3 py-1 mb-4"
|
className="border border-base rounded px-2 py-1 w-full bg-[var(--color-surface-alt)] text-fg"
|
||||||
value={label}
|
value={label}
|
||||||
onChange={(e) => setLabel(e.target.value)}
|
onChange={(e) => setLabel(e.target.value)}
|
||||||
/>
|
/>
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
{/* Offset */}
|
|
||||||
<div className="grid grid-cols-2 gap-x-4 gap-y-3 mb-4">
|
<div className="grid grid-cols-2 gap-x-4 gap-y-3 mb-4">
|
||||||
<span className="font-normal">Offset:</span>
|
<span className="font-normal text-fg-secondary">Offset:</span>
|
||||||
<input
|
<input
|
||||||
type="number"
|
type="number"
|
||||||
step="0.001"
|
step="0.001"
|
||||||
className="border border-gray-300 rounded px-2 py-1 w-full text-right"
|
className="border border-base rounded px-2 py-1 w-full text-right bg-[var(--color-surface-alt)] text-fg"
|
||||||
value={offset}
|
value={offset}
|
||||||
onChange={(e) => setOffset(e.target.value)}
|
onChange={(e) => setOffset(e.target.value)}
|
||||||
/>
|
/>
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
{/* Faktor */}
|
|
||||||
<div className="grid grid-cols-2 gap-x-4 gap-y-3 mb-4">
|
<div className="grid grid-cols-2 gap-x-4 gap-y-3 mb-4">
|
||||||
<span className="font-normal">Faktor:</span>
|
<span className="font-normal text-fg-secondary">Faktor:</span>
|
||||||
<input
|
<input
|
||||||
type="number"
|
type="number"
|
||||||
step="0.001"
|
step="0.001"
|
||||||
className="border border-gray-300 rounded px-2 py-1 w-full text-right"
|
className="border border-base rounded px-2 py-1 w-full text-right bg-[var(--color-surface-alt)] text-fg"
|
||||||
value={factor}
|
value={factor}
|
||||||
onChange={(e) => setFactor(e.target.value)}
|
onChange={(e) => setFactor(e.target.value)}
|
||||||
/>
|
/>
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
{/* Einheit */}
|
|
||||||
<div className="grid grid-cols-2 gap-x-4 gap-y-3 mb-4">
|
<div className="grid grid-cols-2 gap-x-4 gap-y-3 mb-4">
|
||||||
<span className="font-normal">Einheit:</span>
|
<span className="font-normal text-fg-secondary">Einheit:</span>
|
||||||
<Listbox value={unit} onChange={setUnit}>
|
<Listbox value={unit} onChange={setUnit}>
|
||||||
<div className="relative w-full">
|
<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">
|
<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>
|
<span>{unit}</span>
|
||||||
<svg
|
<svg
|
||||||
className="w-5 h-5 text-gray-400"
|
className="w-5 h-5 text-muted"
|
||||||
xmlns="http://www.w3.org/2000/svg"
|
xmlns="http://www.w3.org/2000/svg"
|
||||||
viewBox="0 0 20 20"
|
viewBox="0 0 20 20"
|
||||||
fill="currentColor"
|
fill="currentColor"
|
||||||
@@ -178,7 +172,7 @@ export default function AnalogInputsSettingsModal() {
|
|||||||
/>
|
/>
|
||||||
</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 text-gray-900 font-sans">
|
<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) => (
|
{unitOptions.map((opt) => (
|
||||||
<Listbox.Option
|
<Listbox.Option
|
||||||
key={opt}
|
key={opt}
|
||||||
@@ -188,8 +182,8 @@ export default function AnalogInputsSettingsModal() {
|
|||||||
selected
|
selected
|
||||||
? "bg-littwin-blue text-white font-medium"
|
? "bg-littwin-blue text-white font-medium"
|
||||||
: active
|
: active
|
||||||
? "bg-gray-200"
|
? "bg-base-muted"
|
||||||
: "text-gray-900"
|
: "text-fg"
|
||||||
}`
|
}`
|
||||||
}
|
}
|
||||||
>
|
>
|
||||||
@@ -200,33 +194,32 @@ export default function AnalogInputsSettingsModal() {
|
|||||||
</div>
|
</div>
|
||||||
</Listbox>
|
</Listbox>
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
{/* Speicherintervall */}
|
|
||||||
<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">Speicherintervall:</span>
|
<span className="font-normal text-fg-secondary">
|
||||||
|
Speicherintervall:
|
||||||
|
</span>
|
||||||
<div className="relative w-full">
|
<div className="relative w-full">
|
||||||
<input
|
<input
|
||||||
type="number"
|
type="number"
|
||||||
className="border rounded px-2 py-1 pr-20 w-full text-right"
|
className="border border-base rounded px-2 py-1 pr-20 w-full text-right bg-[var(--color-surface-alt)] text-fg"
|
||||||
value={loggerInterval}
|
value={loggerInterval}
|
||||||
onChange={(e) => setLoggerInterval(e.target.value)}
|
onChange={(e) => setLoggerInterval(e.target.value)}
|
||||||
/>
|
/>
|
||||||
<span className="absolute right-2 top-1/2 transform -translate-y-1/2 text-gray-500 text-sm">
|
<span className="absolute right-2 top-1/2 transform -translate-y-1/2 text-muted text-sm">
|
||||||
Minuten
|
Minuten
|
||||||
</span>
|
</span>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
|
</div>
|
||||||
{/* Speichern-Button */}
|
<footer className="px-6 py-4 border-t border-base flex justify-end">
|
||||||
<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>
|
||||||
);
|
);
|
||||||
|
|||||||
@@ -37,8 +37,8 @@ export default function AnalogInputsTable({ loading }: { loading: boolean }) {
|
|||||||
|
|
||||||
return (
|
return (
|
||||||
<div
|
<div
|
||||||
className={`bg-white text-gray-900 dark:bg-gray-900 dark:text-gray-100 shadow-md border border-gray-200 dark:border-gray-700 p-3 rounded-lg laptop:p-1 xl:p-1 ${
|
className={`text-[var(--color-fg)] bg-[var(--color-surface)] dark:bg-[var(--color-surface)] shadow-sm border border-[var(--color-border)] p-3 rounded-lg laptop:p-1 xl:p-1 ${
|
||||||
loading ? "cursor-wait" : ""
|
loading ? "cursor-wait opacity-70" : ""
|
||||||
}`}
|
}`}
|
||||||
>
|
>
|
||||||
<h2 className="laptop:text-sm md:text-base 2xl:text-lg font-bold mb-3 flex items-center">
|
<h2 className="laptop:text-sm md:text-base 2xl:text-lg font-bold mb-3 flex items-center">
|
||||||
@@ -54,24 +54,24 @@ export default function AnalogInputsTable({ loading }: { loading: boolean }) {
|
|||||||
loading ? "cursor-wait" : ""
|
loading ? "cursor-wait" : ""
|
||||||
}`}
|
}`}
|
||||||
>
|
>
|
||||||
<thead className="bg-white text-gray-900 dark:bg-gray-900 dark:text-gray-100 border-b items-center">
|
<thead className="bg-[var(--color-surface-alt)]/60 dark:bg-[var(--color-surface-alt)]/30 text-[var(--color-fg)] border-b border-[var(--color-border)] items-center">
|
||||||
<tr>
|
<tr>
|
||||||
<th className="border p-1 text-left bg-white text-gray-900 dark:bg-gray-900 dark:text-gray-100">
|
<th className="border p-1 text-left bg-[var(--color-surface)] text-[var(--color-fg)] border-[var(--color-border)]">
|
||||||
Eingang
|
Eingang
|
||||||
</th>
|
</th>
|
||||||
<th className="border p-1 text-left bg-white text-gray-900 dark:bg-gray-900 dark:text-gray-100">
|
<th className="border p-1 text-left bg-[var(--color-surface)] text-[var(--color-fg)] border-[var(--color-border)]">
|
||||||
Messwert
|
Messwert
|
||||||
</th>
|
</th>
|
||||||
<th className="border p-1 text-left bg-white text-gray-900 dark:bg-gray-900 dark:text-gray-100">
|
<th className="border p-1 text-left bg-[var(--color-surface)] text-[var(--color-fg)] border-[var(--color-border)]">
|
||||||
Einheit
|
Einheit
|
||||||
</th>
|
</th>
|
||||||
<th className="border p-1 text-left bg-white text-gray-900 dark:bg-gray-900 dark:text-gray-100">
|
<th className="border p-1 text-left bg-[var(--color-surface)] text-[var(--color-fg)] border-[var(--color-border)]">
|
||||||
Bezeichnung
|
Bezeichnung
|
||||||
</th>
|
</th>
|
||||||
<th className="border p-1 text-left bg-white text-gray-900 dark:bg-gray-900 dark:text-gray-100">
|
<th className="border p-1 text-left bg-[var(--color-surface)] text-[var(--color-fg)] border-[var(--color-border)]">
|
||||||
Einstellungen
|
Einstellungen
|
||||||
</th>
|
</th>
|
||||||
<th className="border p-1 text-left bg-white text-gray-900 dark:bg-gray-900 dark:text-gray-100">
|
<th className="border p-1 text-left bg-[var(--color-surface)] text-[var(--color-fg)] border-[var(--color-border)]">
|
||||||
Messkurve
|
Messkurve
|
||||||
</th>
|
</th>
|
||||||
</tr>
|
</tr>
|
||||||
@@ -92,12 +92,12 @@ export default function AnalogInputsTable({ loading }: { loading: boolean }) {
|
|||||||
loading
|
loading
|
||||||
? "cursor-wait"
|
? "cursor-wait"
|
||||||
: analogInput.id === activeId
|
: analogInput.id === activeId
|
||||||
? "bg-blue-100 dark:bg-gray-700 dark:text-white"
|
? "bg-[var(--color-accent-soft)] dark:bg-[var(--color-surface-alt)]/60 text-[var(--color-fg)]"
|
||||||
: "hover:bg-gray-100 dark:hover:bg-gray-800"
|
: "hover:bg-[var(--color-surface-alt)]/70 dark:hover:bg-[var(--color-surface-alt)]/30"
|
||||||
}`}
|
}`}
|
||||||
>
|
>
|
||||||
<td
|
<td
|
||||||
className="border p-2 bg-white text-gray-900 dark:bg-gray-900 dark:text-gray-100"
|
className="border p-2 bg-[var(--color-surface)] text-[var(--color-fg)] border-[var(--color-border)]"
|
||||||
onClick={() => handleSelect(analogInput.id!, analogInput)}
|
onClick={() => handleSelect(analogInput.id!, analogInput)}
|
||||||
>
|
>
|
||||||
<div className="flex items-center gap-1 ">
|
<div className="flex items-center gap-1 ">
|
||||||
@@ -109,7 +109,7 @@ export default function AnalogInputsTable({ loading }: { loading: boolean }) {
|
|||||||
</div>
|
</div>
|
||||||
</td>
|
</td>
|
||||||
<td
|
<td
|
||||||
className="border p-2 text-right bg-white text-gray-900 dark:bg-gray-900 dark:text-gray-100"
|
className="border p-2 text-right bg-[var(--color-surface)] text-[var(--color-fg)] border-[var(--color-border)]"
|
||||||
onClick={() => handleSelect(analogInput.id!, analogInput)}
|
onClick={() => handleSelect(analogInput.id!, analogInput)}
|
||||||
>
|
>
|
||||||
{typeof analogInput.value === "number"
|
{typeof analogInput.value === "number"
|
||||||
@@ -118,19 +118,19 @@ export default function AnalogInputsTable({ loading }: { loading: boolean }) {
|
|||||||
</td>
|
</td>
|
||||||
|
|
||||||
<td
|
<td
|
||||||
className="border p-2 bg-white text-gray-900 dark:bg-gray-900 dark:text-gray-100"
|
className="border p-2 bg-[var(--color-surface)] text-[var(--color-fg)] border-[var(--color-border)]"
|
||||||
onClick={() => handleSelect(analogInput.id!, analogInput)}
|
onClick={() => handleSelect(analogInput.id!, analogInput)}
|
||||||
>
|
>
|
||||||
{analogInput.unit || "-"}
|
{analogInput.unit || "-"}
|
||||||
</td>
|
</td>
|
||||||
<td
|
<td
|
||||||
className="border p-2 bg-white text-gray-900 dark:bg-gray-900 dark:text-gray-100"
|
className="border p-2 bg-[var(--color-surface)] text-[var(--color-fg)] border-[var(--color-border)]"
|
||||||
onClick={() => handleSelect(analogInput.id!, analogInput)}
|
onClick={() => handleSelect(analogInput.id!, analogInput)}
|
||||||
>
|
>
|
||||||
{analogInput.label || "----"}
|
{analogInput.label || "----"}
|
||||||
</td>
|
</td>
|
||||||
|
|
||||||
<td className="border p-2 text-center bg-white text-gray-900 dark:bg-gray-900 dark:text-gray-100">
|
<td className="border p-2 text-center bg-[var(--color-surface)] text-[var(--color-fg)] border-[var(--color-border)]">
|
||||||
<button
|
<button
|
||||||
onClick={() => {
|
onClick={() => {
|
||||||
handleSelect(analogInput.id!, analogInput);
|
handleSelect(analogInput.id!, analogInput);
|
||||||
@@ -141,7 +141,7 @@ export default function AnalogInputsTable({ loading }: { loading: boolean }) {
|
|||||||
<Icon icon={settingsIcon} className="text-xl" />
|
<Icon icon={settingsIcon} className="text-xl" />
|
||||||
</button>
|
</button>
|
||||||
</td>
|
</td>
|
||||||
<td className="border p-2 text-center bg-white text-gray-900 dark:bg-gray-900 dark:text-gray-100">
|
<td className="border p-2 text-center bg-[var(--color-surface)] text-[var(--color-fg)] border-[var(--color-border)]">
|
||||||
<button
|
<button
|
||||||
onClick={() => {
|
onClick={() => {
|
||||||
handleSelect(analogInput.id!, analogInput);
|
handleSelect(analogInput.id!, analogInput);
|
||||||
|
|||||||
@@ -36,8 +36,8 @@ function AnalogInputsView() {
|
|||||||
}`}
|
}`}
|
||||||
>
|
>
|
||||||
<div className="grid grid-cols-1 gap-4 justify-items-start">
|
<div className="grid grid-cols-1 gap-4 justify-items-start">
|
||||||
<div className="bg-white dark:bg-gray-900 rounded-lg p-4 max-w-3xl text-gray-900 dark:text-gray-100">
|
<div className="rounded-lg p-4 max-w-3xl text-[var(--color-fg)] bg-[var(--color-surface)] dark:bg-[var(--color-surface)] border border-[var(--color-border)] shadow-sm">
|
||||||
<h2 className="text-xl font-semibold mb-4 text-gray-900 dark:text-gray-100">
|
<h2 className="text-xl font-semibold mb-4 text-[var(--color-fg)] tracking-wide">
|
||||||
Messwerteingänge
|
Messwerteingänge
|
||||||
</h2>
|
</h2>
|
||||||
<AnalogInputsTable loading={loading} />
|
<AnalogInputsTable loading={loading} />
|
||||||
|
|||||||
@@ -43,7 +43,7 @@ const Baugruppentraeger: React.FC = () => {
|
|||||||
baugruppen.push(
|
baugruppen.push(
|
||||||
<div
|
<div
|
||||||
key={i}
|
key={i}
|
||||||
className="flex bg-white shadow-md rounded-lg mb-4 xl:mb-0 lg:mb-0 border border-gray-200 w-full laptop:scale-y-75 xl:scale-y-90"
|
className="flex card mb-4 xl:mb-0 lg:mb-0 w-full laptop:scale-y-75 xl:scale-y-90"
|
||||||
>
|
>
|
||||||
<div className="flex gap-1">
|
<div className="flex gap-1">
|
||||||
{slots.map((version, index) => {
|
{slots.map((version, index) => {
|
||||||
|
|||||||
@@ -25,7 +25,7 @@ const DashboardView: React.FC = () => {
|
|||||||
}, [dispatch]);
|
}, [dispatch]);
|
||||||
//-------------------------------------
|
//-------------------------------------
|
||||||
return (
|
return (
|
||||||
<div className="flex flex-col gap-3 p-4 h-[calc(100vh-13vh-8vh)] laptop:h-[calc(100vh-10vh-5vh)] xl:h-[calc(100vh-10vh-6vh)] laptop:gap-0 bg-white dark:bg-gray-900 text-gray-900 dark:text-gray-100">
|
<div className="flex flex-col gap-3 p-4 h-[calc(100vh-13vh-8vh)] laptop:h-[calc(100vh-10vh-5vh)] xl:h-[calc(100vh-10vh-6vh)] laptop:gap-0 bg-[var(--color-background)] text-[var(--color-fg)]">
|
||||||
{/* Header */}
|
{/* Header */}
|
||||||
<div className="flex justify-between items-center w-full lg:w-2/3">
|
<div className="flex justify-between items-center w-full lg:w-2/3">
|
||||||
<div className="flex justify-between gap-1">
|
<div className="flex justify-between gap-1">
|
||||||
@@ -33,7 +33,7 @@ const DashboardView: React.FC = () => {
|
|||||||
icon="ri:calendar-schedule-line"
|
icon="ri:calendar-schedule-line"
|
||||||
className="text-littwin-blue text-4xl xl:text-2xl"
|
className="text-littwin-blue text-4xl xl:text-2xl"
|
||||||
/>
|
/>
|
||||||
<h1 className="text-xl font-bold text-gray-900 dark:text-gray-100 xl:text-base">
|
<h1 className="text-xl font-bold xl:text-base text-[var(--color-fg)] tracking-wide">
|
||||||
Letzten 20 Meldungen
|
Letzten 20 Meldungen
|
||||||
</h1>
|
</h1>
|
||||||
</div>
|
</div>
|
||||||
|
|||||||
@@ -48,22 +48,22 @@ export default function Last20MessagesTable({ className }: Props) {
|
|||||||
return (
|
return (
|
||||||
<div className={`flex flex-col gap-3 p-4 ${className}`}>
|
<div className={`flex flex-col gap-3 p-4 ${className}`}>
|
||||||
<div className="overflow-auto max-h-[80vh]">
|
<div className="overflow-auto max-h-[80vh]">
|
||||||
<table className="min-w-full border bg-white text-gray-900 dark:bg-gray-900 dark:text-gray-100">
|
<table className="min-w-full border border-[var(--color-border)] bg-[var(--color-surface)] text-[var(--color-fg)]">
|
||||||
<thead className="bg-white text-gray-900 dark:bg-gray-900 dark:text-gray-100 text-left sticky top-0 z-10">
|
<thead className="text-left sticky top-0 z-10 bg-[var(--color-surface-alt)]/70 dark:bg-[var(--color-surface-alt)]/25 text-[var(--color-fg)]">
|
||||||
<tr>
|
<tr>
|
||||||
<th className="p-2 border bg-white text-gray-900 dark:bg-gray-900 dark:text-gray-100">
|
<th className="p-2 border border-[var(--color-border)] bg-[var(--color-surface)] text-[var(--color-fg)]">
|
||||||
Prio
|
Prio
|
||||||
</th>
|
</th>
|
||||||
<th className="p-2 border bg-white text-gray-900 dark:bg-gray-900 dark:text-gray-100">
|
<th className="p-2 border border-[var(--color-border)] bg-[var(--color-surface)] text-[var(--color-fg)]">
|
||||||
Zeitstempel
|
Zeitstempel
|
||||||
</th>
|
</th>
|
||||||
<th className="p-2 border bg-white text-gray-900 dark:bg-gray-900 dark:text-gray-100">
|
<th className="p-2 border border-[var(--color-border)] bg-[var(--color-surface)] text-[var(--color-fg)]">
|
||||||
Quelle
|
Quelle
|
||||||
</th>
|
</th>
|
||||||
<th className="p-2 border bg-white text-gray-900 dark:bg-gray-900 dark:text-gray-100">
|
<th className="p-2 border border-[var(--color-border)] bg-[var(--color-surface)] text-[var(--color-fg)]">
|
||||||
Meldung
|
Meldung
|
||||||
</th>
|
</th>
|
||||||
<th className="p-2 border bg-white text-gray-900 dark:bg-gray-900 dark:text-gray-100">
|
<th className="p-2 border border-[var(--color-border)] bg-[var(--color-surface)] text-[var(--color-fg)]">
|
||||||
Status
|
Status
|
||||||
</th>
|
</th>
|
||||||
</tr>
|
</tr>
|
||||||
@@ -72,24 +72,24 @@ export default function Last20MessagesTable({ className }: Props) {
|
|||||||
{filteredMessages.slice(0, 20).map((msg, index) => (
|
{filteredMessages.slice(0, 20).map((msg, index) => (
|
||||||
<tr
|
<tr
|
||||||
key={index}
|
key={index}
|
||||||
className="hover:bg-gray-100 dark:hover:bg-gray-800"
|
className="hover:bg-[var(--color-surface-alt)]/70 dark:hover:bg-[var(--color-surface-alt)]/30 transition"
|
||||||
>
|
>
|
||||||
<td className="border p-2 bg-white text-gray-900 dark:bg-gray-900 dark:text-gray-100">
|
<td className="border p-2 bg-[var(--color-surface)] text-[var(--color-fg)] border-[var(--color-border)]">
|
||||||
<div
|
<div
|
||||||
className="w-4 h-4 rounded"
|
className="w-4 h-4 rounded"
|
||||||
style={{ backgroundColor: msg.c }}
|
style={{ backgroundColor: msg.c }}
|
||||||
></div>
|
></div>
|
||||||
</td>
|
</td>
|
||||||
<td className="border p-2 bg-white text-gray-900 dark:bg-gray-900 dark:text-gray-100">
|
<td className="border p-2 bg-[var(--color-surface)] text-[var(--color-fg)] border-[var(--color-border)]">
|
||||||
{msg.t}
|
{msg.t}
|
||||||
</td>
|
</td>
|
||||||
<td className="border p-2 bg-white text-gray-900 dark:bg-gray-900 dark:text-gray-100">
|
<td className="border p-2 bg-[var(--color-surface)] text-[var(--color-fg)] border-[var(--color-border)]">
|
||||||
{msg.i}
|
{msg.i}
|
||||||
</td>
|
</td>
|
||||||
<td className="border p-2 bg-white text-gray-900 dark:bg-gray-900 dark:text-gray-100">
|
<td className="border p-2 bg-[var(--color-surface)] text-[var(--color-fg)] border-[var(--color-border)]">
|
||||||
{msg.m}
|
{msg.m}
|
||||||
</td>
|
</td>
|
||||||
<td className="border p-2 bg-white text-gray-900 dark:bg-gray-900 dark:text-gray-100">
|
<td className="border p-2 bg-[var(--color-surface)] text-[var(--color-fg)] border-[var(--color-border)]">
|
||||||
{msg.v}
|
{msg.v}
|
||||||
</td>
|
</td>
|
||||||
</tr>
|
</tr>
|
||||||
@@ -97,7 +97,7 @@ export default function Last20MessagesTable({ className }: Props) {
|
|||||||
</tbody>
|
</tbody>
|
||||||
</table>
|
</table>
|
||||||
{messages.length === 0 && (
|
{messages.length === 0 && (
|
||||||
<div className="mt-4 text-center text-gray-500 italic dark:text-gray-400">
|
<div className="mt-4 text-center italic text-[var(--color-fg-muted)]">
|
||||||
Keine Meldungen im gewählten Zeitraum vorhanden.
|
Keine Meldungen im gewählten Zeitraum vorhanden.
|
||||||
</div>
|
</div>
|
||||||
)}
|
)}
|
||||||
|
|||||||
@@ -38,7 +38,7 @@ const NetworkInfo: React.FC = () => {
|
|||||||
|
|
||||||
return (
|
return (
|
||||||
<div className="w-full flex-direction: row flex">
|
<div className="w-full flex-direction: row flex">
|
||||||
<div className=" flex-grow flex justify-between items-center mt-1 bg-white dark:bg-gray-800 p-2 rounded-lg shadow-md border border-gray-200 dark:border-gray-700 laptop:m-0 laptop:scale-y-75 2xl:scale-y-75">
|
<div className=" flex-grow flex justify-between items-center mt-1 p-2 rounded-lg shadow-sm bg-[var(--color-surface)] dark:bg-[var(--color-surface)] border border-[var(--color-border)] laptop:m-0 laptop:scale-y-75 2xl:scale-y-75">
|
||||||
<div className="flex items-center space-x-4">
|
<div className="flex items-center space-x-4">
|
||||||
<Image
|
<Image
|
||||||
src="/images/IP-icon.svg"
|
src="/images/IP-icon.svg"
|
||||||
@@ -49,12 +49,8 @@ const NetworkInfo: React.FC = () => {
|
|||||||
priority
|
priority
|
||||||
/>
|
/>
|
||||||
<div>
|
<div>
|
||||||
<p className="text-xs text-gray-500 dark:text-gray-400">
|
<p className="text-xs text-[var(--color-fg-muted)]">IP-Adresse</p>
|
||||||
IP-Adresse
|
<p className="text-sm font-medium text-[var(--color-fg)]">{ip}</p>
|
||||||
</p>
|
|
||||||
<p className="text-sm font-medium text-gray-700 dark:text-gray-200">
|
|
||||||
{ip}
|
|
||||||
</p>
|
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
@@ -68,10 +64,8 @@ const NetworkInfo: React.FC = () => {
|
|||||||
priority
|
priority
|
||||||
/>
|
/>
|
||||||
<div>
|
<div>
|
||||||
<p className="text-xs text-gray-500 dark:text-gray-400">
|
<p className="text-xs text-[var(--color-fg-muted)]">Subnet-Maske</p>
|
||||||
Subnet-Maske
|
<p className="text-sm font-medium text-[var(--color-fg)]">
|
||||||
</p>
|
|
||||||
<p className="text-sm font-medium text-gray-700 dark:text-gray-200">
|
|
||||||
{subnet}
|
{subnet}
|
||||||
</p>
|
</p>
|
||||||
</div>
|
</div>
|
||||||
@@ -87,8 +81,8 @@ const NetworkInfo: React.FC = () => {
|
|||||||
priority
|
priority
|
||||||
/>
|
/>
|
||||||
<div>
|
<div>
|
||||||
<p className="text-xs text-gray-500 dark:text-gray-400">Gateway</p>
|
<p className="text-xs text-[var(--color-fg-muted)]">Gateway</p>
|
||||||
<p className="text-sm font-medium text-gray-700 dark:text-gray-200">
|
<p className="text-sm font-medium text-[var(--color-fg)]">
|
||||||
{gateway}
|
{gateway}
|
||||||
</p>
|
</p>
|
||||||
</div>
|
</div>
|
||||||
@@ -97,8 +91,8 @@ const NetworkInfo: React.FC = () => {
|
|||||||
<div className="flex items-center space-x-4">
|
<div className="flex items-center space-x-4">
|
||||||
<div className="text-xs font-bold text-littwin-blue">OPC-UA</div>
|
<div className="text-xs font-bold text-littwin-blue">OPC-UA</div>
|
||||||
<div>
|
<div>
|
||||||
<p className="text-xs text-gray-500 dark:text-gray-400">Status</p>
|
<p className="text-xs text-[var(--color-fg-muted)]">Status</p>
|
||||||
<p className="text-sm font-medium text-gray-700 dark:text-gray-200">
|
<p className="text-sm font-medium text-[var(--color-fg)]">
|
||||||
{opcUaZustand}
|
{opcUaZustand}
|
||||||
</p>
|
</p>
|
||||||
</div>
|
</div>
|
||||||
|
|||||||
@@ -4,9 +4,7 @@ import { Icon } from "@iconify/react";
|
|||||||
import { useSelector } from "react-redux";
|
import { useSelector } from "react-redux";
|
||||||
import { RootState } from "../../../redux/store";
|
import { RootState } from "../../../redux/store";
|
||||||
|
|
||||||
type VersionInfoProps = {
|
type VersionInfoProps = { className?: string };
|
||||||
className?: string;
|
|
||||||
};
|
|
||||||
|
|
||||||
const VersionInfo: React.FC<VersionInfoProps> = ({ className = "" }) => {
|
const VersionInfo: React.FC<VersionInfoProps> = ({ className = "" }) => {
|
||||||
const appVersion =
|
const appVersion =
|
||||||
@@ -17,26 +15,26 @@ const VersionInfo: React.FC<VersionInfoProps> = ({ className = "" }) => {
|
|||||||
);
|
);
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<div
|
<div className={`card w-full p-3 laptop:p-2 ${className}`}>
|
||||||
className={`bg-gray-50 dark:bg-gray-800 rounded-lg shadow-sm border border-gray-200 dark:border-gray-700 w-full laptop:p-2 ${className}`}
|
<h2 className="text-base font-semibold mb-2 text-[var(--color-fg)]">
|
||||||
>
|
|
||||||
<h2 className="text-lg font-semibold text-gray-700 dark:text-gray-200 mb-2">
|
|
||||||
Versionsinformationen
|
Versionsinformationen
|
||||||
</h2>
|
</h2>
|
||||||
|
<ul className="space-y-1">
|
||||||
<div className="flex flex-row p-2 space-x-2">
|
<li className="flex items-start gap-2">
|
||||||
<Icon icon="bx:code-block" className="text-xl text-blue-400" />
|
<Icon icon="bx:code-block" className="text-xl text-accent" />
|
||||||
<p className="text-sm text-gray-600 dark:text-gray-300">
|
<p className="text-sm text-fg-muted">
|
||||||
Applikationsversion: {appVersion}
|
Applikationsversion:{" "}
|
||||||
|
<span className="text-[var(--color-fg)]">{appVersion}</span>
|
||||||
</p>
|
</p>
|
||||||
</div>
|
</li>
|
||||||
|
<li className="flex items-start gap-2">
|
||||||
<div className="flex flex-row p-2 space-x-2">
|
<Icon icon="mdi:web" className="text-xl text-accent" />
|
||||||
<Icon icon="mdi:web" className="text-xl text-blue-400" />
|
<p className="text-sm text-fg-muted">
|
||||||
<p className="text-sm text-gray-600 dark:text-gray-300">
|
Webversion:{" "}
|
||||||
Webversion: {webVersion}
|
<span className="text-[var(--color-fg)]">{webVersion}</span>
|
||||||
</p>
|
</p>
|
||||||
</div>
|
</li>
|
||||||
|
</ul>
|
||||||
</div>
|
</div>
|
||||||
);
|
);
|
||||||
};
|
};
|
||||||
|
|||||||
@@ -30,7 +30,7 @@ export default function DigitalInputsWidget({
|
|||||||
//console.log("DigitalInputs", inputs);
|
//console.log("DigitalInputs", inputs);
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<div className="bg-white text-gray-900 dark:bg-gray-900 dark:text-gray-100 shadow-md border border-gray-200 dark:border-gray-700 p-3 rounded-lg w-full laptop:p-1 xl:p-1">
|
<div className="text-[var(--color-fg)] bg-[var(--color-surface)] dark:bg-[var(--color-surface)] shadow-sm border border-[var(--color-border)] p-3 rounded-lg w-full laptop:p-1 xl:p-1">
|
||||||
<h2 className="laptop:text-sm md:text-base 2xl:text-lg font-bold mb-3 flex items-center">
|
<h2 className="laptop:text-sm md:text-base 2xl:text-lg font-bold mb-3 flex items-center">
|
||||||
<Icon
|
<Icon
|
||||||
icon={inputIcon}
|
icon={inputIcon}
|
||||||
@@ -38,19 +38,19 @@ export default function DigitalInputsWidget({
|
|||||||
/>
|
/>
|
||||||
Meldungseingänge {inputRange.start + 1} – {inputRange.end}
|
Meldungseingänge {inputRange.start + 1} – {inputRange.end}
|
||||||
</h2>
|
</h2>
|
||||||
<table className="w-full text-xs laptop:text-[10px] xl:text-xs 2xl:text-sm border-collapse bg-white dark:bg-gray-900">
|
<table className="w-full text-xs laptop:text-[10px] xl:text-xs 2xl:text-sm border-collapse bg-[var(--color-surface)]">
|
||||||
<thead className="bg-white text-gray-900 dark:bg-gray-900 dark:text-gray-100 border-b">
|
<thead className="bg-[var(--color-surface-alt)]/60 dark:bg-[var(--color-surface-alt)]/25 text-[var(--color-fg)] border-b border-[var(--color-border)]">
|
||||||
<tr>
|
<tr>
|
||||||
<th className="px-1 py-1 text-left bg-white text-gray-900 dark:bg-gray-900 dark:text-gray-100">
|
<th className="px-1 py-1 text-left bg-[var(--color-surface)] text-[var(--color-fg)]">
|
||||||
Eingang
|
Eingang
|
||||||
</th>
|
</th>
|
||||||
<th className="px-1 py-1 text-left bg-white text-gray-900 dark:bg-gray-900 dark:text-gray-100">
|
<th className="px-1 py-1 text-left bg-[var(--color-surface)] text-[var(--color-fg)]">
|
||||||
Zustand
|
Zustand
|
||||||
</th>
|
</th>
|
||||||
<th className="px-1 py-1 text-left bg-white text-gray-900 dark:bg-gray-900 dark:text-gray-100">
|
<th className="px-1 py-1 text-left bg-[var(--color-surface)] text-[var(--color-fg)]">
|
||||||
Bezeichnung
|
Bezeichnung
|
||||||
</th>
|
</th>
|
||||||
<th className="px-1 py-1 text-left bg-white text-gray-900 dark:bg-gray-900 dark:text-gray-100">
|
<th className="px-1 py-1 text-left bg-[var(--color-surface)] text-[var(--color-fg)]">
|
||||||
Aktion
|
Aktion
|
||||||
</th>
|
</th>
|
||||||
</tr>
|
</tr>
|
||||||
@@ -59,9 +59,9 @@ export default function DigitalInputsWidget({
|
|||||||
{inputs.map((input) => (
|
{inputs.map((input) => (
|
||||||
<tr
|
<tr
|
||||||
key={input.id}
|
key={input.id}
|
||||||
className="border-b hover:bg-gray-100 dark:hover:bg-gray-800"
|
className="border-b border-[var(--color-border)] hover:bg-[var(--color-surface-alt)]/70 dark:hover:bg-[var(--color-surface-alt)]/30 transition"
|
||||||
>
|
>
|
||||||
<td className="px-1 py-0 bg-white text-gray-900 dark:bg-gray-900 dark:text-gray-100">
|
<td className="px-1 py-0 bg-[var(--color-surface)] text-[var(--color-fg)]">
|
||||||
<div className="flex items-center gap-1 ">
|
<div className="flex items-center gap-1 ">
|
||||||
<Icon
|
<Icon
|
||||||
icon={loginIcon}
|
icon={loginIcon}
|
||||||
@@ -70,7 +70,7 @@ export default function DigitalInputsWidget({
|
|||||||
{input.id}
|
{input.id}
|
||||||
</div>
|
</div>
|
||||||
</td>
|
</td>
|
||||||
<td className="px-1 py-1 bg-white text-gray-900 dark:bg-gray-900 dark:text-gray-100">
|
<td className="px-1 py-1 bg-[var(--color-surface)] text-[var(--color-fg)]">
|
||||||
{input.eingangOffline ? (
|
{input.eingangOffline ? (
|
||||||
<div className="relative group inline-block">
|
<div className="relative group inline-block">
|
||||||
<span className="text-red-500 sm:text-sm md:text-base lg:text-lg xl:text-xl 2xl:text-2xl laptop:text-sm ">
|
<span className="text-red-500 sm:text-sm md:text-base lg:text-lg xl:text-xl 2xl:text-2xl laptop:text-sm ">
|
||||||
@@ -91,10 +91,10 @@ export default function DigitalInputsWidget({
|
|||||||
</div>
|
</div>
|
||||||
)}
|
)}
|
||||||
</td>
|
</td>
|
||||||
<td className="px-1 py-1 bg-white text-gray-900 dark:bg-gray-900 dark:text-gray-100">
|
<td className="px-1 py-1 bg-[var(--color-surface)] text-[var(--color-fg)]">
|
||||||
{input.label}
|
{input.label}
|
||||||
</td>
|
</td>
|
||||||
<td className="px-1 py-1 bg-white text-gray-900 dark:bg-gray-900 dark:text-gray-100">
|
<td className="px-1 py-1 bg-[var(--color-surface)] text-[var(--color-fg)]">
|
||||||
<Icon
|
<Icon
|
||||||
icon={settingsIcon}
|
icon={settingsIcon}
|
||||||
className="text-gray-400 text-base laptop:text-sm xl:text-sm 2xl:text-lg cursor-pointer dark:text-gray-300 dark:hover:text-white"
|
className="text-gray-400 text-base laptop:text-sm xl:text-sm 2xl:text-lg cursor-pointer dark:text-gray-300 dark:hover:text-white"
|
||||||
|
|||||||
@@ -182,36 +182,40 @@ 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">
|
||||||
|
Bezeichnung:
|
||||||
|
</span>
|
||||||
</div>
|
</div>
|
||||||
<div>
|
<div>
|
||||||
<input
|
<input
|
||||||
type="text"
|
type="text"
|
||||||
value={label}
|
value={label}
|
||||||
onChange={(e) => setLabel(e.target.value)}
|
onChange={(e) => setLabel(e.target.value)}
|
||||||
className="border border-gray-300 rounded px-2 py-1 w-full"
|
className="border border-base rounded px-2 py-1 w-full bg-[var(--color-surface-alt)] text-fg"
|
||||||
maxLength={32}
|
maxLength={32}
|
||||||
/>
|
/>
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
<div>
|
<div>
|
||||||
<span className="font-normal">Invertierung:</span>
|
<span className="font-normal text-fg-secondary">
|
||||||
|
Invertierung:
|
||||||
|
</span>
|
||||||
</div>
|
</div>
|
||||||
<div className="flex items-center gap-2">
|
<div className="flex items-center gap-2">
|
||||||
<button
|
<button
|
||||||
@@ -219,21 +223,20 @@ export default function InputModal({
|
|||||||
role="switch"
|
role="switch"
|
||||||
aria-checked={invertiert}
|
aria-checked={invertiert}
|
||||||
onClick={() => setInvertiert(!invertiert)}
|
onClick={() => setInvertiert(!invertiert)}
|
||||||
className={`relative inline-flex h-6 w-11 items-center rounded-full transition-colors duration-200 ${
|
className={`relative inline-flex h-6 w-11 items-center rounded-full border border-base transition-colors duration-200 ${
|
||||||
invertiert ? "bg-littwin-blue" : "bg-gray-300"
|
invertiert ? "bg-littwin-blue" : "bg-base-muted"
|
||||||
}`}
|
}`}
|
||||||
>
|
>
|
||||||
<span
|
<span
|
||||||
className={`inline-block h-4 w-4 transform rounded-full bg-white transition-transform duration-200 ${
|
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-6" : "translate-x-1"
|
invertiert ? "translate-x-5" : "translate-x-0"
|
||||||
}`}
|
}`}
|
||||||
/>
|
/>
|
||||||
</button>
|
</button>
|
||||||
<span>{invertiert ? "Ein" : "Aus"}</span>
|
<span className="text-fg">{invertiert ? "Ein" : "Aus"}</span>
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
<div>
|
<div>
|
||||||
<span className="font-normal">Filterzeit:</span>
|
<span className="font-normal text-fg-secondary">Filterzeit:</span>
|
||||||
</div>
|
</div>
|
||||||
<div className="relative">
|
<div className="relative">
|
||||||
<input
|
<input
|
||||||
@@ -247,17 +250,15 @@ export default function InputModal({
|
|||||||
setTimeFilter(val);
|
setTimeFilter(val);
|
||||||
}
|
}
|
||||||
}}
|
}}
|
||||||
className="border border-gray-300 rounded px-2 py-1 pr-10 w-full text-right"
|
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"
|
title="Maximal 2000 ms erlaubt"
|
||||||
/>
|
/>
|
||||||
|
<span className="absolute right-2 top-1/2 transform -translate-y-1/2 text-muted text-sm">
|
||||||
<span className="absolute right-2 top-1/2 transform -translate-y-1/2 text-gray-500 text-sm">
|
|
||||||
ms
|
ms
|
||||||
</span>
|
</span>
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
<div>
|
<div>
|
||||||
<span className="font-normal">Gewichtung:</span>
|
<span className="font-normal text-fg-secondary">Gewichtung:</span>
|
||||||
</div>
|
</div>
|
||||||
<div>
|
<div>
|
||||||
<input
|
<input
|
||||||
@@ -271,43 +272,43 @@ export default function InputModal({
|
|||||||
setWeighting(val);
|
setWeighting(val);
|
||||||
}
|
}
|
||||||
}}
|
}}
|
||||||
className="border border-gray-300 rounded px-2 py-1 w-full text-right"
|
className="border border-base rounded px-2 py-1 w-full text-right bg-[var(--color-surface-alt)] text-fg"
|
||||||
title="Maximal 1000 erlaubt"
|
title="Maximal 1000 erlaubt"
|
||||||
/>
|
/>
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
<div className="relative group inline-block">
|
<div className="relative group inline-block">
|
||||||
<span className="font-normal">Out of Service:</span>
|
<span className="font-normal text-fg-secondary">
|
||||||
|
Out of Service:
|
||||||
|
</span>
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
<div className="flex items-center gap-2">
|
<div className="flex items-center gap-2">
|
||||||
<button
|
<button
|
||||||
type="button"
|
type="button"
|
||||||
role="switch"
|
role="switch"
|
||||||
aria-checked={!!eingangOffline}
|
aria-checked={!!eingangOffline}
|
||||||
onClick={() => setEingangOffline(eingangOffline ? 0 : 1)}
|
onClick={() => setEingangOffline(eingangOffline ? 0 : 1)}
|
||||||
className={`relative inline-flex h-6 w-11 items-center rounded-full transition-colors duration-200 ${
|
className={`relative inline-flex h-6 w-11 items-center rounded-full border border-base transition-colors duration-200 ${
|
||||||
eingangOffline ? "bg-littwin-blue" : "bg-gray-300"
|
eingangOffline ? "bg-littwin-blue" : "bg-base-muted"
|
||||||
}`}
|
}`}
|
||||||
>
|
>
|
||||||
<span
|
<span
|
||||||
className={`inline-block h-4 w-4 transform rounded-full bg-white transition-transform duration-200 ${
|
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-6" : "translate-x-1"
|
eingangOffline ? "translate-x-5" : "translate-x-0"
|
||||||
}`}
|
}`}
|
||||||
/>
|
/>
|
||||||
</button>
|
</button>
|
||||||
<span>{eingangOffline ? "Ein" : "Aus"}</span>
|
<span className="text-fg">{eingangOffline ? "Ein" : "Aus"}</span>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
|
</div>
|
||||||
<div className="mt-6 flex justify-end gap-2">
|
<footer className="px-6 py-4 border-t border-base flex justify-end">
|
||||||
<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>
|
||||||
|
<div>
|
||||||
<input
|
<input
|
||||||
type="text"
|
type="text"
|
||||||
value={label}
|
value={label}
|
||||||
onChange={(e) => setLabel(e.target.value)}
|
onChange={(e) => setLabel(e.target.value)}
|
||||||
className="w-full border border-gray-300 rounded px-3 py-2"
|
className="border border-base rounded px-2 py-1 w-full bg-[var(--color-surface-alt)] text-fg"
|
||||||
placeholder="z. B. Licht Relais 1"
|
placeholder="z. B. Licht Relais 1"
|
||||||
/>
|
/>
|
||||||
</div>
|
</div>
|
||||||
|
</div>
|
||||||
{errorMsg && <p className="text-red-600 text-sm mb-2">{errorMsg}</p>}
|
{errorMsg && <p className="text-red-600 text-sm mb-2">{errorMsg}</p>}
|
||||||
|
</div>
|
||||||
<div className="flex justify-end gap-2 mt-6">
|
<footer className="px-6 py-4 border-t border-base flex justify-end">
|
||||||
<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>
|
||||||
);
|
);
|
||||||
|
|||||||
@@ -66,7 +66,7 @@ export default function DigitalOutputsWidget({
|
|||||||
};
|
};
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<div className="bg-white text-gray-900 dark:bg-gray-900 dark:text-gray-100 shadow-md border border-gray-200 dark:border-gray-700 p-3 rounded-lg w-full h-fit max-h-[400px] overflow-auto">
|
<div className="bg-[var(--color-surface)] text-[var(--color-fg)] shadow-md border border-base p-3 rounded-lg w-full h-fit max-h-[400px] overflow-auto">
|
||||||
<h2 className="laptop:text-sm md:text-base 2xl:text-lg font-bold mb-3 flex items-center">
|
<h2 className="laptop:text-sm md:text-base 2xl:text-lg font-bold mb-3 flex items-center">
|
||||||
<Icon
|
<Icon
|
||||||
icon={outputIcon}
|
icon={outputIcon}
|
||||||
@@ -74,19 +74,19 @@ export default function DigitalOutputsWidget({
|
|||||||
/>
|
/>
|
||||||
Schaltausgänge
|
Schaltausgänge
|
||||||
</h2>
|
</h2>
|
||||||
<table className="w-full text-xs laptop:text-[10px] xl:text-xs 2xl:text-sm border-collapse bg-white dark:bg-gray-900 rounded-lg">
|
<table className="w-full text-xs laptop:text-[10px] xl:text-xs 2xl:text-sm border-collapse bg-[var(--color-surface)] rounded-lg">
|
||||||
<thead className="bg-white text-gray-900 dark:bg-gray-900 dark:text-gray-100 border-b">
|
<thead className="bg-[var(--color-surface)] text-[var(--color-fg)] border-b border-base">
|
||||||
<tr>
|
<tr>
|
||||||
<th className="px-1 py-1 text-left bg-white text-gray-900 dark:bg-gray-900 dark:text-gray-100">
|
<th className="px-1 py-1 text-left bg-[var(--color-surface)] text-[var(--color-fg)]">
|
||||||
Ausgang
|
Ausgang
|
||||||
</th>
|
</th>
|
||||||
<th className="px-1 py-1 text-left bg-white text-gray-900 dark:bg-gray-900 dark:text-gray-100">
|
<th className="px-1 py-1 text-left bg-[var(--color-surface)] text-[var(--color-fg)]">
|
||||||
Bezeichnung
|
Bezeichnung
|
||||||
</th>
|
</th>
|
||||||
<th className="px-1 py-1 text-left bg-white text-gray-900 dark:bg-gray-900 dark:text-gray-100">
|
<th className="px-1 py-1 text-left bg-[var(--color-surface)] text-[var(--color-fg)]">
|
||||||
Schalter
|
Schalter
|
||||||
</th>
|
</th>
|
||||||
<th className="px-1 py-1 text-left bg-white text-gray-900 dark:bg-gray-900 dark:text-gray-100">
|
<th className="px-1 py-1 text-left bg-[var(--color-surface)] text-[var(--color-fg)]">
|
||||||
Aktion
|
Aktion
|
||||||
</th>
|
</th>
|
||||||
</tr>
|
</tr>
|
||||||
@@ -95,33 +95,39 @@ export default function DigitalOutputsWidget({
|
|||||||
{digitalOutputs.map((output) => (
|
{digitalOutputs.map((output) => (
|
||||||
<tr
|
<tr
|
||||||
key={output.id}
|
key={output.id}
|
||||||
className="border-b hover:bg-gray-100 dark:hover:bg-gray-800"
|
className="border-b border-base hover:bg-[var(--color-surface-alt)] transition-colors"
|
||||||
>
|
>
|
||||||
<td className="flex items-center px-1 py-1 bg-white text-gray-900 dark:bg-gray-900 dark:text-gray-100">
|
<td className="flex items-center px-1 py-1 bg-[var(--color-surface)] text-[var(--color-fg)]">
|
||||||
<Icon
|
<Icon
|
||||||
icon={outputIcon}
|
icon={outputIcon}
|
||||||
className="text-gray-600 mr-1 text-base"
|
className="text-[var(--color-muted)] mr-1 text-base"
|
||||||
/>
|
/>
|
||||||
{output.id}
|
{output.id}
|
||||||
</td>
|
</td>
|
||||||
<td className="px-1 py-1 bg-white text-gray-900 dark:bg-gray-900 dark:text-gray-100">
|
<td className="px-1 py-1 bg-[var(--color-surface)] text-[var(--color-fg)]">
|
||||||
{output.label}
|
{output.label}
|
||||||
</td>
|
</td>
|
||||||
<td className="px-1 py-1 bg-white text-gray-900 dark:bg-gray-900 dark:text-gray-100">
|
<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-littwin-blue"
|
|
||||||
: "text-gray-500 scale-x-[-1]"
|
|
||||||
} dark:hover:text-littwin-blue`}
|
|
||||||
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-white text-gray-900 dark:bg-gray-900 dark:text-gray-100">
|
<td className="px-1 py-1 bg-[var(--color-surface)] text-[var(--color-fg)]">
|
||||||
<Icon
|
<Icon
|
||||||
icon={settingsIcon}
|
icon={settingsIcon}
|
||||||
className="text-gray-400 text-base cursor-pointer dark:text-gray-300 dark:hover:text-white"
|
className="text-[var(--color-muted)] text-base cursor-pointer hover:text-[var(--color-fg)]"
|
||||||
onClick={() => openOutputModal(output)}
|
onClick={() => openOutputModal(output)}
|
||||||
/>
|
/>
|
||||||
</td>
|
</td>
|
||||||
|
|||||||
@@ -119,19 +119,23 @@ function KabelueberwachungView() {
|
|||||||
return (
|
return (
|
||||||
<div>
|
<div>
|
||||||
<div className="mb-4">
|
<div className="mb-4">
|
||||||
{[1, 2, 3, 4].map((rack) => (
|
{[1, 2, 3, 4].map((rack) => {
|
||||||
|
const isActive = Number(activeRack) === Number(rack);
|
||||||
|
return (
|
||||||
<button
|
<button
|
||||||
key={rack}
|
key={rack}
|
||||||
onClick={() => changeRack(rack)}
|
onClick={() => changeRack(rack)}
|
||||||
className={`mr-2 ${
|
aria-pressed={isActive}
|
||||||
Number(activeRack) === Number(rack)
|
className={`mr-2 px-2 py-1 rounded-sm text-sm font-medium transition-colors focus:outline-none focus:ring-2 focus:ring-accent/50 ${
|
||||||
? "bg-littwin-blue text-white p-1 rounded-sm"
|
isActive
|
||||||
: "bg-gray-300 p-1 text-sm"
|
? "btn-primary"
|
||||||
|
: "btn-muted text-fg opacity-90 hover:opacity-100"
|
||||||
}`}
|
}`}
|
||||||
>
|
>
|
||||||
Rack {rack}
|
Rack {rack}
|
||||||
</button>
|
</button>
|
||||||
))}
|
);
|
||||||
|
})}
|
||||||
</div>
|
</div>
|
||||||
<div className="flex flex-row space-x-8 xl:space-x-0 2xl:space-x-8 qhd:space-x-16 ml-[5%] mt-[5%]">
|
<div className="flex flex-row space-x-8 xl:space-x-0 2xl:space-x-8 qhd:space-x-16 ml-[5%] mt-[5%]">
|
||||||
{(
|
{(
|
||||||
|
|||||||
@@ -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,34 +269,25 @@ 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 */}
|
|
||||||
<div
|
|
||||||
style={{
|
|
||||||
visibility: chartTitle === "Messkurve" ? "visible" : "hidden",
|
|
||||||
}}
|
|
||||||
>
|
|
||||||
<DateRangePicker />
|
<DateRangePicker />
|
||||||
</div>
|
{!isMeldungen && (
|
||||||
|
<>
|
||||||
{/* DIA0-DIA2 Dropdown - Platz reservieren, aber ausblenden wenn Meldungen */}
|
|
||||||
<div
|
|
||||||
style={{
|
|
||||||
visibility: chartTitle === "Messkurve" ? "visible" : "hidden",
|
|
||||||
}}
|
|
||||||
>
|
|
||||||
<Listbox
|
<Listbox
|
||||||
value={selectedMode}
|
value={selectedMode}
|
||||||
onChange={(value) => {
|
onChange={(value) => {
|
||||||
@@ -317,39 +296,29 @@ const IsoChartActionBar = 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"
|
||||||
: ""
|
: ""
|
||||||
}`
|
}`
|
||||||
}
|
}
|
||||||
@@ -357,30 +326,33 @@ const IsoChartActionBar = 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 as "DIA0" | "DIA1" | "DIA2"]
|
||||||
}
|
}
|
||||||
</Listbox.Option>
|
</Listbox.Option>
|
||||||
))}
|
))}
|
||||||
</Listbox.Options>
|
</Listbox.Options>
|
||||||
</div>
|
</div>
|
||||||
</Listbox>
|
</Listbox>
|
||||||
</div>
|
|
||||||
|
|
||||||
{/* Dropdown für Auswahl zwischen "Messkurve" und "Meldungen" - immer anzeigen */}
|
|
||||||
{/* Dropdown für Auswahl zwischen "Messkurve" und "Meldungen" entfernt */}
|
|
||||||
|
|
||||||
{/* Daten laden Button – lädt je nach Ansicht Messkurve oder Meldungen */}
|
|
||||||
<button
|
<button
|
||||||
style={{
|
|
||||||
visibility: chartTitle === "Messkurve" ? "visible" : "hidden",
|
|
||||||
}}
|
|
||||||
onClick={handleFetchData}
|
onClick={handleFetchData}
|
||||||
className="px-4 py-1 bg-littwin-blue text-white rounded text-sm"
|
className="btn-primary h-8 font-medium px-3"
|
||||||
|
type="button"
|
||||||
>
|
>
|
||||||
Daten laden
|
Daten laden
|
||||||
</button>
|
</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",
|
|
||||||
display: "flex",
|
|
||||||
gap: "0.75rem",
|
|
||||||
}}
|
|
||||||
>
|
|
||||||
{/* Fullscreen-Button */}
|
|
||||||
<button
|
<button
|
||||||
onClick={toggleFullScreen}
|
onClick={toggleFullScreen}
|
||||||
style={{
|
className="icon-btn"
|
||||||
background: "transparent",
|
aria-label={isFullScreen ? "Vollbild verlassen" : "Vollbild"}
|
||||||
border: "none",
|
type="button"
|
||||||
fontSize: "1.5rem",
|
|
||||||
cursor: "pointer",
|
|
||||||
}}
|
|
||||||
>
|
>
|
||||||
<i
|
<i
|
||||||
className={
|
className={
|
||||||
isFullScreen ? "bi bi-fullscreen-exit" : "bi bi-arrows-fullscreen"
|
isFullScreen
|
||||||
|
? "bi bi-fullscreen-exit"
|
||||||
|
: "bi bi-arrows-fullscreen"
|
||||||
}
|
}
|
||||||
></i>
|
/>
|
||||||
</button>
|
</button>
|
||||||
|
|
||||||
{/* Schließen-Button */}
|
|
||||||
<button
|
<button
|
||||||
onClick={handleClose}
|
onClick={handleClose}
|
||||||
|
className="icon-btn"
|
||||||
|
aria-label="Schließen"
|
||||||
|
type="button"
|
||||||
|
>
|
||||||
|
<i
|
||||||
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>
|
||||||
|
<div className="absolute top-2 right-28">
|
||||||
{/* 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,27 +224,28 @@ 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">
|
||||||
|
<thead>
|
||||||
<tr>
|
<tr>
|
||||||
<th className="p-2 border">Prio</th>
|
<th style={{ width: "60px" }}>Prio</th>
|
||||||
<th className="p-2 border">Zeitstempel</th>
|
<th style={{ minWidth: "180px" }}>Zeitstempel</th>
|
||||||
<th className="p-2 border">Quelle</th>
|
<th style={{ minWidth: "140px" }}>Quelle</th>
|
||||||
<th className="p-2 border">Meldung</th>
|
<th style={{ minWidth: "260px" }}>Meldung</th>
|
||||||
<th className="p-2 border">Status</th>
|
<th style={{ minWidth: "120px" }}>Status</th>
|
||||||
</tr>
|
</tr>
|
||||||
</thead>
|
</thead>
|
||||||
<tbody>
|
<tbody>
|
||||||
{filteredMessages.map((msg, index) => (
|
{filteredMessages.map((msg, index) => (
|
||||||
<tr key={index} className="hover:bg-gray-200">
|
<tr key={index}>
|
||||||
<td className="border p-2">
|
<td>
|
||||||
<div
|
<div
|
||||||
className="w-4 h-4 rounded"
|
className="prio-dot"
|
||||||
style={{ backgroundColor: msg.c }}
|
style={{ backgroundColor: msg.c }}
|
||||||
></div>
|
/>
|
||||||
</td>
|
</td>
|
||||||
<td className="border p-2">
|
<td>
|
||||||
{new Date(msg.t).toLocaleString("de-DE", {
|
{new Date(msg.t).toLocaleString("de-DE", {
|
||||||
day: "2-digit",
|
day: "2-digit",
|
||||||
month: "2-digit",
|
month: "2-digit",
|
||||||
@@ -254,14 +255,17 @@ const Report: React.FC<ReportProps> = ({ moduleType, autoLoad = true }) => {
|
|||||||
second: "2-digit",
|
second: "2-digit",
|
||||||
})}
|
})}
|
||||||
</td>
|
</td>
|
||||||
<td className="border p-2">{msg.i}</td>
|
<td>{msg.i}</td>
|
||||||
<td className="border p-2">{msg.m}</td>
|
<td className="truncate max-w-[22ch]" title={msg.m}>
|
||||||
<td className="border p-2">{msg.v}</td>
|
{msg.m}
|
||||||
|
</td>
|
||||||
|
<td>{msg.v}</td>
|
||||||
</tr>
|
</tr>
|
||||||
))}
|
))}
|
||||||
</tbody>
|
</tbody>
|
||||||
</table>
|
</table>
|
||||||
</div>
|
</div>
|
||||||
|
</div>
|
||||||
)}
|
)}
|
||||||
|
|
||||||
{/* <div className="mt-4 text-sm text-gray-500 text-center mt-4">
|
{/* <div className="mt-4 text-sm text-gray-500 text-center mt-4">
|
||||||
|
|||||||
@@ -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,95 +66,116 @@ 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",
|
|
||||||
display: "flex",
|
|
||||||
gap: "0.75rem",
|
|
||||||
}}
|
|
||||||
>
|
|
||||||
<button
|
<button
|
||||||
onClick={toggleFullScreen}
|
onClick={toggleFullScreen}
|
||||||
style={{
|
className="icon-btn"
|
||||||
background: "transparent",
|
aria-label={isFullScreen ? "Vollbild verlassen" : "Vollbild"}
|
||||||
border: "none",
|
type="button"
|
||||||
fontSize: "1.5rem",
|
|
||||||
cursor: "pointer",
|
|
||||||
}}
|
|
||||||
>
|
>
|
||||||
<i
|
<i
|
||||||
className={
|
className={
|
||||||
isFullScreen ? "bi bi-fullscreen-exit" : "bi bi-arrows-fullscreen"
|
isFullScreen
|
||||||
|
? "bi bi-fullscreen-exit"
|
||||||
|
: "bi bi-arrows-fullscreen"
|
||||||
}
|
}
|
||||||
></i>
|
/>
|
||||||
</button>
|
</button>
|
||||||
<button
|
<button
|
||||||
onClick={handleClose}
|
onClick={handleClose}
|
||||||
|
className="icon-btn"
|
||||||
|
aria-label="Schließen"
|
||||||
|
type="button"
|
||||||
|
>
|
||||||
|
<i
|
||||||
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>
|
||||||
{/* Content */}
|
<div className="flex flex-col flex-1 p-3 gap-3">
|
||||||
<div className="flex flex-col h-full">
|
{/* Toolbar */}
|
||||||
<h3 className="text-lg font-semibold mb-1">KVz Zustände & Meldungen</h3>
|
<div className="w-full flex flex-wrap items-center gap-4">
|
||||||
|
<div className="flex items-center gap-2">
|
||||||
{/* LED Bereich */}
|
<span className="text-xs font-semibold opacity-80 select-none text-fg-secondary">
|
||||||
<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,40 +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",
|
|
||||||
}}
|
|
||||||
className="flex items-center space-x-2"
|
|
||||||
>
|
|
||||||
<button
|
<button
|
||||||
onClick={handleStartRSL}
|
onClick={handleStartRSL}
|
||||||
className="px-4 py-1 bg-littwin-blue text-white rounded text-sm whitespace-nowrap"
|
className="btn-primary h-8 font-medium px-3"
|
||||||
disabled={isLoading || rslRunning}
|
disabled={isLoading || rslRunning}
|
||||||
|
type="button"
|
||||||
>
|
>
|
||||||
{rslRunning ? "RSL läuft..." : "RSL Messung starten"}
|
{rslRunning ? "RSL läuft…" : "RSL Messung starten"}
|
||||||
</button>
|
</button>
|
||||||
<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-3"
|
||||||
disabled={rslRunning}
|
disabled={rslRunning}
|
||||||
|
type="button"
|
||||||
>
|
>
|
||||||
Daten laden
|
Daten laden
|
||||||
</button>
|
</button>
|
||||||
</div>
|
</div>
|
||||||
|
)}
|
||||||
|
{isMeldungen && (
|
||||||
|
<button
|
||||||
|
onClick={handleFetchData}
|
||||||
|
className="btn-primary h-8 font-medium px-4"
|
||||||
|
disabled={rslRunning}
|
||||||
|
type="button"
|
||||||
|
>
|
||||||
|
Anzeigen
|
||||||
|
</button>
|
||||||
|
)}
|
||||||
</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… (noch {TOTAL_DURATION - rslProgress}s)
|
Bitte warten…{" "}
|
||||||
|
{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",
|
|
||||||
display: "flex",
|
|
||||||
gap: "0.75rem",
|
|
||||||
}}
|
|
||||||
>
|
|
||||||
{/* Fullscreen-Button */}
|
|
||||||
<button
|
<button
|
||||||
onClick={toggleFullScreen}
|
onClick={toggleFullScreen}
|
||||||
style={{
|
className="icon-btn"
|
||||||
background: "transparent",
|
aria-label={isFullScreen ? "Vollbild verlassen" : "Vollbild"}
|
||||||
border: "none",
|
type="button"
|
||||||
fontSize: "1.5rem",
|
|
||||||
cursor: "pointer",
|
|
||||||
}}
|
|
||||||
>
|
>
|
||||||
<i
|
<i
|
||||||
className={
|
className={
|
||||||
isFullScreen ? "bi bi-fullscreen-exit" : "bi bi-arrows-fullscreen"
|
isFullScreen
|
||||||
|
? "bi bi-fullscreen-exit"
|
||||||
|
: "bi bi-arrows-fullscreen"
|
||||||
}
|
}
|
||||||
></i>
|
/>
|
||||||
</button>
|
</button>
|
||||||
|
|
||||||
{/* Schließen-Button */}
|
|
||||||
<button
|
<button
|
||||||
onClick={handleClose}
|
onClick={handleClose}
|
||||||
|
className="icon-btn"
|
||||||
|
aria-label="Schließen"
|
||||||
|
type="button"
|
||||||
|
>
|
||||||
|
<i
|
||||||
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>
|
||||||
|
<div className="absolute top-2 right-28">
|
||||||
{/* 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,127 +1,149 @@
|
|||||||
|
"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] : [];
|
||||||
|
|
||||||
// 📌 Referenz setzen (nutzt Slotnummer + 1 für die API)
|
const isMeldungen = chartTitle === "Meldungen";
|
||||||
|
|
||||||
|
// Progress for running TDR measurement
|
||||||
|
const TDR_TOTAL_DURATION = parseInt(
|
||||||
|
process.env.NEXT_PUBLIC_TDR_DURATION_SECONDS || "120",
|
||||||
|
10
|
||||||
|
);
|
||||||
|
const [tdrRunning, setTdrRunning] = useState(false);
|
||||||
|
const [tdrProgress, setTdrProgress] = useState(0);
|
||||||
|
|
||||||
|
useEffect(() => {
|
||||||
|
if (!tdrRunning) return;
|
||||||
|
setTdrProgress(0);
|
||||||
|
const started = Date.now();
|
||||||
|
const interval = setInterval(() => {
|
||||||
|
const elapsed = Math.floor((Date.now() - started) / 1000);
|
||||||
|
if (elapsed >= TDR_TOTAL_DURATION) {
|
||||||
|
setTdrProgress(TDR_TOTAL_DURATION);
|
||||||
|
setTdrRunning(false);
|
||||||
|
clearInterval(interval);
|
||||||
|
} else {
|
||||||
|
setTdrProgress(elapsed);
|
||||||
|
}
|
||||||
|
}, 1000);
|
||||||
|
return () => clearInterval(interval);
|
||||||
|
}, [tdrRunning, TDR_TOTAL_DURATION]);
|
||||||
|
|
||||||
|
const startTdrProgress = () => {
|
||||||
|
setTdrRunning(true);
|
||||||
|
setTdrProgress(0);
|
||||||
|
};
|
||||||
|
|
||||||
|
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;
|
||||||
|
|
||||||
try {
|
|
||||||
const slotNumber = selectedSlot + 1; // Slot ist 0-basiert, API will 1-basiert
|
|
||||||
const isDev = process.env.NEXT_PUBLIC_NODE_ENV === "development";
|
const isDev = process.env.NEXT_PUBLIC_NODE_ENV === "development";
|
||||||
|
try {
|
||||||
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);
|
const isDev = process.env.NEXT_PUBLIC_NODE_ENV === "development";
|
||||||
console.log("📡 CGI URL:", cgiUrl);
|
if (isDev) {
|
||||||
|
await new Promise((r) => setTimeout(r, 150));
|
||||||
const response = await fetch(cgiUrl);
|
startTdrProgress();
|
||||||
|
return;
|
||||||
if (!response.ok) {
|
|
||||||
throw new Error(`CGI-Fehler: ${response.status}`);
|
|
||||||
}
|
}
|
||||||
|
const response = await fetch(cgiUrl);
|
||||||
console.log("✅ TDR Messung gestartet für Slot", selectedSlot);
|
if (!response.ok) throw new Error(`CGI-Fehler: ${response.status}`);
|
||||||
//alert(`✅ TDR Messung für Slot ${selectedSlot + 1} gestartet`);
|
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));
|
||||||
}
|
}
|
||||||
@@ -130,109 +152,160 @@ const TDRChartActionBar: React.FC = () => {
|
|||||||
}, [selectedSlot, dispatch]);
|
}, [selectedSlot, dispatch]);
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<div className="flex justify-between items-center p-2 bg-gray-100 rounded-lg space-x-4">
|
<>
|
||||||
{/* 🧩 Slot-Anzeige (1-basiert für Benutzer) */}
|
<div className="toolbar w-full justify-between flex-wrap">
|
||||||
<div className="text-sm font-semibold">
|
{/* KÜ number left, controls right, like IsoChartActionBar */}
|
||||||
{selectedSlot !== null ? `KÜ ${selectedSlot + 1}` : "Kein KÜ gewählt"}
|
<div className="flex items-center gap-2 pr-4">
|
||||||
|
<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 ? (
|
||||||
|
<>
|
||||||
|
<DateRangePicker />
|
||||||
|
<button
|
||||||
|
type="button"
|
||||||
|
onClick={handleFetchMessages}
|
||||||
|
className="btn-primary h-8 font-medium px-4"
|
||||||
|
disabled={selectedSlot === null}
|
||||||
|
>
|
||||||
|
Anzeigen
|
||||||
|
</button>
|
||||||
|
</>
|
||||||
|
) : (
|
||||||
|
<>
|
||||||
{selectedId !== null && (
|
{selectedId !== null && (
|
||||||
<button
|
<button
|
||||||
onClick={handleSetReference}
|
onClick={handleSetReference}
|
||||||
className="border border-littwin-blue text-littwin-blue bg-white rounded px-3 py-1 text-sm hover:bg-gray-200"
|
type="button"
|
||||||
|
className="btn-primary h-8 px-3 font-medium"
|
||||||
|
disabled={selectedSlot === null}
|
||||||
>
|
>
|
||||||
TDR-Kurve als Referenz speichern
|
TDR-Kurve als Referenz speichern
|
||||||
</button>
|
</button>
|
||||||
)}
|
)}
|
||||||
|
|
||||||
{/* 🚀 TDR starten */}
|
|
||||||
<button
|
<button
|
||||||
onClick={handleStartTDR}
|
onClick={handleStartTDR}
|
||||||
className="px-4 py-1 bg-littwin-blue text-white rounded text-sm whitespace-nowrap "
|
type="button"
|
||||||
disabled={selectedSlot === null}
|
disabled={selectedSlot === null || tdrRunning}
|
||||||
|
className={`btn-primary h-8 px-4 whitespace-nowrap ${
|
||||||
|
tdrRunning ? "opacity-90" : ""
|
||||||
|
}`}
|
||||||
>
|
>
|
||||||
TDR-Messung starten
|
{tdrRunning
|
||||||
|
? `TDR läuft... (${Math.min(
|
||||||
|
100,
|
||||||
|
Math.round((tdrProgress / TDR_TOTAL_DURATION) * 100)
|
||||||
|
)}%)`
|
||||||
|
: "TDR-Messung starten"}
|
||||||
</button>
|
</button>
|
||||||
|
<div className="ml-auto flex-1 min-w-[14rem] max-w-[30rem]">
|
||||||
{/* 🔽 Dropdown für Messungen */}
|
|
||||||
<div className="flex items-center space-x-2">
|
|
||||||
<Listbox
|
<Listbox
|
||||||
value={selectedId}
|
value={selectedId}
|
||||||
onChange={(id) => {
|
onChange={(id) => {
|
||||||
setSelectedId(id);
|
setSelectedId(id);
|
||||||
if (id !== null) {
|
if (id !== null) dispatch(getTDRChartDataByIdThunk(id));
|
||||||
dispatch(getTDRChartDataByIdThunk(id));
|
|
||||||
}
|
|
||||||
}}
|
}}
|
||||||
disabled={idsForSlot.length === 0}
|
disabled={idsForSlot.length === 0}
|
||||||
>
|
>
|
||||||
<div className="relative w-96">
|
<div className="relative w-full">
|
||||||
<Listbox.Button className="w-full border px-2 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 disabled:opacity-50 disabled:cursor-not-allowed">
|
||||||
<span className="whitespace-nowrap overflow-hidden text-ellipsis">
|
<span className="dropdown-text-fix whitespace-nowrap overflow-hidden text-ellipsis pr-2">
|
||||||
{selectedId
|
{selectedId
|
||||||
? (() => {
|
? (() => {
|
||||||
const selected = idsForSlot.find(
|
const selected = idsForSlot.find(
|
||||||
(e) => e.id === selectedId
|
(e) => e.id === selectedId
|
||||||
);
|
);
|
||||||
return selected
|
return selected
|
||||||
? `${new Date(selected.t).toLocaleString("de-DE", {
|
? `${new Date(selected.t).toLocaleString(
|
||||||
|
"de-DE",
|
||||||
|
{
|
||||||
day: "2-digit",
|
day: "2-digit",
|
||||||
month: "2-digit",
|
month: "2-digit",
|
||||||
year: "numeric",
|
year: "numeric",
|
||||||
hour: "2-digit",
|
hour: "2-digit",
|
||||||
minute: "2-digit",
|
minute: "2-digit",
|
||||||
second: "2-digit",
|
second: "2-digit",
|
||||||
})} – Fehlerstelle: ${selected.d} m`
|
}
|
||||||
|
)} – Fehlerstelle: ${selected.d} m`
|
||||||
: "Wähle Messung";
|
: "Wähle Messung";
|
||||||
})()
|
})()
|
||||||
: "Wähle Messung"}
|
: "Wähle Messung"}
|
||||||
</span>
|
</span>
|
||||||
<svg
|
<i className="bi bi-chevron-down text-sm opacity-70" />
|
||||||
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.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-72 overflow-auto text-sm bg-[var(--color-surface)] border border-base rounded-md shadow-lg p-1">
|
||||||
{idsForSlot.map((entry) => (
|
{idsForSlot.map((entry) => {
|
||||||
<Listbox.Option
|
const dateLabel = new Date(entry.t).toLocaleString(
|
||||||
key={entry.id}
|
"de-DE",
|
||||||
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",
|
day: "2-digit",
|
||||||
month: "2-digit",
|
month: "2-digit",
|
||||||
year: "numeric",
|
year: "numeric",
|
||||||
hour: "2-digit",
|
hour: "2-digit",
|
||||||
minute: "2-digit",
|
minute: "2-digit",
|
||||||
second: "2-digit",
|
second: "2-digit",
|
||||||
})}{" "}
|
}
|
||||||
– Fehlerstelle: {entry.d} m
|
);
|
||||||
|
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.Option>
|
||||||
))}
|
);
|
||||||
|
})}
|
||||||
</Listbox.Options>
|
</Listbox.Options>
|
||||||
</div>
|
</div>
|
||||||
</Listbox>
|
</Listbox>
|
||||||
</div>
|
</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 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 TDR mode and slot
|
|
||||||
dispatch(setActiveMode("TDR"));
|
dispatch(setActiveMode("TDR"));
|
||||||
dispatch(setSelectedSlot(slotIndex));
|
dispatch(setSelectedSlot(slotIndex));
|
||||||
|
|
||||||
// Also set slot number for general chart slice
|
|
||||||
dispatch(setSlotNumber(slotIndex));
|
dispatch(setSlotNumber(slotIndex));
|
||||||
|
|
||||||
// Set dates
|
|
||||||
dispatch(setVonDatum(toISO(thirtyDaysAgo)));
|
dispatch(setVonDatum(toISO(thirtyDaysAgo)));
|
||||||
dispatch(setBisDatum(toISO(today)));
|
dispatch(setBisDatum(toISO(today)));
|
||||||
|
|
||||||
// TDR specific settings (if needed)
|
if (chartTitle !== "Messkurve" && chartTitle !== "Meldungen") {
|
||||||
dispatch(setSelectedSlotType("isolationswiderstand"));
|
dispatch(setChartTitle("Messkurve"));
|
||||||
}
|
}
|
||||||
}, [isOpen, slotIndex, dispatch]);
|
// Only run when opened or slot changes or chartTitle invalid
|
||||||
|
}, [isOpen, slotIndex, chartTitle, dispatch]);
|
||||||
|
|
||||||
// **Modal schließen + Redux-Status zurücksetzen**
|
|
||||||
const handleClose = () => {
|
const handleClose = () => {
|
||||||
|
// Reset generic chart slice to DIA1 isolationswiderstand defaults (same pattern as other modals)
|
||||||
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 Dropdowns
|
|
||||||
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",
|
|
||||||
top: "0.625rem",
|
|
||||||
right: "0.625rem",
|
|
||||||
display: "flex",
|
|
||||||
gap: "0.75rem",
|
|
||||||
}}
|
|
||||||
>
|
|
||||||
{/* Fullscreen-Button */}
|
|
||||||
<button
|
<button
|
||||||
onClick={toggleFullScreen}
|
onClick={toggleFullScreen}
|
||||||
style={{
|
className="icon-btn"
|
||||||
background: "transparent",
|
aria-label={isFullScreen ? "Vollbild verlassen" : "Vollbild"}
|
||||||
border: "none",
|
type="button"
|
||||||
fontSize: "1.5rem",
|
|
||||||
cursor: "pointer",
|
|
||||||
}}
|
|
||||||
>
|
>
|
||||||
<i
|
<i
|
||||||
className={
|
className={
|
||||||
isFullScreen ? "bi bi-fullscreen-exit" : "bi bi-arrows-fullscreen"
|
isFullScreen
|
||||||
|
? "bi bi-fullscreen-exit"
|
||||||
|
: "bi bi-arrows-fullscreen"
|
||||||
}
|
}
|
||||||
></i>
|
/>
|
||||||
</button>
|
</button>
|
||||||
|
|
||||||
{/* Schließen-Button */}
|
|
||||||
<button
|
<button
|
||||||
onClick={handleClose}
|
onClick={handleClose}
|
||||||
|
className="icon-btn"
|
||||||
|
aria-label="Schließen"
|
||||||
|
type="button"
|
||||||
|
>
|
||||||
|
<i
|
||||||
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>
|
||||||
|
<div className="absolute top-2 right-28">
|
||||||
{/* 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(
|
||||||
@@ -174,18 +174,6 @@ const Kue705FO: React.FC<Kue705FOProps> = ({
|
|||||||
const openTdrModal = () => {
|
const openTdrModal = () => {
|
||||||
setActiveButton("TDR");
|
setActiveButton("TDR");
|
||||||
setloopTitleText("Entfernung [km]");
|
setloopTitleText("Entfernung [km]");
|
||||||
|
|
||||||
const latestTdrDistanceMeters =
|
|
||||||
Array.isArray(tdmChartData?.[slotIndex]) &&
|
|
||||||
tdmChartData[slotIndex].length > 0 &&
|
|
||||||
typeof tdmChartData[slotIndex][0].d === "number"
|
|
||||||
? tdmChartData[slotIndex][0].d
|
|
||||||
: 0;
|
|
||||||
|
|
||||||
const latestTdrDistance = Number(
|
|
||||||
(latestTdrDistanceMeters / 1000).toFixed(3)
|
|
||||||
);
|
|
||||||
setLoopDisplayValue(latestTdrDistance);
|
|
||||||
setShowTdrModal(true);
|
setShowTdrModal(true);
|
||||||
};
|
};
|
||||||
|
|
||||||
@@ -272,30 +260,16 @@ const Kue705FO: React.FC<Kue705FOProps> = ({
|
|||||||
return () => window.removeEventListener("resize", measure);
|
return () => window.removeEventListener("resize", measure);
|
||||||
}, [moduleName48, scrollFeatureEnabled]);
|
}, [moduleName48, scrollFeatureEnabled]);
|
||||||
//---------------------------------
|
//---------------------------------
|
||||||
const tdmChartData = useSelector(
|
// TDR Distanz wird im Display nicht angezeigt – Daten für Modal werden separat geladen
|
||||||
(state: RootState) => state.tdmChartSlice.data
|
|
||||||
);
|
|
||||||
const latestTdrDistanceMeters =
|
|
||||||
Array.isArray(tdmChartData?.[slotIndex]) &&
|
|
||||||
tdmChartData[slotIndex].length > 0 &&
|
|
||||||
typeof tdmChartData[slotIndex][0].d === "number"
|
|
||||||
? tdmChartData[slotIndex][0].d
|
|
||||||
: 0;
|
|
||||||
|
|
||||||
const latestTdrDistance = Number((latestTdrDistanceMeters / 1000).toFixed(3));
|
|
||||||
//setLoopDisplayValue(latestTdrDistance);
|
|
||||||
|
|
||||||
//---------------------------------
|
//---------------------------------
|
||||||
|
|
||||||
const loopValue =
|
const rslValue =
|
||||||
activeButton === "TDR"
|
typeof schleifenwiderstand === "number"
|
||||||
? latestTdrDistance
|
|
||||||
: typeof schleifenwiderstand === "number"
|
|
||||||
? schleifenwiderstand
|
? schleifenwiderstand
|
||||||
: Number(schleifenwiderstand);
|
: Number(schleifenwiderstand);
|
||||||
|
|
||||||
const { loopDisplayValue, setLoopDisplayValue } = useLoopDisplay(
|
const { loopDisplayValue, setLoopDisplayValue } = useLoopDisplay(
|
||||||
loopValue,
|
rslValue,
|
||||||
activeButton
|
activeButton
|
||||||
);
|
);
|
||||||
|
|
||||||
@@ -303,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
|
||||||
|
|
||||||
@@ -409,7 +383,7 @@ const Kue705FO: React.FC<Kue705FOProps> = ({
|
|||||||
.toFixed(2)
|
.toFixed(2)
|
||||||
.replace(".", ",")} MOhm`}
|
.replace(".", ",")} MOhm`}
|
||||||
</span>
|
</span>
|
||||||
{/* 3. Zeile: Schleifenwert, in Rot bei Schleifenfehler, sonst normal */}
|
{/* 3. Zeile: Schleifenwert (RSL) immer anzeigen, unabhängig von aktivem Button */}
|
||||||
<span
|
<span
|
||||||
className={`whitespace-nowrap block text-[0.65rem] font-semibold ${
|
className={`whitespace-nowrap block text-[0.65rem] font-semibold ${
|
||||||
Number(kueAlarm2?.[slotIndex]) === 1 ? "text-red-500" : ""
|
Number(kueAlarm2?.[slotIndex]) === 1 ? "text-red-500" : ""
|
||||||
|
|||||||
@@ -12,22 +12,48 @@ export default function SlotActivityOverlay({
|
|||||||
const ksz = useAppSelector((s) => s.deviceEvents.ksz);
|
const ksz = useAppSelector((s) => s.deviceEvents.ksz);
|
||||||
const loopStartedAt = useAppSelector((s) => s.deviceEvents.loopStartedAt);
|
const loopStartedAt = useAppSelector((s) => s.deviceEvents.loopStartedAt);
|
||||||
const tdrStartedAt = useAppSelector((s) => s.deviceEvents.tdrStartedAt);
|
const tdrStartedAt = useAppSelector((s) => s.deviceEvents.tdrStartedAt);
|
||||||
const alignmentStartedAt = useAppSelector(
|
const comparisonStartedAt = useAppSelector(
|
||||||
(s) => s.deviceEvents.alignmentStartedAt
|
(s) => s.deviceEvents.comparisonStartedAt
|
||||||
|
);
|
||||||
|
const loopStartedAtBySlot = useAppSelector(
|
||||||
|
(s) => s.deviceEvents.loopStartedAtBySlot
|
||||||
|
);
|
||||||
|
const tdrStartedAtBySlot = useAppSelector(
|
||||||
|
(s) => s.deviceEvents.tdrStartedAtBySlot
|
||||||
|
);
|
||||||
|
const comparisonStartedAtBySlot = useAppSelector(
|
||||||
|
(s) => s.deviceEvents.comparisonStartedAtBySlot
|
||||||
);
|
);
|
||||||
|
|
||||||
const loopActive = Array.isArray(ksx) && ksx[slotIndex] === 1;
|
const loopActive = Array.isArray(ksx) && ksx[slotIndex] === 1;
|
||||||
const tdrActive = Array.isArray(ksy) && ksy[slotIndex] === 1;
|
const tdrActive = Array.isArray(ksy) && ksy[slotIndex] === 1;
|
||||||
const alignActive = Array.isArray(ksz) && ksz[slotIndex] === 1;
|
const comparisonActive = Array.isArray(ksz) && ksz[slotIndex] === 1;
|
||||||
|
|
||||||
|
// Persist whenever arrays change
|
||||||
|
useEffect(() => {
|
||||||
|
try {
|
||||||
|
localStorage.setItem(
|
||||||
|
"deviceEventsTimingsV1",
|
||||||
|
JSON.stringify({
|
||||||
|
loop: loopStartedAtBySlot,
|
||||||
|
tdr: tdrStartedAtBySlot,
|
||||||
|
compare: comparisonStartedAtBySlot,
|
||||||
|
})
|
||||||
|
);
|
||||||
|
} catch (e) {
|
||||||
|
// eslint-disable-next-line no-console
|
||||||
|
console.warn("Failed to persist timings", e);
|
||||||
|
}
|
||||||
|
}, [loopStartedAtBySlot, tdrStartedAtBySlot, comparisonStartedAtBySlot]);
|
||||||
|
|
||||||
// Progress ticker
|
// Progress ticker
|
||||||
const [now, setNow] = useState<number>(Date.now());
|
const [now, setNow] = useState<number>(Date.now());
|
||||||
useEffect(() => {
|
useEffect(() => {
|
||||||
const any = loopActive || tdrActive || alignActive;
|
const any = loopActive || tdrActive || comparisonActive;
|
||||||
if (!any) return;
|
if (!any) return;
|
||||||
const id = setInterval(() => setNow(Date.now()), 1000);
|
const id = setInterval(() => setNow(Date.now()), 1000);
|
||||||
return () => clearInterval(id);
|
return () => clearInterval(id);
|
||||||
}, [loopActive, tdrActive, alignActive]);
|
}, [loopActive, tdrActive, comparisonActive]);
|
||||||
|
|
||||||
const clamp = (v: number, min = 0, max = 1) =>
|
const clamp = (v: number, min = 0, max = 1) =>
|
||||||
Math.max(min, Math.min(max, v));
|
Math.max(min, Math.min(max, v));
|
||||||
@@ -40,10 +66,10 @@ export default function SlotActivityOverlay({
|
|||||||
|
|
||||||
// Durations
|
// Durations
|
||||||
const LOOP_MS = 2 * 60 * 1000; // ~2 min
|
const LOOP_MS = 2 * 60 * 1000; // ~2 min
|
||||||
const TDR_MS = 30 * 1000; // ~30 s
|
const TDR_MS = 110 * 1000; // ~2 min laut die Eigaben
|
||||||
const ALIGN_MS = 10 * 60 * 1000; // ~10 min
|
const ALIGN_MS = 10 * 60 * 1000; // ~10 min
|
||||||
|
|
||||||
if (!loopActive && !tdrActive && !alignActive) return null;
|
if (!loopActive && !tdrActive && !comparisonActive) return null;
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<div className="absolute inset-0 z-20 flex items-center justify-center bg-white/70 backdrop-blur-sm">
|
<div className="absolute inset-0 z-20 flex items-center justify-center bg-white/70 backdrop-blur-sm">
|
||||||
@@ -56,7 +82,8 @@ export default function SlotActivityOverlay({
|
|||||||
<div>
|
<div>
|
||||||
<div className="text-[0.7rem] text-gray-800 mb-1">Schleife</div>
|
<div className="text-[0.7rem] text-gray-800 mb-1">Schleife</div>
|
||||||
{(() => {
|
{(() => {
|
||||||
const { pct } = compute(loopStartedAt, LOOP_MS);
|
const started = loopStartedAtBySlot[slotIndex] ?? loopStartedAt;
|
||||||
|
const { pct } = compute(started, LOOP_MS);
|
||||||
return (
|
return (
|
||||||
<div>
|
<div>
|
||||||
<div className="h-2 w-full bg-gray-200 rounded overflow-hidden">
|
<div className="h-2 w-full bg-gray-200 rounded overflow-hidden">
|
||||||
@@ -77,7 +104,8 @@ export default function SlotActivityOverlay({
|
|||||||
<div>
|
<div>
|
||||||
<div className="text-[0.7rem] text-gray-800 mb-1">TDR</div>
|
<div className="text-[0.7rem] text-gray-800 mb-1">TDR</div>
|
||||||
{(() => {
|
{(() => {
|
||||||
const { pct } = compute(tdrStartedAt, TDR_MS);
|
const started = tdrStartedAtBySlot[slotIndex] ?? tdrStartedAt;
|
||||||
|
const { pct } = compute(started, TDR_MS);
|
||||||
return (
|
return (
|
||||||
<div>
|
<div>
|
||||||
<div className="h-2 w-full bg-gray-200 rounded overflow-hidden">
|
<div className="h-2 w-full bg-gray-200 rounded overflow-hidden">
|
||||||
@@ -94,11 +122,13 @@ export default function SlotActivityOverlay({
|
|||||||
})()}
|
})()}
|
||||||
</div>
|
</div>
|
||||||
)}
|
)}
|
||||||
{alignActive && (
|
{comparisonActive && (
|
||||||
<div>
|
<div>
|
||||||
<div className="text-[0.7rem] text-gray-800 mb-1">Abgleich</div>
|
<div className="text-[0.7rem] text-gray-800 mb-1">Abgleich</div>
|
||||||
{(() => {
|
{(() => {
|
||||||
const { pct } = compute(alignmentStartedAt, ALIGN_MS);
|
const started =
|
||||||
|
comparisonStartedAtBySlot[slotIndex] ?? comparisonStartedAt;
|
||||||
|
const { pct } = compute(started, ALIGN_MS);
|
||||||
return (
|
return (
|
||||||
<div>
|
<div>
|
||||||
<div className="h-2 w-full bg-gray-200 rounded overflow-hidden">
|
<div className="h-2 w-full bg-gray-200 rounded overflow-hidden">
|
||||||
|
|||||||
@@ -1,19 +1,19 @@
|
|||||||
// components/main/kabelueberwachung/kue705FO/hooks/useLoopDisplay.ts
|
// components/main/kabelueberwachung/kue705FO/hooks/useLoopDisplay.ts
|
||||||
import { useEffect, useState } from "react";
|
import { useEffect, useState } from "react";
|
||||||
|
|
||||||
|
// Keeps and updates the loop (RSL) display value only when "Schleife" active.
|
||||||
|
// For ISO or TDR views we do not overwrite the displayed RSL value.
|
||||||
const useLoopDisplay = (
|
const useLoopDisplay = (
|
||||||
schleifenwiderstand: number,
|
rslValue: number,
|
||||||
activeButton: "Schleife" | "TDR" | "ISO"
|
activeButton: "Schleife" | "TDR" | "ISO"
|
||||||
) => {
|
) => {
|
||||||
const [loopDisplayValue, setLoopDisplayValue] =
|
const [loopDisplayValue, setLoopDisplayValue] = useState<number>(rslValue);
|
||||||
useState<number>(schleifenwiderstand);
|
|
||||||
|
|
||||||
useEffect(() => {
|
useEffect(() => {
|
||||||
if (activeButton === "Schleife") {
|
if (activeButton === "Schleife") {
|
||||||
setLoopDisplayValue(schleifenwiderstand);
|
setLoopDisplayValue(rslValue);
|
||||||
}
|
}
|
||||||
// For ISO and TDR, the value is set manually via setLoopDisplayValue
|
}, [rslValue, activeButton]);
|
||||||
}, [schleifenwiderstand, activeButton]);
|
|
||||||
|
|
||||||
return { loopDisplayValue, setLoopDisplayValue };
|
return { loopDisplayValue, setLoopDisplayValue };
|
||||||
};
|
};
|
||||||
|
|||||||
@@ -41,63 +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">
|
<div className="modal-header">
|
||||||
<h2 className="text-base font-bold">Einstellungen KÜ {slot + 1}</h2>
|
<h2 className="text-sm font-semibold tracking-wide text-fg">
|
||||||
<button onClick={onClose} className="text-2xl hover:text-gray-200">
|
Einstellungen KÜ {slot + 1}
|
||||||
<i className="bi bi-x-circle-fill"></i>
|
</h2>
|
||||||
|
<button
|
||||||
|
onClick={onClose}
|
||||||
|
className="icon-btn"
|
||||||
|
aria-label="Modal schließen"
|
||||||
|
type="button"
|
||||||
|
>
|
||||||
|
<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-gray-100 space-x-2 p-2">
|
<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 }) => {
|
||||||
|
const isActive = activeTab === key;
|
||||||
|
return (
|
||||||
<button
|
<button
|
||||||
key={key}
|
key={key}
|
||||||
|
type="button"
|
||||||
onClick={() => setActiveTab(key)}
|
onClick={() => setActiveTab(key)}
|
||||||
className={`px-4 py-1 rounded-t font-bold text-sm ${
|
className={`tab-btn ${isActive ? "tab-btn-active" : ""}`}
|
||||||
activeTab === key
|
aria-current={isActive ? "page" : undefined}
|
||||||
? "bg-white text-littwin-blue"
|
|
||||||
: "text-gray-500 hover:text-black"
|
|
||||||
}`}
|
|
||||||
>
|
>
|
||||||
{label}
|
{label}
|
||||||
</button>
|
</button>
|
||||||
))}
|
);
|
||||||
|
})}
|
||||||
</div>
|
</div>
|
||||||
|
{/* Einheitliche Body-Höhe mit internem Scroll statt dynamischer Außenhöhe */}
|
||||||
<div className="p-4 bg-white rounded-b-md h-[20rem] laptop:h-[24rem] 2xl:h-[30rem] overflow-y-auto">
|
<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}
|
||||||
|
|||||||
@@ -14,22 +14,22 @@ export default function MeldungenTabelle({
|
|||||||
}) {
|
}) {
|
||||||
return (
|
return (
|
||||||
<div className="overflow-auto max-h-[80vh]">
|
<div className="overflow-auto max-h-[80vh]">
|
||||||
<table className="min-w-full border">
|
<table className="min-w-full border border-base table-surface text-fg">
|
||||||
<thead className="bg-white text-gray-900 dark:bg-gray-900 dark:text-gray-100 text-left sticky top-0 z-10">
|
<thead className="text-left sticky top-0 z-10 bg-surface-alt/90 backdrop-blur supports-[backdrop-filter]:bg-surface-alt/70">
|
||||||
<tr>
|
<tr>
|
||||||
<th className="p-2 border bg-white text-gray-900 dark:bg-gray-900 dark:text-gray-100">
|
<th className="p-2 border border-base bg-surface-alt text-fg font-medium">
|
||||||
Prio
|
Prio
|
||||||
</th>
|
</th>
|
||||||
<th className="p-2 border bg-white text-gray-900 dark:bg-gray-900 dark:text-gray-100">
|
<th className="p-2 border border-base bg-surface-alt text-fg font-medium">
|
||||||
Zeitstempel
|
Zeitstempel
|
||||||
</th>
|
</th>
|
||||||
<th className="p-2 border bg-white text-gray-900 dark:bg-gray-900 dark:text-gray-100">
|
<th className="p-2 border border-base bg-surface-alt text-fg font-medium">
|
||||||
Quelle
|
Quelle
|
||||||
</th>
|
</th>
|
||||||
<th className="p-2 border bg-white text-gray-900 dark:bg-gray-900 dark:text-gray-100">
|
<th className="p-2 border border-base bg-surface-alt text-fg font-medium">
|
||||||
Meldung
|
Meldung
|
||||||
</th>
|
</th>
|
||||||
<th className="p-2 border bg-white text-gray-900 dark:bg-gray-900 dark:text-gray-100">
|
<th className="p-2 border border-base bg-surface-alt text-fg font-medium">
|
||||||
Status
|
Status
|
||||||
</th>
|
</th>
|
||||||
</tr>
|
</tr>
|
||||||
@@ -38,15 +38,15 @@ export default function MeldungenTabelle({
|
|||||||
{messages.map((msg, index) => (
|
{messages.map((msg, index) => (
|
||||||
<tr
|
<tr
|
||||||
key={index}
|
key={index}
|
||||||
className="hover:bg-gray-100 dark:hover:bg-gray-700"
|
className="transition-colors hover:bg-surface-alt/60"
|
||||||
>
|
>
|
||||||
<td className="border p-2 bg-white text-gray-900 dark:bg-gray-900 dark:text-gray-100">
|
<td className="border border-base p-2 bg-surface text-fg">
|
||||||
<div
|
<div
|
||||||
className="w-4 h-4 rounded"
|
className="w-4 h-4 rounded"
|
||||||
style={{ backgroundColor: msg.c }}
|
style={{ backgroundColor: msg.c }}
|
||||||
></div>
|
></div>
|
||||||
</td>
|
</td>
|
||||||
<td className="border p-2 bg-white text-gray-900 dark:bg-gray-900 dark:text-gray-100">
|
<td className="border border-base p-2 bg-surface text-fg">
|
||||||
{new Date(msg.t).toLocaleString("de-DE", {
|
{new Date(msg.t).toLocaleString("de-DE", {
|
||||||
day: "2-digit",
|
day: "2-digit",
|
||||||
month: "2-digit",
|
month: "2-digit",
|
||||||
@@ -56,13 +56,13 @@ export default function MeldungenTabelle({
|
|||||||
second: "2-digit",
|
second: "2-digit",
|
||||||
})}
|
})}
|
||||||
</td>
|
</td>
|
||||||
<td className="border p-2 bg-white text-gray-900 dark:bg-gray-900 dark:text-gray-100">
|
<td className="border border-base p-2 bg-surface text-fg">
|
||||||
{msg.i}
|
{msg.i}
|
||||||
</td>
|
</td>
|
||||||
<td className="border p-2 bg-white text-gray-900 dark:bg-gray-900 dark:text-gray-100">
|
<td className="border border-base p-2 bg-surface text-fg">
|
||||||
{msg.m}
|
{msg.m}
|
||||||
</td>
|
</td>
|
||||||
<td className="border p-2 bg-white text-gray-900 dark:bg-gray-900 dark:text-gray-100">
|
<td className="border border-base p-2 bg-surface text-fg">
|
||||||
{msg.v}
|
{msg.v}
|
||||||
</td>
|
</td>
|
||||||
</tr>
|
</tr>
|
||||||
@@ -70,7 +70,7 @@ export default function MeldungenTabelle({
|
|||||||
</tbody>
|
</tbody>
|
||||||
</table>
|
</table>
|
||||||
{messages.length === 0 && (
|
{messages.length === 0 && (
|
||||||
<div className="mt-4 text-center text-gray-500 italic">
|
<div className="mt-4 text-center text-fg-muted italic">
|
||||||
Keine Meldungen im gewählten Zeitraum vorhanden.
|
Keine Meldungen im gewählten Zeitraum vorhanden.
|
||||||
</div>
|
</div>
|
||||||
)}
|
)}
|
||||||
|
|||||||
@@ -60,14 +60,14 @@ export default function MeldungenView() {
|
|||||||
/>
|
/>
|
||||||
<button
|
<button
|
||||||
onClick={() => dispatch(getMessagesThunk({ fromDate, toDate }))}
|
onClick={() => dispatch(getMessagesThunk({ fromDate, toDate }))}
|
||||||
className="bg-littwin-blue text-white px-4 py-2 rounded h-fit"
|
className="btn-primary px-4 py-2 h-fit"
|
||||||
>
|
>
|
||||||
Anzeigen
|
Anzeigen
|
||||||
</button>
|
</button>
|
||||||
|
|
||||||
<Listbox value={sourceFilter} onChange={setSourceFilter}>
|
<Listbox value={sourceFilter} onChange={setSourceFilter}>
|
||||||
<div className="relative ml-6 w-64">
|
<div className="relative ml-6 w-64">
|
||||||
<Listbox.Button className="bg-white text-gray-900 w-full border px-4 py-2 rounded text-left flex justify-between items-center dark:bg-gray-900 dark:text-gray-100">
|
<Listbox.Button className="bg-[var(--color-surface)] text-[var(--color-fg)] w-full border border-base px-4 py-2 rounded text-left flex justify-between items-center">
|
||||||
<span>{sourceFilter}</span>
|
<span>{sourceFilter}</span>
|
||||||
<svg
|
<svg
|
||||||
className="w-5 h-5 text-gray-400"
|
className="w-5 h-5 text-gray-400"
|
||||||
@@ -83,19 +83,19 @@ export default function MeldungenView() {
|
|||||||
/>
|
/>
|
||||||
</svg>
|
</svg>
|
||||||
</Listbox.Button>
|
</Listbox.Button>
|
||||||
<Listbox.Options className="bg-white absolute z-50 mt-1 w-full border rounded dark:bg-gray-900">
|
<Listbox.Options className="bg-[var(--color-surface)] absolute z-50 mt-1 w-full border border-base rounded shadow-sm">
|
||||||
{sources.map((src) => (
|
{sources.map((src) => (
|
||||||
<Listbox.Option
|
<Listbox.Option
|
||||||
key={src}
|
key={src}
|
||||||
value={src}
|
value={src}
|
||||||
className={({ selected, active, disabled }) =>
|
className={({ selected, active, disabled }) =>
|
||||||
`px-4 py-2 cursor-pointer text-gray-900 dark:text-gray-100 ${
|
`px-4 py-2 cursor-pointer text-[var(--color-fg)] transition-colors ${
|
||||||
selected
|
disabled
|
||||||
? "bg-littwin-blue text-white"
|
? "opacity-50 text-[var(--color-muted)] cursor-not-allowed"
|
||||||
|
: selected
|
||||||
|
? "bg-accent text-white"
|
||||||
: active
|
: active
|
||||||
? "bg-blue-100 dark:bg-gray-700 dark:text-white"
|
? "bg-[var(--color-surface-alt)]"
|
||||||
: disabled
|
|
||||||
? "opacity-50 text-gray-400 dark:text-gray-500 cursor-not-allowed"
|
|
||||||
: ""
|
: ""
|
||||||
}`
|
}`
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -10,14 +10,14 @@ import { useAdminAuth } from "./hooks/useAdminAuth";
|
|||||||
const DatabaseSettings: React.FC = () => {
|
const DatabaseSettings: React.FC = () => {
|
||||||
const { isAdminLoggedIn } = useAdminAuth(true);
|
const { isAdminLoggedIn } = useAdminAuth(true);
|
||||||
return (
|
return (
|
||||||
<div className="p-6 bg-gray-100 dark:bg-gray-800 max-w-5xl mr-auto rounded shadow text-gray-900 dark:text-gray-100">
|
<div className="p-6 bg-[var(--color-surface-alt)] max-w-5xl mr-auto rounded shadow text-[var(--color-fg)]">
|
||||||
<h2 className="text-lg font-bold mb-6">Datenbank Einstellungen</h2>
|
<h2 className="text-lg font-bold mb-6">Datenbank Einstellungen</h2>
|
||||||
|
|
||||||
<div className="grid grid-cols-1 md:grid-cols-2 gap-4 mb-6">
|
<div className="grid grid-cols-1 md:grid-cols-2 gap-4 mb-6">
|
||||||
<button
|
<button
|
||||||
type="button"
|
type="button"
|
||||||
onClick={handleClearMessages}
|
onClick={handleClearMessages}
|
||||||
className="bg-littwin-blue text-white px-4 py-2 rounded shadow "
|
className="btn-accent px-4 py-2 rounded shadow"
|
||||||
>
|
>
|
||||||
Meldungen löschen
|
Meldungen löschen
|
||||||
</button>
|
</button>
|
||||||
@@ -25,7 +25,7 @@ const DatabaseSettings: React.FC = () => {
|
|||||||
<button
|
<button
|
||||||
type="button"
|
type="button"
|
||||||
onClick={handleClearLogger}
|
onClick={handleClearLogger}
|
||||||
className="bg-littwin-blue text-white px-4 py-2 rounded shadow "
|
className="btn-accent px-4 py-2 rounded shadow"
|
||||||
>
|
>
|
||||||
Messwerte Logger löschen
|
Messwerte Logger löschen
|
||||||
</button>
|
</button>
|
||||||
@@ -41,7 +41,7 @@ const DatabaseSettings: React.FC = () => {
|
|||||||
<button
|
<button
|
||||||
type="button"
|
type="button"
|
||||||
onClick={handleClearDatabase}
|
onClick={handleClearDatabase}
|
||||||
className="bg-littwin-blue text-white px-4 py-2 rounded shadow "
|
className="btn-accent px-4 py-2 rounded shadow"
|
||||||
>
|
>
|
||||||
Datenbank vollständig leeren
|
Datenbank vollständig leeren
|
||||||
</button>
|
</button>
|
||||||
@@ -49,7 +49,7 @@ const DatabaseSettings: React.FC = () => {
|
|||||||
<button
|
<button
|
||||||
type="button"
|
type="button"
|
||||||
onClick={handleClearConfig}
|
onClick={handleClearConfig}
|
||||||
className="bg-littwin-blue text-white px-4 py-2 rounded shadow "
|
className="btn-accent px-4 py-2 rounded shadow"
|
||||||
>
|
>
|
||||||
Konfiguration löschen
|
Konfiguration löschen
|
||||||
</button>
|
</button>
|
||||||
|
|||||||
@@ -63,8 +63,11 @@ const GeneralSettings: React.FC = () => {
|
|||||||
setMac1(systemSettings.mac1 || "");
|
setMac1(systemSettings.mac1 || "");
|
||||||
}, [systemSettings]);
|
}, [systemSettings]);
|
||||||
|
|
||||||
|
const inputCls =
|
||||||
|
"border border-base focus:border-accent rounded h-8 p-1 w-full text-xs bg-[var(--color-surface)] text-[var(--color-fg)] placeholder-[var(--color-muted)] transition-colors duration-150 focus:outline-none";
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<div className="p-6 md:p-3 bg-gray-100 dark:bg-gray-800 max-w-5xl mr-auto overflow-y-auto max-h-[calc(100vh-200px)] dark:text-gray-100 ">
|
<div className="p-6 md:p-3 bg-[var(--color-surface-alt)] max-w-5xl mr-auto overflow-y-auto max-h-[calc(100vh-200px)] text-[var(--color-fg)]">
|
||||||
<h2 className="text-sm md:text-md font-bold mb-2">
|
<h2 className="text-sm md:text-md font-bold mb-2">
|
||||||
Allgemeine Einstellungen
|
Allgemeine Einstellungen
|
||||||
</h2>
|
</h2>
|
||||||
@@ -74,25 +77,18 @@ const GeneralSettings: React.FC = () => {
|
|||||||
<label className="block text-xs md:text-sm font-medium">Name:</label>
|
<label className="block text-xs md:text-sm font-medium">Name:</label>
|
||||||
<input
|
<input
|
||||||
type="text"
|
type="text"
|
||||||
className="border border-gray-300 dark:border-gray-600 focus:border-littwin-blue dark:focus:border-littwin-blue rounded h-8 p-1 w-full text-xs !bg-white !text-black placeholder-gray-400 transition-colors duration-150 focus:outline-none dark:bg-gray-900 dark:text-gray-100 dark:placeholder-gray-500"
|
className={inputCls}
|
||||||
value={name}
|
value={name}
|
||||||
onChange={(e) => setName(e.target.value)}
|
onChange={(e) => setName(e.target.value)}
|
||||||
/>
|
/>
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
{/* MAC Adresse */}
|
{/* MAC Adresse */}
|
||||||
<div>
|
<div>
|
||||||
<label className="block text-xs md:text-sm font-medium">
|
<label className="block text-xs md:text-sm font-medium">
|
||||||
MAC Adresse 1:
|
MAC Adresse 1:
|
||||||
</label>
|
</label>
|
||||||
<input
|
<input type="text" className={inputCls} value={mac1} disabled />
|
||||||
type="text"
|
|
||||||
className="border border-gray-300 dark:border-gray-600 focus:border-littwin-blue dark:focus:border-littwin-blue rounded h-8 p-1 w-full text-xs !bg-white !text-black placeholder-gray-400 transition-colors duration-150 focus:outline-none dark:bg-gray-900 dark:text-gray-100 dark:placeholder-gray-500"
|
|
||||||
value={mac1}
|
|
||||||
disabled
|
|
||||||
/>
|
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
{/* Systemzeit */}
|
{/* Systemzeit */}
|
||||||
<div className="col-span-2">
|
<div className="col-span-2">
|
||||||
<label className="block text-xs md:text-sm font-medium mb-1">
|
<label className="block text-xs md:text-sm font-medium mb-1">
|
||||||
@@ -101,13 +97,13 @@ const GeneralSettings: React.FC = () => {
|
|||||||
<div className="flex flex-row gap-2">
|
<div className="flex flex-row gap-2">
|
||||||
<input
|
<input
|
||||||
type="text"
|
type="text"
|
||||||
className="border border-gray-300 dark:border-gray-600 focus:border-littwin-blue dark:focus:border-littwin-blue rounded h-8 p-1 w-full text-xs !bg-white !text-black placeholder-gray-400 transition-colors duration-150 focus:outline-none dark:bg-gray-900 dark:text-gray-100 dark:placeholder-gray-500"
|
className={inputCls}
|
||||||
value={systemUhr.replace(/\s*Uhr$/, "")}
|
value={systemUhr.replace(/\s*Uhr$/, "")}
|
||||||
disabled
|
disabled
|
||||||
/>
|
/>
|
||||||
<button
|
<button
|
||||||
type="button"
|
type="button"
|
||||||
className="bg-littwin-blue text-white px-4 py-2 h-8 text-xs rounded whitespace-nowrap"
|
className="btn-accent px-4 py-2 h-8 text-xs rounded whitespace-nowrap"
|
||||||
onClick={() => handleSetDateTime()}
|
onClick={() => handleSetDateTime()}
|
||||||
>
|
>
|
||||||
Systemzeit übernehmen
|
Systemzeit übernehmen
|
||||||
@@ -120,7 +116,7 @@ const GeneralSettings: React.FC = () => {
|
|||||||
<label className="block text-xs md:text-sm font-medium">IP:</label>
|
<label className="block text-xs md:text-sm font-medium">IP:</label>
|
||||||
<input
|
<input
|
||||||
type="text"
|
type="text"
|
||||||
className="border border-gray-300 dark:border-gray-600 focus:border-littwin-blue dark:focus:border-littwin-blue rounded h-8 p-1 w-full text-xs !bg-white !text-black placeholder-gray-400 transition-colors duration-150 focus:outline-none dark:bg-gray-900 dark:text-gray-100 dark:placeholder-gray-500"
|
className={inputCls}
|
||||||
value={ip}
|
value={ip}
|
||||||
onChange={(e) => setIp(e.target.value)}
|
onChange={(e) => setIp(e.target.value)}
|
||||||
/>
|
/>
|
||||||
@@ -131,7 +127,7 @@ const GeneralSettings: React.FC = () => {
|
|||||||
</label>
|
</label>
|
||||||
<input
|
<input
|
||||||
type="text"
|
type="text"
|
||||||
className="border border-gray-300 dark:border-gray-600 focus:border-littwin-blue dark:focus:border-littwin-blue rounded h-8 p-1 w-full text-xs !bg-white !text-black placeholder-gray-400 transition-colors duration-150 focus:outline-none dark:bg-gray-900 dark:text-gray-100 dark:placeholder-gray-500"
|
className={inputCls}
|
||||||
value={subnet}
|
value={subnet}
|
||||||
onChange={(e) => setSubnet(e.target.value)}
|
onChange={(e) => setSubnet(e.target.value)}
|
||||||
/>
|
/>
|
||||||
@@ -142,7 +138,7 @@ const GeneralSettings: React.FC = () => {
|
|||||||
</label>
|
</label>
|
||||||
<input
|
<input
|
||||||
type="text"
|
type="text"
|
||||||
className="border border-gray-300 dark:border-gray-600 focus:border-littwin-blue dark:focus:border-littwin-blue rounded h-8 p-1 w-full text-xs !bg-white !text-black placeholder-gray-400 transition-colors duration-150 focus:outline-none dark:bg-gray-900 dark:text-gray-100 dark:placeholder-gray-500"
|
className={inputCls}
|
||||||
value={gateway}
|
value={gateway}
|
||||||
onChange={(e) => setGateway(e.target.value)}
|
onChange={(e) => setGateway(e.target.value)}
|
||||||
/>
|
/>
|
||||||
@@ -152,7 +148,7 @@ const GeneralSettings: React.FC = () => {
|
|||||||
<div className="col-span-2 flex flex-wrap md:justify-between gap-1 mt-2">
|
<div className="col-span-2 flex flex-wrap md:justify-between gap-1 mt-2">
|
||||||
<button
|
<button
|
||||||
type="button"
|
type="button"
|
||||||
className="bg-littwin-blue text-white px-4 py-2 h-8 text-xs rounded whitespace-nowrap"
|
className="btn-accent px-4 py-2 h-8 text-xs rounded whitespace-nowrap"
|
||||||
onClick={() => handleReboot()}
|
onClick={() => handleReboot()}
|
||||||
>
|
>
|
||||||
Neustart CPL
|
Neustart CPL
|
||||||
@@ -160,7 +156,7 @@ const GeneralSettings: React.FC = () => {
|
|||||||
{isAdminLoggedIn && (
|
{isAdminLoggedIn && (
|
||||||
<button
|
<button
|
||||||
type="button"
|
type="button"
|
||||||
className="bg-littwin-blue text-white px-4 py-2 h-8 text-xs rounded whitespace-nowrap"
|
className="btn-accent px-4 py-2 h-8 text-xs rounded whitespace-nowrap"
|
||||||
onClick={() => {
|
onClick={() => {
|
||||||
const confirmed = window.confirm(
|
const confirmed = window.confirm(
|
||||||
"⚠️ Wollen Sie wirklich ein Firmwareupdate für alle KÜ-Module starten?"
|
"⚠️ Wollen Sie wirklich ein Firmwareupdate für alle KÜ-Module starten?"
|
||||||
|
|||||||
@@ -27,7 +27,7 @@ const NTPSettings: React.FC = () => {
|
|||||||
}
|
}
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<div className="p-6 md:p-3 bg-gray-100 dark:bg-gray-800 max-w-5xl mr-auto text-gray-900 dark:text-gray-100">
|
<div className="p-6 md:p-3 bg-[var(--color-surface-alt)] max-w-5xl mr-auto text-[var(--color-fg)]">
|
||||||
<h2 className="text-sm md:text-md font-bold mb-4">NTP Einstellungen</h2>
|
<h2 className="text-sm md:text-md font-bold mb-4">NTP Einstellungen</h2>
|
||||||
|
|
||||||
<div className="grid md:grid-cols-2 gap-3">
|
<div className="grid md:grid-cols-2 gap-3">
|
||||||
@@ -35,7 +35,7 @@ const NTPSettings: React.FC = () => {
|
|||||||
<label className="block text-xs font-medium">NTP Server 1</label>
|
<label className="block text-xs font-medium">NTP Server 1</label>
|
||||||
<input
|
<input
|
||||||
type="text"
|
type="text"
|
||||||
className="border border-gray-300 dark:border-gray-700 rounded h-8 p-1 w-full text-xs !bg-white !text-black placeholder-gray-400 transition-colors duration-150 focus:outline-none dark:bg-gray-900 dark:text-gray-100 dark:placeholder-gray-500"
|
className="border border-base rounded h-8 p-1 w-full text-xs bg-[var(--color-surface)] text-[var(--color-fg)] placeholder-[var(--color-muted)] transition-colors duration-150 focus:outline-none"
|
||||||
value={ntp1}
|
value={ntp1}
|
||||||
onChange={(e) => setNtp1(e.target.value)}
|
onChange={(e) => setNtp1(e.target.value)}
|
||||||
/>
|
/>
|
||||||
@@ -45,7 +45,7 @@ const NTPSettings: React.FC = () => {
|
|||||||
<label className="block text-xs font-medium">NTP Server 2</label>
|
<label className="block text-xs font-medium">NTP Server 2</label>
|
||||||
<input
|
<input
|
||||||
type="text"
|
type="text"
|
||||||
className="border border-gray-300 dark:border-gray-700 rounded h-8 p-1 w-full text-xs !bg-white !text-black placeholder-gray-400 transition-colors duration-150 focus:outline-none dark:bg-gray-900 dark:text-gray-100 dark:placeholder-gray-500"
|
className="border border-base rounded h-8 p-1 w-full text-xs bg-[var(--color-surface)] text-[var(--color-fg)] placeholder-[var(--color-muted)] transition-colors duration-150 focus:outline-none"
|
||||||
value={ntp2}
|
value={ntp2}
|
||||||
onChange={(e) => setNtp2(e.target.value)}
|
onChange={(e) => setNtp2(e.target.value)}
|
||||||
/>
|
/>
|
||||||
@@ -55,7 +55,7 @@ const NTPSettings: React.FC = () => {
|
|||||||
<label className="block text-xs font-medium">NTP Server 3</label>
|
<label className="block text-xs font-medium">NTP Server 3</label>
|
||||||
<input
|
<input
|
||||||
type="text"
|
type="text"
|
||||||
className="border border-gray-300 dark:border-gray-700 rounded h-8 p-1 w-full text-xs !bg-white !text-black placeholder-gray-400 transition-colors duration-150 focus:outline-none dark:bg-gray-900 dark:text-gray-100 dark:placeholder-gray-500"
|
className="border border-base rounded h-8 p-1 w-full text-xs bg-[var(--color-surface)] text-[var(--color-fg)] placeholder-[var(--color-muted)] transition-colors duration-150 focus:outline-none"
|
||||||
value={ntp3}
|
value={ntp3}
|
||||||
onChange={(e) => setNtp3(e.target.value)}
|
onChange={(e) => setNtp3(e.target.value)}
|
||||||
/>
|
/>
|
||||||
@@ -65,7 +65,7 @@ const NTPSettings: React.FC = () => {
|
|||||||
<label className="block text-xs font-medium">Zeitzone</label>
|
<label className="block text-xs font-medium">Zeitzone</label>
|
||||||
<input
|
<input
|
||||||
type="text"
|
type="text"
|
||||||
className="border border-gray-300 dark:border-gray-700 rounded h-8 p-1 w-full text-xs !bg-white !text-black placeholder-gray-400 transition-colors duration-150 focus:outline-none dark:bg-gray-900 dark:text-gray-100 dark:placeholder-gray-500"
|
className="border border-base rounded h-8 p-1 w-full text-xs bg-[var(--color-surface)] text-[var(--color-fg)] placeholder-[var(--color-muted)] transition-colors duration-150 focus:outline-none"
|
||||||
value={ntpTimezone}
|
value={ntpTimezone}
|
||||||
onChange={(e) => setNtpTimezone(e.target.value)}
|
onChange={(e) => setNtpTimezone(e.target.value)}
|
||||||
/>
|
/>
|
||||||
|
|||||||
@@ -21,7 +21,7 @@ export default function OPCUAInterfaceSettings() {
|
|||||||
);
|
);
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<div className="p-6 md:p-3 bg-gray-100 dark:bg-gray-800 max-w-5xl mr-auto text-gray-900 dark:text-gray-100 ">
|
<div className="p-6 md:p-3 bg-[var(--color-surface-alt)] max-w-5xl mr-auto text-[var(--color-fg)]">
|
||||||
<div className="flex justify-between items-center mb-3">
|
<div className="flex justify-between items-center mb-3">
|
||||||
<Image
|
<Image
|
||||||
src="/images/OPCUA.jpg"
|
src="/images/OPCUA.jpg"
|
||||||
@@ -44,9 +44,11 @@ export default function OPCUAInterfaceSettings() {
|
|||||||
<label className="mr-3 font-medium text-sm">Server Status:</label>
|
<label className="mr-3 font-medium text-sm">Server Status:</label>
|
||||||
<button
|
<button
|
||||||
onClick={() => dispatch(toggleOpcUaServer())}
|
onClick={() => dispatch(toggleOpcUaServer())}
|
||||||
className={`px-3 py-1 rounded text-sm ${
|
className={`px-3 py-1 rounded text-sm font-medium transition-colors text-white ${
|
||||||
opcuaSettings.isEnabled ? "bg-littwin-blue" : "bg-gray-300"
|
opcuaSettings.isEnabled
|
||||||
} text-white`}
|
? "bg-accent hover:brightness-110"
|
||||||
|
: "bg-[var(--color-muted)] hover:bg-[var(--color-fg)]/20"
|
||||||
|
}`}
|
||||||
>
|
>
|
||||||
{opcuaSettings.isEnabled ? "Aktiviert" : "Deaktiviert"}
|
{opcuaSettings.isEnabled ? "Aktiviert" : "Deaktiviert"}
|
||||||
</button>
|
</button>
|
||||||
@@ -62,7 +64,7 @@ export default function OPCUAInterfaceSettings() {
|
|||||||
<select
|
<select
|
||||||
value={opcuaSettings.encryption}
|
value={opcuaSettings.encryption}
|
||||||
onChange={(e) => dispatch(setOpcUaEncryption(e.target.value))}
|
onChange={(e) => dispatch(setOpcUaEncryption(e.target.value))}
|
||||||
className="w-full p-1 border border-gray-300 rounded-md text-sm"
|
className="w-full p-1 border border-base rounded-md text-sm bg-[var(--color-surface)] text-[var(--color-fg)]"
|
||||||
>
|
>
|
||||||
<option value="None">Keine</option>
|
<option value="None">Keine</option>
|
||||||
<option value="Basic256">Basic256</option>
|
<option value="Basic256">Basic256</option>
|
||||||
@@ -74,7 +76,7 @@ export default function OPCUAInterfaceSettings() {
|
|||||||
{/* ✅ OPCUA Zustand */}
|
{/* ✅ OPCUA Zustand */}
|
||||||
<div className="mb-3">
|
<div className="mb-3">
|
||||||
<label className="block font-medium text-sm mb-1">OPCUA Zustand</label>
|
<label className="block font-medium text-sm mb-1">OPCUA Zustand</label>
|
||||||
<div className="p-1 border border-gray-300 dark:border-gray-700 rounded-md bg-white dark:bg-gray-900 text-sm text-gray-900 dark:text-gray-100">
|
<div className="p-1 border border-base rounded-md bg-[var(--color-surface)] text-sm text-[var(--color-fg)]">
|
||||||
{opcuaSettings.opcUaZustand}
|
{opcuaSettings.opcUaZustand}
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
@@ -85,7 +87,7 @@ export default function OPCUAInterfaceSettings() {
|
|||||||
<div className="flex">
|
<div className="flex">
|
||||||
<input
|
<input
|
||||||
type="text"
|
type="text"
|
||||||
className="flex-grow p-1 border border-gray-300 dark:border-gray-700 rounded-l-md text-sm !bg-white !text-black placeholder-gray-400 transition-colors duration-150 focus:outline-none dark:bg-gray-900 dark:text-gray-100 dark:placeholder-gray-500"
|
className="flex-grow p-1 border border-base rounded-l-md text-sm bg-[var(--color-surface)] text-[var(--color-fg)] placeholder-[var(--color-muted)] transition-colors duration-150 focus:outline-none"
|
||||||
value={nodesetName}
|
value={nodesetName}
|
||||||
onChange={(e) => setNodesetName(e.target.value)}
|
onChange={(e) => setNodesetName(e.target.value)}
|
||||||
disabled={opcuaSettings.isEnabled} // Disable input when server is enabled
|
disabled={opcuaSettings.isEnabled} // Disable input when server is enabled
|
||||||
@@ -106,7 +108,7 @@ export default function OPCUAInterfaceSettings() {
|
|||||||
<label className="block font-medium text-sm mb-1">
|
<label className="block font-medium text-sm mb-1">
|
||||||
Aktuelle OPC-Clients
|
Aktuelle OPC-Clients
|
||||||
</label>
|
</label>
|
||||||
<div className="p-1 border border-gray-300 dark:border-gray-700 rounded-md bg-white dark:bg-gray-900 text-sm text-gray-900 dark:text-gray-100">
|
<div className="p-1 border border-base rounded-md bg-[var(--color-surface)] text-sm text-[var(--color-fg)]">
|
||||||
{opcUaActiveClientCount}
|
{opcUaActiveClientCount}
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
@@ -120,12 +122,12 @@ export default function OPCUAInterfaceSettings() {
|
|||||||
{opcuaSettings.users.map((user) => (
|
{opcuaSettings.users.map((user) => (
|
||||||
<li
|
<li
|
||||||
key={user.id}
|
key={user.id}
|
||||||
className="p-1 bg-white shadow-sm rounded-md flex justify-between items-center text-sm"
|
className="p-1 bg-[var(--color-surface)] border border-base rounded-md flex justify-between items-center text-sm text-[var(--color-fg)]"
|
||||||
>
|
>
|
||||||
<span className="font-medium">{user.username}</span>
|
<span className="font-medium">{user.username}</span>
|
||||||
<button
|
<button
|
||||||
onClick={() => dispatch(removeOpcUaUser(user.id))}
|
onClick={() => dispatch(removeOpcUaUser(user.id))}
|
||||||
className="text-red-500"
|
className="text-danger hover:underline"
|
||||||
>
|
>
|
||||||
Löschen
|
Löschen
|
||||||
</button>
|
</button>
|
||||||
@@ -140,18 +142,18 @@ export default function OPCUAInterfaceSettings() {
|
|||||||
placeholder="Benutzername"
|
placeholder="Benutzername"
|
||||||
value={newUsername}
|
value={newUsername}
|
||||||
onChange={(e) => setNewUsername(e.target.value)}
|
onChange={(e) => setNewUsername(e.target.value)}
|
||||||
className="p-1 border rounded flex-grow text-sm"
|
className="p-1 border border-base rounded flex-grow text-sm bg-[var(--color-surface)] text-[var(--color-fg)] placeholder-[var(--color-muted)] focus:outline-none focus:border-accent"
|
||||||
/>
|
/>
|
||||||
<input
|
<input
|
||||||
type="password"
|
type="password"
|
||||||
placeholder="Passwort"
|
placeholder="Passwort"
|
||||||
value={newPassword}
|
value={newPassword}
|
||||||
onChange={(e) => setNewPassword(e.target.value)}
|
onChange={(e) => setNewPassword(e.target.value)}
|
||||||
className="p-1 border rounded flex-grow text-sm"
|
className="p-1 border border-base rounded flex-grow text-sm bg-[var(--color-surface)] text-[var(--color-fg)] placeholder-[var(--color-muted)] focus:outline-none focus:border-accent"
|
||||||
/>
|
/>
|
||||||
<button
|
<button
|
||||||
onClick={handleAddUser}
|
onClick={handleAddUser}
|
||||||
className="bg-littwin-blue text-white p-1 rounded text-sm"
|
className="btn-primary p-1 rounded text-sm"
|
||||||
>
|
>
|
||||||
Hinzufügen
|
Hinzufügen
|
||||||
</button>
|
</button>
|
||||||
|
|||||||
@@ -22,6 +22,12 @@ const UserManagementSettings: React.FC = () => {
|
|||||||
() => {
|
() => {
|
||||||
setLoginSuccess(true);
|
setLoginSuccess(true);
|
||||||
setError("");
|
setError("");
|
||||||
|
// Speichere die System-Uhrzeit (Login-Zeitpunkt) im localStorage
|
||||||
|
try {
|
||||||
|
localStorage.setItem("adminLoginTime", new Date().toISOString());
|
||||||
|
} catch {
|
||||||
|
// Ignoriere Speicherfehler (z. B. in Private Mode)
|
||||||
|
}
|
||||||
},
|
},
|
||||||
(errorMsg) => {
|
(errorMsg) => {
|
||||||
setLoginSuccess(false);
|
setLoginSuccess(false);
|
||||||
@@ -38,7 +44,7 @@ const UserManagementSettings: React.FC = () => {
|
|||||||
};
|
};
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<div className="p-6 md:p-3 bg-gray-100 dark:bg-gray-800 max-w-5xl mr-auto text-gray-900 dark:text-gray-100">
|
<div className="p-6 md:p-3 bg-[var(--color-surface-alt)] max-w-5xl mr-auto text-[var(--color-fg)]">
|
||||||
<h2 className="text-sm md:text-md font-bold mb-4">Login Admin-Bereich</h2>
|
<h2 className="text-sm md:text-md font-bold mb-4">Login Admin-Bereich</h2>
|
||||||
|
|
||||||
{/* Admin Login/Logout */}
|
{/* Admin Login/Logout */}
|
||||||
@@ -46,8 +52,15 @@ const UserManagementSettings: React.FC = () => {
|
|||||||
{isAdminLoggedIn ? (
|
{isAdminLoggedIn ? (
|
||||||
<button
|
<button
|
||||||
type="button"
|
type="button"
|
||||||
className="bg-littwin-blue text-white px-4 py-2 h-8 text-xs rounded whitespace-nowrap"
|
className="btn-accent px-4 py-2 h-8 text-xs rounded whitespace-nowrap"
|
||||||
onClick={logoutAdmin}
|
onClick={() => {
|
||||||
|
try {
|
||||||
|
localStorage.removeItem("adminLoginTime");
|
||||||
|
} catch {
|
||||||
|
// ignore
|
||||||
|
}
|
||||||
|
logoutAdmin();
|
||||||
|
}}
|
||||||
>
|
>
|
||||||
Admin abmelden
|
Admin abmelden
|
||||||
</button>
|
</button>
|
||||||
@@ -57,7 +70,7 @@ const UserManagementSettings: React.FC = () => {
|
|||||||
<input
|
<input
|
||||||
type="text"
|
type="text"
|
||||||
placeholder="Benutzername"
|
placeholder="Benutzername"
|
||||||
className="border border-gray-300 dark:border-gray-700 rounded h-8 p-1 w-full text-xs !bg-white !text-black placeholder-gray-400 transition-colors duration-150 focus:outline-none dark:bg-gray-900 dark:text-gray-100 dark:placeholder-gray-500"
|
className="border border-base rounded h-8 p-1 w-full text-xs bg-[var(--color-surface)] text-[var(--color-fg)] placeholder-[var(--color-muted)] transition-colors duration-150 focus:outline-none"
|
||||||
value={username}
|
value={username}
|
||||||
onChange={(e) => setUsername(e.target.value)}
|
onChange={(e) => setUsername(e.target.value)}
|
||||||
onKeyDown={handleKeyDown}
|
onKeyDown={handleKeyDown}
|
||||||
@@ -65,14 +78,14 @@ const UserManagementSettings: React.FC = () => {
|
|||||||
<input
|
<input
|
||||||
type="password"
|
type="password"
|
||||||
placeholder="Passwort"
|
placeholder="Passwort"
|
||||||
className="border border-gray-300 dark:border-gray-700 rounded h-8 p-1 w-full text-xs !bg-white !text-black placeholder-gray-400 transition-colors duration-150 focus:outline-none dark:bg-gray-900 dark:text-gray-100 dark:placeholder-gray-500"
|
className="border border-base rounded h-8 p-1 w-full text-xs bg-[var(--color-surface)] text-[var(--color-fg)] placeholder-[var(--color-muted)] transition-colors duration-150 focus:outline-none"
|
||||||
value={password}
|
value={password}
|
||||||
onChange={(e) => setPassword(e.target.value)}
|
onChange={(e) => setPassword(e.target.value)}
|
||||||
onKeyDown={handleKeyDown}
|
onKeyDown={handleKeyDown}
|
||||||
/>
|
/>
|
||||||
<button
|
<button
|
||||||
type="button"
|
type="button"
|
||||||
className="bg-littwin-blue text-white px-4 py-2 h-8 text-xs rounded whitespace-nowrap"
|
className="btn-accent px-4 py-2 h-8 text-xs rounded whitespace-nowrap"
|
||||||
onClick={handleLogin}
|
onClick={handleLogin}
|
||||||
>
|
>
|
||||||
Admin anmelden
|
Admin anmelden
|
||||||
@@ -83,9 +96,9 @@ const UserManagementSettings: React.FC = () => {
|
|||||||
</div>
|
</div>
|
||||||
|
|
||||||
{loginSuccess && (
|
{loginSuccess && (
|
||||||
<p className="text-green-600 text-xs mt-2">Login erfolgreich!</p>
|
<p className="text-success text-xs mt-2">Login erfolgreich!</p>
|
||||||
)}
|
)}
|
||||||
{error && <p className="text-red-500 text-xs mt-2">{error}</p>}
|
{error && <p className="text-danger text-xs mt-2">{error}</p>}
|
||||||
|
|
||||||
{/*
|
{/*
|
||||||
// Benutzerverwaltungstabelle (kommt später)
|
// Benutzerverwaltungstabelle (kommt später)
|
||||||
|
|||||||
@@ -13,7 +13,7 @@ const ProgressModal: React.FC<Props> = ({ visible, progress, slot }) => {
|
|||||||
|
|
||||||
return (
|
return (
|
||||||
<div className="fixed inset-0 bg-black bg-opacity-50 flex items-center justify-center z-50 ">
|
<div className="fixed inset-0 bg-black bg-opacity-50 flex items-center justify-center z-50 ">
|
||||||
<div className="bg-white dark:bg-gray-800 text-gray-900 dark:text-gray-100 p-6 rounded shadow-md text-center w-80">
|
<div className="p-6 rounded shadow-sm text-center w-80 bg-[var(--color-surface)] dark:bg-[var(--color-surface)] text-[var(--color-fg)] border border-[var(--color-border)]">
|
||||||
{/*
|
{/*
|
||||||
<h2 className="text-lg font-bold mb-4">
|
<h2 className="text-lg font-bold mb-4">
|
||||||
Firmwareupdate
|
Firmwareupdate
|
||||||
@@ -26,9 +26,9 @@ const ProgressModal: React.FC<Props> = ({ visible, progress, slot }) => {
|
|||||||
</h2>
|
</h2>
|
||||||
Bitte Fenster nicht schließen
|
Bitte Fenster nicht schließen
|
||||||
<h2></h2>
|
<h2></h2>
|
||||||
<div className="w-full bg-gray-200 rounded-full h-4">
|
<div className="w-full h-4 rounded-full bg-[var(--color-surface-alt)]/80 dark:bg-[var(--color-surface-alt)]/30 border border-[var(--color-border)] overflow-hidden">
|
||||||
<div
|
<div
|
||||||
className="bg-littwin-blue h-4 rounded-full transition-all duration-100"
|
className="h-full rounded-full transition-all duration-200 bg-[var(--color-accent)]"
|
||||||
style={{ width: `${progress}%` }}
|
style={{ width: `${progress}%` }}
|
||||||
></div>
|
></div>
|
||||||
</div>
|
</div>
|
||||||
|
|||||||
@@ -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-gray-600 hover:text-gray-800"
|
className="icon-btn text-[1.4rem] hover:text-fg transition"
|
||||||
|
aria-label={isFullScreen ? "Vollbild verlassen" : "Vollbild"}
|
||||||
|
type="button"
|
||||||
>
|
>
|
||||||
<i
|
<i
|
||||||
className={
|
className={
|
||||||
@@ -462,29 +482,36 @@ 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-gray-600 hover:text-gray-800"
|
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>
|
||||||
|
|
||||||
|
{/* Body */}
|
||||||
|
<div className="flex-1 min-h-0 flex flex-col px-6 pt-4 pb-5 bg-[var(--color-surface)] overflow-hidden">
|
||||||
|
<div className="mb-3">
|
||||||
<SystemChartActionBar
|
<SystemChartActionBar
|
||||||
zeitraum={zeitraum}
|
zeitraum={zeitraum}
|
||||||
setZeitraum={setZeitraum}
|
setZeitraum={setZeitraum}
|
||||||
onFetchData={handleFetchData}
|
onFetchData={handleFetchData}
|
||||||
isLoading={isLoading}
|
isLoading={isLoading}
|
||||||
|
className="mb-0"
|
||||||
/>
|
/>
|
||||||
|
</div>
|
||||||
<div className="h-[85%] bg-white dark:bg-gray-800 rounded shadow border border-gray-200 dark:border-gray-700 p-2">
|
<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} />
|
<Line ref={chartRef} data={chartData} options={chartOptions} />
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
|
{/* Optional Footer (currently empty, reserved for future) */}
|
||||||
|
</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,
|
||||||
|
|||||||
@@ -16,7 +16,7 @@ export const SystemOverviewGrid = ({ voltages, onOpenDetail }: Props) => {
|
|||||||
return (
|
return (
|
||||||
<div
|
<div
|
||||||
key={key}
|
key={key}
|
||||||
className="p-4 border rounded shadow bg-white dark:bg-gray-800 border-gray-200 dark:border-gray-700 text-gray-900 dark:text-gray-100"
|
className="p-4 border rounded shadow-sm bg-[var(--color-surface)] dark:bg-[var(--color-surface)] border-[var(--color-border)] text-[var(--color-fg)] hover:bg-[var(--color-surface-alt)]/60 dark:hover:bg-[var(--color-surface-alt)]/30 transition"
|
||||||
>
|
>
|
||||||
<h2 className="font-semibold">{key}</h2>
|
<h2 className="font-semibold">{key}</h2>
|
||||||
<p>
|
<p>
|
||||||
|
|||||||
@@ -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":
|
||||||
@@ -71,8 +71,8 @@ const SystemPage = () => {
|
|||||||
};
|
};
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<div className="p-4 bg-white dark:bg-gray-900">
|
<div className="p-4 bg-[var(--color-background)] text-[var(--color-fg)]">
|
||||||
<h1 className="text-xl font-bold mb-4">
|
<h1 className="text-xl font-bold mb-4 tracking-wide">
|
||||||
System Spannungen & Temperaturen
|
System Spannungen & Temperaturen
|
||||||
</h1>
|
</h1>
|
||||||
|
|
||||||
@@ -80,7 +80,7 @@ const SystemPage = () => {
|
|||||||
<div className="flex justify-center items-center h-[400px]">
|
<div className="flex justify-center items-center h-[400px]">
|
||||||
<div className="text-center">
|
<div className="text-center">
|
||||||
<ClipLoader size={50} color="#3B82F6" />
|
<ClipLoader size={50} color="#3B82F6" />
|
||||||
<p className="mt-4 text-gray-500">
|
<p className="mt-4 text-[var(--color-fg-muted)]">
|
||||||
Lade Systemdaten … bitte warten
|
Lade Systemdaten … bitte warten
|
||||||
</p>
|
</p>
|
||||||
</div>
|
</div>
|
||||||
|
|||||||
@@ -36,12 +36,12 @@ const Navigation: React.FC<NavigationProps> = ({ className }) => {
|
|||||||
];
|
];
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<aside className="bg-white dark:bg-gray-900 h-full">
|
<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 ? (
|
||||||
<div className="block px-4 py-2 mb-4 font-bold whitespace-nowrap text-gray-400 dark:text-gray-600 cursor-not-allowed text-[1rem] sm:text-[1rem] md:text-[1rem] lg:text-[1rem] xl:text-sm 2xl:text-lg">
|
<div className="block px-4 py-2 mb-4 font-bold whitespace-nowrap text-[var(--color-fg-muted)] opacity-60 cursor-not-allowed text-[1rem] sm:text-[1rem] md:text-[1rem] lg:text-[1rem] xl:text-sm 2xl:text-lg">
|
||||||
{item.name}
|
{item.name}
|
||||||
</div>
|
</div>
|
||||||
) : (
|
) : (
|
||||||
@@ -49,11 +49,13 @@ const Navigation: React.FC<NavigationProps> = ({ className }) => {
|
|||||||
href={formatPath(item.path)}
|
href={formatPath(item.path)}
|
||||||
prefetch={false}
|
prefetch={false}
|
||||||
onClick={() => setActiveLink(item.path)}
|
onClick={() => setActiveLink(item.path)}
|
||||||
className={`block px-4 py-2 mb-4 font-bold whitespace-nowrap transition duration-300 text-[1rem] sm:text-[1rem] md:text-[1rem] lg:text-[1rem] xl:text-sm 2xl:text-lg ${
|
className={`block px-4 py-2 mb-4 font-semibold whitespace-nowrap transition duration-200 rounded-r-full pr-6 relative text-[1rem] sm:text-[1rem] md:text-[1rem] lg:text-[1rem] xl:text-sm 2xl:text-lg
|
||||||
|
${
|
||||||
activeLink.startsWith(item.path)
|
activeLink.startsWith(item.path)
|
||||||
? "bg-sky-500 text-white rounded-r-full xl:mr-4 xl:w-full dark:bg-sky-600 dark:text-white"
|
? "bg-[var(--color-accent)] text-white shadow-sm xl:mr-4 xl:w-full"
|
||||||
: "text-black hover:bg-gray-200 rounded-r-full dark:text-gray-200 dark:hover:bg-gray-800"
|
: "text-[var(--color-fg-muted)] hover:text-[var(--color-fg)] hover:bg-[var(--color-surface-alt)]/80 dark:hover:bg-[var(--color-surface-alt)]/40"
|
||||||
}`}
|
}
|
||||||
|
`}
|
||||||
>
|
>
|
||||||
{item.name}
|
{item.name}
|
||||||
</Link>
|
</Link>
|
||||||
|
|||||||
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**
|
||||||
|
|
||||||
|
|||||||
@@ -2,7 +2,7 @@
|
|||||||
"win_da_state": [
|
"win_da_state": [
|
||||||
1,
|
1,
|
||||||
1,
|
1,
|
||||||
1,
|
0,
|
||||||
1
|
1
|
||||||
],
|
],
|
||||||
"win_da_bezeichnung": [
|
"win_da_bezeichnung": [
|
||||||
|
|||||||
@@ -269,7 +269,12 @@ var tdrMeasurementEvent = [
|
|||||||
0, 0, 0, 0, 0, 0,
|
0, 0, 0, 0, 0, 0,
|
||||||
];
|
];
|
||||||
//Event Abgleich
|
//Event Abgleich
|
||||||
var alignmentEvent = [
|
var comparisonEvent = [
|
||||||
|
// renamed from alignmentEvent
|
||||||
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,
|
||||||
];
|
];
|
||||||
|
// expose for browser simulation
|
||||||
|
if (typeof window !== "undefined") {
|
||||||
|
window.comparisonEvent = comparisonEvent;
|
||||||
|
}
|
||||||
|
|||||||
@@ -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,21 +1,203 @@
|
|||||||
{
|
{
|
||||||
"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",
|
||||||
|
|||||||
@@ -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.855",
|
"version": "1.6.913",
|
||||||
"lockfileVersion": 3,
|
"lockfileVersion": 3,
|
||||||
"requires": true,
|
"requires": true,
|
||||||
"packages": {
|
"packages": {
|
||||||
"": {
|
"": {
|
||||||
"name": "cpl-v4",
|
"name": "cpl-v4",
|
||||||
"version": "1.6.855",
|
"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.855",
|
"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,11 +38,25 @@ 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}>
|
||||||
|
<CssBaseline />
|
||||||
{process.env.NODE_ENV === "development" ? (
|
{process.env.NODE_ENV === "development" ? (
|
||||||
<Script
|
<Script
|
||||||
src="/api/cpl/kabelueberwachungAPIHandler"
|
src="/api/cpl/kabelueberwachungAPIHandler"
|
||||||
@@ -53,8 +66,8 @@ function MyApp({ Component, pageProps }: AppProps) {
|
|||||||
<Script src="/CPL/SERVICE/kueData.js" strategy="afterInteractive" />
|
<Script src="/CPL/SERVICE/kueData.js" strategy="afterInteractive" />
|
||||||
)}
|
)}
|
||||||
<AppContent Component={Component} pageProps={pageProps} />
|
<AppContent Component={Component} pageProps={pageProps} />
|
||||||
{/* Bridge window events -> Redux (works across all pages) */}
|
|
||||||
<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)
|
||||||
@@ -148,11 +160,11 @@ function AppContent({
|
|||||||
}, [pathname, dispatch]);
|
}, [pathname, dispatch]);
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<div className="flex flex-col h-screen overflow-hidden bg-gray-100 dark:bg-gray-900 text-gray-900 dark:text-gray-100">
|
<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-white dark:bg-gray-900">
|
<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
|
||||||
|
|||||||
@@ -8,6 +8,13 @@ export default function Document() {
|
|||||||
<link rel="icon" href="/favicon.png" type="image/png" />
|
<link rel="icon" href="/favicon.png" type="image/png" />
|
||||||
</Head>
|
</Head>
|
||||||
<body>
|
<body>
|
||||||
|
{/* Theme init (executed before React hydration) */}
|
||||||
|
<script
|
||||||
|
// eslint-disable-next-line react/no-danger
|
||||||
|
dangerouslySetInnerHTML={{
|
||||||
|
__html: `(() => { try { const ls = localStorage.getItem('theme'); const mql = window.matchMedia('(prefers-color-scheme: dark)'); const wantDark = ls === 'dark' || (!ls && mql.matches); if (wantDark) document.documentElement.classList.add('dark'); } catch(e) {} })();`,
|
||||||
|
}}
|
||||||
|
/>
|
||||||
<Main /> {/* Hier wird der Seiteninhalt eingebettet */}
|
<Main /> {/* Hier wird der Seiteninhalt eingebettet */}
|
||||||
<NextScript /> {/* Fügt Next.js-Skripte für die Seite hinzu */}
|
<NextScript /> {/* Fügt Next.js-Skripte für die Seite hinzu */}
|
||||||
</body>
|
</body>
|
||||||
|
|||||||
@@ -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);
|
|
||||||
}
|
|
||||||
Some files were not shown because too many files have changed in this diff Show More
Reference in New Issue
Block a user