133 lines
4.7 KiB
TypeScript
133 lines
4.7 KiB
TypeScript
"use client";
|
|
import React, { useEffect, useState } from "react";
|
|
import { useAppSelector } from "@/redux/store";
|
|
|
|
export default function GlobalActivityOverlay() {
|
|
const anyLoop = useAppSelector((s) => s.deviceEvents.anyLoopActive);
|
|
const anyTdr = useAppSelector((s) => s.deviceEvents.anyTdrActive);
|
|
const anyAlign = useAppSelector((s) => s.deviceEvents.anyAlignmentActive);
|
|
const ksx = useAppSelector((s) => s.deviceEvents.ksx);
|
|
const ksy = useAppSelector((s) => s.deviceEvents.ksy);
|
|
const ksz = useAppSelector((s) => s.deviceEvents.ksz);
|
|
const loopStartedAt = useAppSelector((s) => s.deviceEvents.loopStartedAt);
|
|
const tdrStartedAt = useAppSelector((s) => s.deviceEvents.tdrStartedAt);
|
|
const alignmentStartedAt = useAppSelector(
|
|
(s) => s.deviceEvents.alignmentStartedAt
|
|
);
|
|
|
|
const fmt = (arr: number[]) =>
|
|
arr
|
|
.map((v, i) => (v ? i + 1 : 0))
|
|
.filter((n) => n !== 0)
|
|
.join(", ");
|
|
|
|
// Simple 1s ticker so progress bars advance while overlay is shown
|
|
const [now, setNow] = useState<number>(Date.now());
|
|
useEffect(() => {
|
|
const active = anyLoop || anyTdr || anyAlign;
|
|
if (!active) return;
|
|
const id = setInterval(() => setNow(Date.now()), 1000);
|
|
return () => clearInterval(id);
|
|
}, [anyLoop, anyTdr, anyAlign]);
|
|
|
|
const active = anyLoop || anyTdr || anyAlign;
|
|
if (!active) return null;
|
|
|
|
const clamp = (v: number, min = 0, max = 1) =>
|
|
Math.max(min, Math.min(max, v));
|
|
|
|
const compute = (startedAt: number | null, durationMs: number) => {
|
|
if (!startedAt) return { pct: 0, remaining: durationMs };
|
|
const elapsed = now - startedAt;
|
|
const pct = clamp(elapsed / durationMs) * 100;
|
|
const remaining = Math.max(0, durationMs - Math.max(0, elapsed));
|
|
return { pct, remaining };
|
|
};
|
|
|
|
// Durations
|
|
const LOOP_MS = 2 * 60 * 1000; // ~2 min
|
|
const TDR_MS = 30 * 1000; // ~30 s
|
|
const ALIGN_MS = 10 * 60 * 1000; // ~10 min
|
|
|
|
return (
|
|
<div className="fixed inset-0 z-[2000] flex items-center justify-center bg-white/70 backdrop-blur-sm">
|
|
<div className="p-4 rounded-md shadow bg-white border border-gray-200 w-[min(90vw,680px)]">
|
|
<div className="font-semibold mb-3">Bitte warten…</div>
|
|
<div className="space-y-3">
|
|
{anyLoop && (
|
|
<div>
|
|
<div className="text-sm text-gray-800 mb-1">
|
|
Schleifenmessung läuft… (KÜ: {fmt(ksx)})
|
|
</div>
|
|
{(() => {
|
|
const { pct } = compute(loopStartedAt, LOOP_MS);
|
|
return (
|
|
<div>
|
|
<div className="h-2 w-full bg-gray-200 rounded overflow-hidden">
|
|
<div
|
|
className="h-full bg-littwin-blue transition-all"
|
|
style={{ width: `${pct}%` }}
|
|
/>
|
|
</div>
|
|
<div className="text-xs text-gray-600 mt-1">
|
|
{Math.round(pct)}%
|
|
</div>
|
|
</div>
|
|
);
|
|
})()}
|
|
</div>
|
|
)}
|
|
|
|
{anyTdr && (
|
|
<div>
|
|
<div className="text-sm text-gray-800 mb-1">
|
|
TDR-Messung läuft… (KÜ: {fmt(ksy)})
|
|
</div>
|
|
{(() => {
|
|
const { pct } = compute(tdrStartedAt, TDR_MS);
|
|
return (
|
|
<div>
|
|
<div className="h-2 w-full bg-gray-200 rounded overflow-hidden">
|
|
<div
|
|
className="h-full bg-littwin-blue transition-all"
|
|
style={{ width: `${pct}%` }}
|
|
/>
|
|
</div>
|
|
<div className="text-xs text-gray-600 mt-1">
|
|
{Math.round(pct)}%
|
|
</div>
|
|
</div>
|
|
);
|
|
})()}
|
|
</div>
|
|
)}
|
|
|
|
{anyAlign && (
|
|
<div>
|
|
<div className="text-sm text-gray-800 mb-1">
|
|
Abgleich läuft… (KÜ: {fmt(ksz)}) kann bis zu 10 Minuten dauern
|
|
</div>
|
|
{(() => {
|
|
const { pct } = compute(alignmentStartedAt, ALIGN_MS);
|
|
return (
|
|
<div>
|
|
<div className="h-2 w-full bg-gray-200 rounded overflow-hidden">
|
|
<div
|
|
className="h-full bg-littwin-blue transition-all"
|
|
style={{ width: `${pct}%` }}
|
|
/>
|
|
</div>
|
|
<div className="text-xs text-gray-600 mt-1">
|
|
{Math.round(pct)}%
|
|
</div>
|
|
</div>
|
|
);
|
|
})()}
|
|
</div>
|
|
)}
|
|
</div>
|
|
</div>
|
|
</div>
|
|
);
|
|
}
|