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

@@ -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]">