import { useCallback } from "react"; import { ListChecks } from "lucide-react"; import { Link } from "react-router"; import { DashboardWidgetList, DismissibleAlert, LoadingFrame, StatusBadge, useDashboardWidgetData, type ApiSettings, type DashboardWidgetConfiguration } from "@govoplan/core-webui"; import { listWorkflowInstances, type WorkflowInstance, type WorkflowInstanceStep } from "../../api/workflow"; export default function WorkflowOpenWorkWidget({ settings, refreshKey, configuration }: { settings: ApiSettings; refreshKey: number; configuration: DashboardWidgetConfiguration; }) { const maxItems = numberSetting(configuration.maxItems, 6, 1, 20); const includeRunning = configuration.includeRunning !== false; const load = useCallback(async () => { const instances = await listWorkflowInstances(settings); return instances .filter((instance) => instance.status === "waiting" || (includeRunning && instance.status === "running") ) .sort(compareOpenWork) .slice(0, maxItems); }, [includeRunning, maxItems, settings]); const { data: instances, loading, error } = useDashboardWidgetData( load, refreshKey ); return ( {error && ( {error} )} { const step = currentStep(instance); return { id: instance.id, title: instance.definition_name, detail: handoffTitle(step), meta: updatedLabel(instance.updated_at), leading: ); } function currentStep( instance: WorkflowInstance ): WorkflowInstanceStep | null { return instance.steps.find( (step) => step.id === instance.current_step_id ) ?? null; } function handoffTitle(step: WorkflowInstanceStep | null): string { const title = step?.handoff.title; if (typeof title === "string" && title.trim()) return title; if (!step) return "Preparing next step"; return step.node_type .replace(/^workflow\./, "") .split(".") .join(" "); } function workflowRunUrl(instance: WorkflowInstance): string { const query = new URLSearchParams({ definition: instance.definition_id, run: instance.id }); return `/workflow?${query.toString()}`; } function compareOpenWork( left: WorkflowInstance, right: WorkflowInstance ): number { if (left.status !== right.status) { return left.status === "waiting" ? -1 : 1; } return ( new Date(right.updated_at).getTime() - new Date(left.updated_at).getTime() ); } function updatedLabel(value: string): string { return new Intl.DateTimeFormat(undefined, { day: "2-digit", month: "short", hour: "2-digit", minute: "2-digit" }).format(new Date(value)); } function numberSetting( value: unknown, fallback: number, minimum: number, maximum: number ): number { const numeric = typeof value === "number" ? value : Number(value); return Number.isFinite(numeric) ? Math.max(minimum, Math.min(maximum, Math.floor(numeric))) : fallback; }