Files
MineGeo/components/ProcessingProgress.tsx

91 lines
3.1 KiB
TypeScript

"use client";
import { useI18n } from "@/components/I18nProvider";
import { useEffect, useState } from "react";
import {
processingStage,
type ProcessingJob,
} from "@/shared/processing-progress";
const completionDeadlines = new Map<string, number>();
export function ProcessingProgress({ job }: { job: ProcessingJob | null }) {
const { t } = useI18n();
const [now, setNow] = useState<number | null>(null);
const [visibleCompletion, setVisibleCompletion] = useState<string | null>(
null,
);
const complete = job?.kind === "map" && job.state === "complete";
const completionKey = complete
? `minegeo:map-refresh-notice:${job.id ?? job.updated_at}`
: null;
useEffect(() => {
setVisibleCompletion(null);
if (!completionKey) return;
let deadline = completionDeadlines.get(completionKey);
try {
const saved = Number(localStorage.getItem(completionKey));
if (Number.isFinite(saved) && saved > 0) deadline = saved;
} catch {
// Keep the same deadline during navigation if browser storage is blocked.
}
deadline ??= Date.now() + 10_000;
completionDeadlines.set(completionKey, deadline);
try {
localStorage.setItem(completionKey, String(deadline));
} catch {}
const remaining = deadline - Date.now();
if (remaining <= 0) return;
setVisibleCompletion(completionKey);
const timer = setTimeout(() => setVisibleCompletion(null), remaining);
return () => clearTimeout(timer);
}, [completionKey]);
useEffect(() => {
setNow(Date.now());
if (job?.state !== "running") return;
const timer = setInterval(() => setNow(Date.now()), 5000);
return () => clearInterval(timer);
}, [job?.state]);
if (
!job ||
(complete
? visibleCompletion !== completionKey
: !["queued", "running"].includes(job.state))
)
return null;
const progress = complete ? 100 : Math.max(0, Math.min(100, job.progress));
const stopped =
now !== null &&
job.state === "running" &&
(!job.lease_until || Date.parse(job.lease_until) < now);
return (
<div className="processing-progress" role="status" aria-live="polite">
<p>
{complete
? `100% · ${t("Guessing map refreshed successfully")}`
: job.state === "queued"
? t("Waiting for a processing worker")
: `${progress}% · ${job.kind === "map" ? t("Refreshing guessing map") : t(processingStage(progress))}`}
</p>
<progress
max={100}
value={progress}
aria-label={t("Map processing progress")}
/>
<p className="muted">
{complete
? t("The updated guessing map is ready to preview.")
: stopped
? t(
"The worker has stopped reporting. Check its terminal and restart it if necessary. Your uploaded export is saved.",
)
: job.state === "running"
? t(
"Worker connected. Large exports can take a while; progress measures completed work.",
)
: t("Processing will start when a worker is available.")}
</p>
</div>
);
}