import { useEffect, useState } from "react"; import { Button, DescriptionList, Dialog, DismissibleAlert, MetricCard, MetricGrid, i18nMessage } from "@govoplan/core-webui"; import type { ApiSettings } from "../../../types"; import { getCampaignDeliveryProgress, type CampaignDeliveryProgress } from "../../../api/campaigns"; export type DeliveryProgressRequestState = "running" | "finished" | "interrupted"; /** Only this small dialog polls; the workspace, attachments and report stay still. */ export default function CampaignDeliveryProgressDialog({ settings, campaignId, versionId, kind, requestState, requestError, onClose }: { settings: ApiSettings; campaignId: string; versionId: string; kind: "smtp" | "imap"; requestState: DeliveryProgressRequestState; requestError?: string; onClose: () => void; }) { const scope = JSON.stringify([settings.apiBaseUrl, settings.apiKey, settings.accessToken, campaignId, versionId]); const [snapshot, setSnapshot] = useState<{ scope: string; progress: CampaignDeliveryProgress } | null>(null); const [pollError, setPollError] = useState(false); const progress = snapshot?.scope === scope ? snapshot.progress : null; const running = requestState === "running"; useEffect(() => { let stopped = false; let timer: ReturnType | undefined; const controller = new AbortController(); setPollError(false); async function poll() { let succeeded = false; try { const result = await getCampaignDeliveryProgress(settings, campaignId, versionId, controller.signal); if (stopped) return; if (result.campaign_id !== campaignId || result.version_id !== versionId) throw new Error("Progress context changed"); setSnapshot({ scope, progress: result }); setPollError(false); succeeded = true; } catch { if (stopped) return; // A read failure never retries or changes the authoritative send/append. setPollError(true); } if (!stopped && (requestState !== "finished" || !succeeded)) timer = setTimeout(() => void poll(), 2000); } void poll(); return () => { stopped = true; controller.abort(); if (timer) clearTimeout(timer); }; }, [scope, requestState]); const counts = progress?.[kind]; const accepted = progress ? kind === "smtp" ? progress.smtp.accepted : progress.imap.appended : null; const label = kind === "smtp" ? "i18n:govoplan-campaign.delivery_progress_smtp" : "i18n:govoplan-campaign.delivery_progress_imap"; return i18n:govoplan-campaign.close.bbfa773e}>

{counts ? i18nMessage("i18n:govoplan-campaign.delivery_progress_processed", { value0: counts.processed, value1: counts.total }) : "i18n:govoplan-campaign.delivery_progress_loading"}

{running ? "i18n:govoplan-campaign.delivery_progress_running" : requestState === "finished" ? "i18n:govoplan-campaign.delivery_progress_finished" : "i18n:govoplan-campaign.delivery_progress_interrupted"}

{pollError && i18n:govoplan-campaign.delivery_progress_stale} {requestError && {requestError}} {kind === "smtp" && <>
i18n:govoplan-campaign.delivery_progress_paused
{progress?.smtp.paused ?? "—"}
i18n:govoplan-campaign.delivery_progress_cancelled
{progress?.smtp.cancelled ?? "—"}
}
i18n:govoplan-campaign.delivery_progress_updated
{progress?.generated_at ? new Date(progress.generated_at).toLocaleTimeString() : "—"}

i18n:govoplan-campaign.delivery_progress_scope

; }