import { useCallback, useEffect, useMemo, useState } from "react"; import { AlertTriangle, Check, Circle, Clock3, ExternalLink, Play, RefreshCw, RotateCcw, XCircle } from "lucide-react"; import { ActionToolbar, Button, ConfirmDialog, Dialog, DismissibleAlert, ContentGrid, FormField, IconButton, LoadingFrame, StageRail, StatusBadge, dispatchWorkflowViewChanged, usePlatformUiCapability, type StageRailTone, type ApiSettings, type ViewsRuntimeUiCapability } from "@govoplan/core-webui"; import { cancelWorkflowInstance, listWorkflowInstances, reconcileWorkflowInstance, resolveWorkflowStep, startWorkflowInstance, type WorkflowDefinition, type WorkflowInstance, type WorkflowInstanceStep } from "../../api/workflow"; type WorkflowAction = | "complete" | "approve" | "changes" | "reject" | "resume" | "retry" | "confirm_effect" | "confirm_absent" | "cancel"; export default function WorkflowRunsDialog({ open, settings, definition, initialInstanceId, canStart, canTransition, onClose }: { open: boolean; settings: ApiSettings; definition: WorkflowDefinition | null; initialInstanceId?: string | null; canStart: boolean; canTransition: boolean; onClose: () => void; }) { const [instances, setInstances] = useState([]); const [selectedId, setSelectedId] = useState(null); const [loading, setLoading] = useState(false); const [working, setWorking] = useState(false); const [error, setError] = useState(""); const [comment, setComment] = useState(""); const [evidence, setEvidence] = useState(""); const [cancelOpen, setCancelOpen] = useState(false); const viewsRuntime = usePlatformUiCapability( "views.runtime" ); const selected = useMemo( () => instances.find((item) => item.id === selectedId) ?? instances[0] ?? null, [instances, selectedId] ); const currentStep = useMemo( () => currentInstanceStep(selected), [selected] ); const allowedActions = useMemo( () => handoffActions(currentStep), [currentStep] ); const mergeInstance = useCallback((instance: WorkflowInstance) => { setInstances((current) => [ instance, ...current.filter((item) => item.id !== instance.id) ]); setSelectedId(instance.id); }, []); const load = useCallback(async () => { if (!open || !definition?.id) return; setLoading(true); setError(""); try { const items = await listWorkflowInstances(settings, definition.id); setInstances(items); setSelectedId((current) => ( initialInstanceId && items.some((item) => item.id === initialInstanceId) ? initialInstanceId : items.some((item) => item.id === current) ? current : items[0]?.id ?? null )); } catch (loadError) { setError(errorMessage(loadError)); } finally { setLoading(false); } }, [definition?.id, initialInstanceId, open, settings]); useEffect(() => { if (!open) return; setComment(""); setEvidence(""); void load(); }, [load, open]); useEffect(() => { if (!open || !selected) return; const context = selected.view_context; if (!context || !viewsRuntime) { dispatchWorkflowViewChanged(null); return; } let cancelled = false; void viewsRuntime.resolveWorkflowView(settings, { viewId: context.view_id, revisionId: context.revision_id, visibleSurfaceIds: context.visible_surface_ids }).then((projection) => { if (!cancelled) { dispatchWorkflowViewChanged(projection, selected.id); } }).catch((viewError) => { if (!cancelled) { dispatchWorkflowViewChanged(null); setError(errorMessage(viewError)); } }); return () => { cancelled = true; }; }, [ open, selected?.id, selected?.updated_at, settings, viewsRuntime ]); useEffect(() => { if ( !open || !canTransition || !selected || !currentStep || currentStep.node_type !== "workflow.dataflow" || !["queued", "retrying", "running"].includes( String(currentStep.handoff.state ?? "") ) ) { return; } let stopped = false; const poll = window.setInterval(() => { void reconcileWorkflowInstance(settings, selected.id) .then((instance) => { if (!stopped) mergeInstance(instance); }) .catch((pollError) => { if (!stopped) setError(errorMessage(pollError)); }); }, 2500); return () => { stopped = true; window.clearInterval(poll); }; }, [ canTransition, currentStep, mergeInstance, open, selected, settings ]); const start = async () => { if (!definition?.id) return; setWorking(true); setError(""); try { const instance = await startWorkflowInstance(settings, definition.id, { idempotency_key: crypto.randomUUID(), input: {} }); mergeInstance(instance); } catch (startError) { setError(errorMessage(startError)); } finally { setWorking(false); } }; const refreshSelected = async () => { if (!selected) { await load(); return; } setWorking(true); setError(""); try { const instance = canTransition ? await reconcileWorkflowInstance(settings, selected.id) : (await listWorkflowInstances(settings, definition?.id)) .find((item) => item.id === selected.id); if (instance) mergeInstance(instance); else await load(); } catch (refreshError) { setError(errorMessage(refreshError)); } finally { setWorking(false); } }; const performAction = async (action: WorkflowAction) => { if (!selected || !currentStep) return; setWorking(true); setError(""); try { const instance = await resolveWorkflowStep( settings, selected.id, currentStep.id, { action, comment: comment.trim() || null, evidence: evidence .split("\n") .map((item) => item.trim()) .filter(Boolean) } ); mergeInstance(instance); setComment(""); setEvidence(""); } catch (actionError) { setError(errorMessage(actionError)); } finally { setWorking(false); } }; const cancel = async () => { if (!selected) return; setWorking(true); setError(""); try { mergeInstance(await cancelWorkflowInstance(settings, selected.id)); setCancelOpen(false); } catch (cancelError) { setError(errorMessage(cancelError)); } finally { setWorking(false); } }; const actionUrl = typeof currentStep?.handoff.action_url === "string" ? currentStep.handoff.action_url : ""; const unavailableOptionalCapabilities = Array.isArray( currentStep?.handoff.unavailable_optional_capabilities ) ? currentStep.handoff.unavailable_optional_capabilities.filter( (value): value is string => typeof value === "string" && Boolean(value) ) : []; const close = () => { dispatchWorkflowViewChanged(null); onClose(); }; return ( <> Close} > Workflow instances Revision-pinned runs and human handoffs } variant="ghost" onClick={() => void refreshSelected()} disabled={loading || working} /> {error ? ( {error} ) : null}
{instances.map((instance) => ( ))} {!instances.length ? (
No runs yet
) : null}
{selected ? ( <>
{selected.definition_name} Revision {selected.definition_revision} · {selected.definition_hash.slice(0, 12)} {["running", "waiting"].includes(selected.status) ? ( } variant="danger" onClick={() => setCancelOpen(true)} disabled={!canTransition || working} /> ) : null}
{selected.error ? ( {selected.error} ) : null} {selected.view_context && !viewsRuntime ? ( The focused View is unavailable. Workflow position is preserved; use the linked module action or ask an administrator to enable Views. ) : null} {currentStep ? (
{String( currentStep.handoff.title ?? currentStep.handoff.kind ?? currentStep.node_type )} Step {currentStep.sequence} · attempt {currentStep.attempt}
{typeof currentStep.handoff.message === "string" ? (

{currentStep.handoff.message}

) : null} {typeof currentStep.handoff.instructions === "string" && currentStep.handoff.instructions ? (

{currentStep.handoff.instructions}

) : null} {unavailableOptionalCapabilities.length ? ( Optional integration unavailable: {unavailableOptionalCapabilities.join(", ")}. The hand-off remains valid and resumable. ) : null} {actionUrl ? ( Open linked work ) : null} {allowedActions.some((action) => action !== "cancel") ? (