Compare commits

2 Commits

11 changed files with 176 additions and 47 deletions

View File

@@ -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.865 NEXT_PUBLIC_APP_VERSION=1.6.867
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)

View File

@@ -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.865 NEXT_PUBLIC_APP_VERSION=1.6.867
NEXT_PUBLIC_CPL_MODE=production NEXT_PUBLIC_CPL_MODE=production

View File

@@ -1,3 +1,13 @@
## [1.6.867] 2025-09-08
- WIP: Timer für jeder KÜ separate und nicht eine für alle, aktuell wird prozentzahl bei allen das gleiche angezeigt
---
## [1.6.866] 2025-09-08
- Test: Jenkinsfile
---
## [1.6.865] 2025-09-08 ## [1.6.865] 2025-09-08
- test: Jenkinsfile - test: Jenkinsfile

View File

@@ -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[]) => {

View File

@@ -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 (: {fmt(ksz)}) kann bis zu 10 Minuten dauern Comparison läuft (: {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">

View File

@@ -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));
@@ -43,7 +69,7 @@ export default function SlotActivityOverlay({
const TDR_MS = 30 * 1000; // ~30 s const TDR_MS = 30 * 1000; // ~30 s
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">

View File

@@ -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 = [
0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, // renamed from alignmentEvent
1, 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,
]; ];
// expose for browser simulation
if (typeof window !== "undefined") {
window.comparisonEvent = comparisonEvent;
}

4
package-lock.json generated
View File

@@ -1,12 +1,12 @@
{ {
"name": "cpl-v4", "name": "cpl-v4",
"version": "1.6.865", "version": "1.6.867",
"lockfileVersion": 3, "lockfileVersion": 3,
"requires": true, "requires": true,
"packages": { "packages": {
"": { "": {
"name": "cpl-v4", "name": "cpl-v4",
"version": "1.6.865", "version": "1.6.867",
"dependencies": { "dependencies": {
"@fontsource/roboto": "^5.1.0", "@fontsource/roboto": "^5.1.0",
"@headlessui/react": "^2.2.4", "@headlessui/react": "^2.2.4",

View File

@@ -1,6 +1,6 @@
{ {
"name": "cpl-v4", "name": "cpl-v4",
"version": "1.6.865", "version": "1.6.867",
"private": true, "private": true,
"scripts": { "scripts": {
"dev": "next dev -p 3000", "dev": "next dev -p 3000",

View File

@@ -35,8 +35,8 @@ var win_tdrLocation=[<%=KTF80%>,<%=KTF81%>,<%=KTF82%>,<%=KTF83%>];//Entfernung B
var loopMeasurementEvent=[<%=KSX80%>, <%=KSX81%>, <%=KSX82%>, <%=KSX83%>]; var loopMeasurementEvent=[<%=KSX80%>, <%=KSX81%>, <%=KSX82%>, <%=KSX83%>];
//Event TDR-Messung //Event TDR-Messung
var tdrMeasurementEvent=[<%=KSY80%>, <%=KSY81%>, <%=KSY82%>, <%=KSY83%>]; var tdrMeasurementEvent=[<%=KSY80%>, <%=KSY81%>, <%=KSY82%>, <%=KSY83%>];
//Event Abgleich //Event Comparison (ehem. Abgleich)
var alignmentEvent=[<%=KSZ80%>, <%=KSZ81%>, <%=KSZ82%>, <%=KSZ83%>]; var comparisonEvent=[<%=KSZ80%>, <%=KSZ81%>, <%=KSZ82%>, <%=KSZ83%>];
//--------------------------------------------------- //---------------------------------------------------

View File

@@ -3,13 +3,17 @@ import { createSlice, PayloadAction } from "@reduxjs/toolkit";
export interface DeviceEventsState { export interface DeviceEventsState {
ksx: number[]; // 32 Slots: Schleifenmessung aktiv ksx: number[]; // 32 Slots: Schleifenmessung aktiv
ksy: number[]; // 32 Slots: TDR-Messung aktiv ksy: number[]; // 32 Slots: TDR-Messung aktiv
ksz: number[]; // 32 Slots: Abgleich aktiv ksz: number[]; // 32 Slots: Comparison (ehem. Abgleich) aktiv
anyLoopActive: boolean; anyLoopActive: boolean;
anyTdrActive: boolean; anyTdrActive: boolean;
anyAlignmentActive: boolean; anyComparisonActive: boolean; // renamed from anyAlignmentActive
loopStartedAt: number | null; // unix ms timestamp when KSX became active loopStartedAt: number | null; // unix ms timestamp when KSX became active
tdrStartedAt: number | null; // unix ms timestamp when KSY became active tdrStartedAt: number | null; // unix ms timestamp when KSY became active
alignmentStartedAt: number | null; // unix ms timestamp when KSZ became active comparisonStartedAt: number | null; // renamed from alignmentStartedAt
// per-slot start timestamps (persistable)
loopStartedAtBySlot: (number | null)[];
tdrStartedAtBySlot: (number | null)[];
comparisonStartedAtBySlot: (number | null)[]; // renamed from alignmentStartedAtBySlot
} }
const ZERO32 = Array.from({ length: 32 }, () => 0); const ZERO32 = Array.from({ length: 32 }, () => 0);
@@ -20,10 +24,13 @@ const initialState: DeviceEventsState = {
ksz: ZERO32.slice(), ksz: ZERO32.slice(),
anyLoopActive: false, anyLoopActive: false,
anyTdrActive: false, anyTdrActive: false,
anyAlignmentActive: false, anyComparisonActive: false,
loopStartedAt: null, loopStartedAt: null,
tdrStartedAt: null, tdrStartedAt: null,
alignmentStartedAt: null, comparisonStartedAt: null,
loopStartedAtBySlot: Array.from({ length: 32 }, () => null),
tdrStartedAtBySlot: Array.from({ length: 32 }, () => null),
comparisonStartedAtBySlot: Array.from({ length: 32 }, () => null),
}; };
export const deviceEventsSlice = createSlice({ export const deviceEventsSlice = createSlice({
@@ -36,7 +43,10 @@ export const deviceEventsSlice = createSlice({
) { ) {
const prevLoop = state.anyLoopActive; const prevLoop = state.anyLoopActive;
const prevTdr = state.anyTdrActive; const prevTdr = state.anyTdrActive;
const prevAlign = state.anyAlignmentActive; const prevCompare = state.anyComparisonActive;
const prevKsx = state.ksx.slice();
const prevKsy = state.ksy.slice();
const prevKsz = state.ksz.slice();
const to32 = (arr?: number[]) => { const to32 = (arr?: number[]) => {
if (!Array.isArray(arr)) return ZERO32.slice(); if (!Array.isArray(arr)) return ZERO32.slice();
const a = arr const a = arr
@@ -50,17 +60,43 @@ export const deviceEventsSlice = createSlice({
state.ksz = to32(action.payload.ksz); state.ksz = to32(action.payload.ksz);
state.anyLoopActive = state.ksx.some((v) => v === 1); state.anyLoopActive = state.ksx.some((v) => v === 1);
state.anyTdrActive = state.ksy.some((v) => v === 1); state.anyTdrActive = state.ksy.some((v) => v === 1);
state.anyAlignmentActive = state.ksz.some((v) => v === 1); state.anyComparisonActive = state.ksz.some((v) => v === 1);
// Transition detection to set/reset startedAt timestamps // Global transition detection
if (!prevLoop && state.anyLoopActive) state.loopStartedAt = Date.now(); if (!prevLoop && state.anyLoopActive) state.loopStartedAt = Date.now();
if (prevLoop && !state.anyLoopActive) state.loopStartedAt = null; if (prevLoop && !state.anyLoopActive) state.loopStartedAt = null;
if (!prevTdr && state.anyTdrActive) state.tdrStartedAt = Date.now(); if (!prevTdr && state.anyTdrActive) state.tdrStartedAt = Date.now();
if (prevTdr && !state.anyTdrActive) state.tdrStartedAt = null; if (prevTdr && !state.anyTdrActive) state.tdrStartedAt = null;
if (!prevAlign && state.anyAlignmentActive) if (!prevCompare && state.anyComparisonActive)
state.alignmentStartedAt = Date.now(); state.comparisonStartedAt = Date.now();
if (prevAlign && !state.anyAlignmentActive) if (prevCompare && !state.anyComparisonActive)
state.alignmentStartedAt = null; state.comparisonStartedAt = null;
// Per-slot transition detection
for (let i = 0; i < 32; i++) {
if (prevKsx[i] === 0 && state.ksx[i] === 1) {
// Only set if no existing (hydrated) timestamp
if (!state.loopStartedAtBySlot[i]) {
state.loopStartedAtBySlot[i] = Date.now();
}
} else if (prevKsx[i] === 1 && state.ksx[i] === 0) {
state.loopStartedAtBySlot[i] = null;
}
if (prevKsy[i] === 0 && state.ksy[i] === 1) {
if (!state.tdrStartedAtBySlot[i]) {
state.tdrStartedAtBySlot[i] = Date.now();
}
} else if (prevKsy[i] === 1 && state.ksy[i] === 0) {
state.tdrStartedAtBySlot[i] = null;
}
if (prevKsz[i] === 0 && state.ksz[i] === 1) {
if (!state.comparisonStartedAtBySlot[i]) {
state.comparisonStartedAtBySlot[i] = Date.now();
}
} else if (prevKsz[i] === 1 && state.ksz[i] === 0) {
state.comparisonStartedAtBySlot[i] = null;
}
}
}, },
resetEvents(state) { resetEvents(state) {
state.ksx = ZERO32.slice(); state.ksx = ZERO32.slice();
@@ -68,13 +104,39 @@ export const deviceEventsSlice = createSlice({
state.ksz = ZERO32.slice(); state.ksz = ZERO32.slice();
state.anyLoopActive = false; state.anyLoopActive = false;
state.anyTdrActive = false; state.anyTdrActive = false;
state.anyAlignmentActive = false; state.anyComparisonActive = false;
state.loopStartedAt = null; state.loopStartedAt = null;
state.tdrStartedAt = null; state.tdrStartedAt = null;
state.alignmentStartedAt = null; state.comparisonStartedAt = null;
state.loopStartedAtBySlot = Array.from({ length: 32 }, () => null);
state.tdrStartedAtBySlot = Array.from({ length: 32 }, () => null);
state.comparisonStartedAtBySlot = Array.from({ length: 32 }, () => null);
},
initPersistedTimings(
state,
action: PayloadAction<{
loop?: (number | null)[];
tdr?: (number | null)[];
compare?: (number | null)[]; // renamed key
}>
) {
const normalize = (arr?: (number | null)[]) => {
const out: (number | null)[] = Array.isArray(arr)
? arr.slice(0, 32).map((v) => (typeof v === "number" ? v : null))
: [];
while (out.length < 32) out.push(null);
return out;
};
if (action.payload.loop)
state.loopStartedAtBySlot = normalize(action.payload.loop);
if (action.payload.tdr)
state.tdrStartedAtBySlot = normalize(action.payload.tdr);
if (action.payload.compare)
state.comparisonStartedAtBySlot = normalize(action.payload.compare);
}, },
}, },
}); });
export const { setEvents, resetEvents } = deviceEventsSlice.actions; export const { setEvents, resetEvents, initPersistedTimings } =
deviceEventsSlice.actions;
export default deviceEventsSlice.reducer; export default deviceEventsSlice.reducer;