feat(kue705FO): scrolling für lange Modulnamen (48 Zeichen) + Version-Gate/Env-Override

- Unterstützt bis zu 48 Zeichen im Modulnamen; bei Überlänge automatische Laufschrift
- Marquee via react-fast-marquee (SSR-sicher per next/dynamic)
- Overflow-Erkennung + Tooltip mit vollem Namen
- Version-Gate: aktiviert ab V4.30
This commit is contained in:
ISA
2025-09-05 08:41:10 +02:00
parent 2484d057fb
commit 8a9cd72718
9 changed files with 140 additions and 11 deletions

View File

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

View File

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

View File

@@ -1,3 +1,8 @@
## [1.6.845] 2025-09-05
- feat: prepare KÜ 8 for scrolling text
---
## [1.6.844] 2025-09-05
- test: woodpecker

View File

@@ -1,5 +1,10 @@
"use client"; // components/modules/kue705FO/Kue705FO.tsx
import React, { useState, useMemo } from "react";
import React, { useState, useMemo, useEffect, useRef } from "react";
import dynamic from "next/dynamic";
// eslint-disable-next-line @typescript-eslint/no-explicit-any
const Marquee: any = dynamic(() => import("react-fast-marquee"), {
ssr: false,
});
import { useSelector } from "react-redux";
import KueModal from "./modals/SettingsModalWrapper";
// import FallSensors from "../../fall-detection-sensors/FallSensors";
@@ -54,6 +59,19 @@ const Kue705FO: React.FC<Kue705FOProps> = ({
// Admin authentication hook for security - using showModal as true for continuous auth check
const { isAdminLoggedIn } = useAdminAuth(true);
// Modulname (max 48 Zeichen) vorbereiten
const moduleNameRaw = useMemo(
() => kueName?.[slotIndex] || `Modul ${slotIndex + 1}`,
[kueName, slotIndex]
);
const moduleName48 = useMemo(
() =>
typeof moduleNameRaw === "string"
? moduleNameRaw.slice(0, 48)
: String(moduleNameRaw),
[moduleNameRaw]
);
const [activeButton, setActiveButton] = useState<"Schleife" | "TDR" | "ISO">(
"Schleife"
);
@@ -206,6 +224,53 @@ const Kue705FO: React.FC<Kue705FOProps> = ({
);
const { setCurrentModulName } = useModulName(slotIndex, modulName);
//---------------------------------
// Version-gate für Laufschrift: erst ab V4.30 aktiv
const parseVersion = (v?: string): [number, number, number] => {
if (!v) return [0, 0, 0];
const m = String(v).match(/(\d+)(?:\.(\d+))?(?:\.(\d+))?/);
if (!m) return [0, 0, 0];
const major = parseInt(m[1] || "0", 10) || 0;
const minor = parseInt(m[2] || "0", 10) || 0;
const patch = parseInt(m[3] || "0", 10) || 0;
return [major, minor, patch];
};
const gte = (a: [number, number, number], b: [number, number, number]) => {
if (a[0] !== b[0]) return a[0] > b[0];
if (a[1] !== b[1]) return a[1] > b[1];
return a[2] >= b[2];
};
const marqueeOverride =
process.env.NEXT_PUBLIC_ENABLE_KUE_MARQUEE === "1" ||
process.env.NEXT_PUBLIC_ENABLE_KUE_MARQUEE === "true";
const scrollFeatureEnabled = useMemo(
() => marqueeOverride || gte(parseVersion(kueVersion), [4, 30, 0]),
[kueVersion, marqueeOverride]
);
// Überlängen-Erkennung für Laufschrift
const nameContainerRef = useRef<HTMLDivElement | null>(null);
const measureTextRef = useRef<HTMLSpanElement | null>(null);
const [shouldScroll, setShouldScroll] = useState(false);
useEffect(() => {
const measure = () => {
if (!scrollFeatureEnabled) {
setShouldScroll(false);
return;
}
const container = nameContainerRef.current;
const text = measureTextRef.current;
if (!container || !text) {
setShouldScroll(false);
return;
}
const needs = text.scrollWidth > container.clientWidth + 2;
setShouldScroll(needs);
};
measure();
window.addEventListener("resize", measure);
return () => window.removeEventListener("resize", measure);
}, [moduleName48, scrollFeatureEnabled]);
//---------------------------------
const tdmChartData = useSelector(
(state: RootState) => state.tdmChartSlice.data
@@ -360,8 +425,38 @@ const Kue705FO: React.FC<Kue705FOProps> = ({
<div className="absolute top-0 left-[4.688rem] w-[0.188rem] h-full bg-white z-0"></div>
<div className="absolute top-[2.5rem] left-[4.688rem] w-[2.5rem] h-[0.188rem] bg-white z-0"></div>
<div className="absolute bottom-[1.25rem] left-0 right-0 text-black text-[0.625rem] bg-gray-300 p-[0.063rem] text-center">
{kueName?.[slotIndex] || `Modul ${slotIndex + 1}`}
{/* Hidden measuring span for overflow detection (kept measurable) */}
<span
ref={measureTextRef}
style={{
position: "absolute",
left: -9999,
top: -9999,
visibility: "hidden",
whiteSpace: "nowrap",
}}
>
{moduleName48}
</span>
<div
ref={nameContainerRef}
className="absolute bottom-[1.25rem] left-0 right-0 text-black text-[0.625rem] bg-gray-300 p-[0.063rem] overflow-hidden"
>
{shouldScroll && scrollFeatureEnabled ? (
<Marquee pauseOnHover gradient={false} speed={40}>
<span className="pr-8 whitespace-nowrap" title={moduleName48}>
{moduleName48}
</span>
</Marquee>
) : (
<span
className="block text-center whitespace-nowrap"
title={moduleName48}
>
{moduleName48}
</span>
)}
</div>
<div className="absolute bottom-[0.063rem] right-[0.063rem] text-black text-[0.5rem]">

View File

@@ -94,9 +94,9 @@ in Rot, wenn Schleifenfehler ansteht
# Folgende Erweiterung / Neuerungen:
[ ] TODO: Messverlauf bei Systemwerten (Temperatur und Spannungen) mit Datumsauswahl
[x] TODO: Messverlauf bei Systemwerten (Temperatur und Spannungen) mit Datumsauswahl
[ ] 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)

View File

@@ -92,7 +92,7 @@ var win_kueLoopInterval = [
//---------------------------------------------------
//KÜ Modul Version soll /100 und davor V angezeigt werden z.B. 4.19V
var win_kueVersion = [
420, 419, 419, 419, 419, 419, 419, 419, 419, 419, 419, 419, 419, 419, 419,
420, 419, 419, 419, 419, 419, 419, 430, 419, 419, 419, 419, 419, 419, 419,
419, 419, 419, 419, 419, 419, 419, 419, 419, 419, 419, 419, 419, 419, 419,
419, 419,
];

15
package-lock.json generated
View File

@@ -1,12 +1,12 @@
{
"name": "cpl-v4",
"version": "1.6.844",
"version": "1.6.845",
"lockfileVersion": 3,
"requires": true,
"packages": {
"": {
"name": "cpl-v4",
"version": "1.6.844",
"version": "1.6.845",
"dependencies": {
"@fontsource/roboto": "^5.1.0",
"@headlessui/react": "^2.2.4",
@@ -39,6 +39,7 @@
"react-datepicker": "^8.0.0",
"react-day-picker": "^9.6.7",
"react-dom": "^18.3.1",
"react-fast-marquee": "^1.6.0",
"react-modal": "^3.16.1",
"react-redux": "^9.1.2",
"react-spinners": "^0.14.1",
@@ -9161,6 +9162,16 @@
"react": "^18.3.1"
}
},
"node_modules/react-fast-marquee": {
"version": "1.6.5",
"resolved": "https://registry.npmjs.org/react-fast-marquee/-/react-fast-marquee-1.6.5.tgz",
"integrity": "sha512-swDnPqrT2XISAih0o74zQVE2wQJFMvkx+9VZXYYNSLb/CUcAzU9pNj637Ar2+hyRw6b4tP6xh4GQZip2ZCpQpg==",
"license": "MIT",
"peerDependencies": {
"react": ">= 16.8.0 || ^18.0.0",
"react-dom": ">= 16.8.0 || ^18.0.0"
}
},
"node_modules/react-fit": {
"version": "2.0.1",
"resolved": "https://registry.npmjs.org/react-fit/-/react-fit-2.0.1.tgz",

View File

@@ -1,6 +1,6 @@
{
"name": "cpl-v4",
"version": "1.6.844",
"version": "1.6.845",
"private": true,
"scripts": {
"dev": "next dev -p 3000",
@@ -60,6 +60,7 @@
"next": "^14.2.23",
"node-fetch": "^3.3.2",
"react": "^18.3.1",
"react-fast-marquee": "^1.6.0",
"react-chartjs-2": "^5.3.0",
"react-date-picker": "^11.0.0",
"react-datepicker": "^8.0.0",

17
types/react-fast-marquee.d.ts vendored Normal file
View File

@@ -0,0 +1,17 @@
declare module "react-fast-marquee" {
import * as React from "react";
interface MarqueeProps {
speed?: number;
pauseOnHover?: boolean;
gradient?: boolean;
gradientColor?: [number, number, number];
gradientWidth?: number | string;
direction?: "left" | "right";
play?: boolean;
className?: string;
style?: React.CSSProperties;
children?: React.ReactNode;
}
const Marquee: React.FC<MarqueeProps>;
export default Marquee;
}