mirror of
https://github.com/mgeeky/decode-spam-headers.git
synced 2026-02-22 05:23:31 +01:00
MAESTRO: add live countdown timer to progress indicator
This commit is contained in:
@@ -43,7 +43,7 @@ Frontend uses `fetch` with `ReadableStream` reader (not native `EventSource`, wh
|
|||||||
- [x] All vitest tests pass: `npx vitest run src/__tests__/ProgressIndicator.test.tsx src/__tests__/useAnalysis.test.ts`
|
- [x] All vitest tests pass: `npx vitest run src/__tests__/ProgressIndicator.test.tsx src/__tests__/useAnalysis.test.ts`
|
||||||
- [x] Submitting headers triggers backend analysis with SSE streaming
|
- [x] Submitting headers triggers backend analysis with SSE streaming
|
||||||
- [x] Progress bar updates in real-time showing current test name and percentage
|
- [x] Progress bar updates in real-time showing current test name and percentage
|
||||||
- [ ] Countdown timer counts down from 30 seconds
|
- [x] Countdown timer counts down from 30 seconds
|
||||||
- [ ] Partial failures show inline error indicators per FR-25
|
- [ ] Partial failures show inline error indicators per FR-25
|
||||||
- [ ] Timeout at 30s displays partial results with notification listing incomplete tests
|
- [ ] Timeout at 30s displays partial results with notification listing incomplete tests
|
||||||
- [ ] Empty input returns 400, oversized >1MB returns 413
|
- [ ] Empty input returns 400, oversized >1MB returns 413
|
||||||
|
|||||||
@@ -1,5 +1,6 @@
|
|||||||
"use client";
|
"use client";
|
||||||
|
|
||||||
|
import { useEffect, useRef, useState } from "react";
|
||||||
import { FontAwesomeIcon } from "@fortawesome/react-fontawesome";
|
import { FontAwesomeIcon } from "@fortawesome/react-fontawesome";
|
||||||
import { faSpinner } from "@fortawesome/free-solid-svg-icons";
|
import { faSpinner } from "@fortawesome/free-solid-svg-icons";
|
||||||
|
|
||||||
@@ -62,7 +63,42 @@ export default function ProgressIndicator({
|
|||||||
timeoutSeconds,
|
timeoutSeconds,
|
||||||
incompleteTests = [],
|
incompleteTests = [],
|
||||||
}: ProgressIndicatorProps) {
|
}: ProgressIndicatorProps) {
|
||||||
const elapsedSeconds = progress ? Math.floor(progress.elapsedMs / 1000) : 0;
|
const [nowMs, setNowMs] = useState(() => Date.now());
|
||||||
|
const anchorRef = useRef<{ elapsedMs: number; timestamp: number } | null>(null);
|
||||||
|
|
||||||
|
useEffect(() => {
|
||||||
|
if (status !== "analysing") {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
const interval = window.setInterval(() => {
|
||||||
|
setNowMs(Date.now());
|
||||||
|
}, 1000);
|
||||||
|
|
||||||
|
return () => {
|
||||||
|
window.clearInterval(interval);
|
||||||
|
};
|
||||||
|
}, [status]);
|
||||||
|
|
||||||
|
useEffect(() => {
|
||||||
|
if (!progress || status !== "analysing") {
|
||||||
|
anchorRef.current = null;
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
anchorRef.current = {
|
||||||
|
elapsedMs: progress.elapsedMs,
|
||||||
|
timestamp: Date.now(),
|
||||||
|
};
|
||||||
|
}, [progress?.elapsedMs, status]);
|
||||||
|
|
||||||
|
const baseElapsedMs = progress?.elapsedMs ?? 0;
|
||||||
|
const anchor = anchorRef.current;
|
||||||
|
const elapsedMs =
|
||||||
|
status === "analysing" && progress && anchor
|
||||||
|
? anchor.elapsedMs + Math.max(0, nowMs - anchor.timestamp)
|
||||||
|
: baseElapsedMs;
|
||||||
|
const elapsedSeconds = Math.floor(elapsedMs / 1000);
|
||||||
const remainingSeconds = Math.max(0, timeoutSeconds - elapsedSeconds);
|
const remainingSeconds = Math.max(0, timeoutSeconds - elapsedSeconds);
|
||||||
const percentage = progress ? Math.round(progress.percentage) : 0;
|
const percentage = progress ? Math.round(progress.percentage) : 0;
|
||||||
const variant = getVariant(status, remainingSeconds, timeoutSeconds);
|
const variant = getVariant(status, remainingSeconds, timeoutSeconds);
|
||||||
|
|||||||
Reference in New Issue
Block a user