"use client"; import { Link } from "@tanstack/react-router"; import { Pause, Play, Trash2, Clock, AlertTriangle } from "lucide-react"; import { toast } from "sonner"; import { useState } from "react"; import { Button } from "@/components/ui/button"; import { Badge } from "@/components/ui/badge"; import { AlertDialog, AlertDialogAction, AlertDialogCancel, AlertDialogContent, AlertDialogDescription, AlertDialogFooter, AlertDialogHeader, AlertDialogTitle, AlertDialogTrigger, } from "@/components/ui/alert-dialog"; import { type ScheduledJob, useDeleteCronjob, useUpdateCronjobStatus, } from "@/hooks/use-cronjobs"; import { useAvailableMcps, useUserIntegrations } from "@/hooks/use-integrations"; import { syncAgentRuntime } from "@/lib/sync-agent-runtime"; interface Props { job: ScheduledJob; } export function CronjobCard({ job }: Props) { const updateMut = useUpdateCronjobStatus(); const deleteMut = useDeleteCronjob(); const { data: mcps = [] } = useAvailableMcps(); const { data: integrations = [] } = useUserIntegrations(); const [confirmDelete, setConfirmDelete] = useState(false); const connectedMcpIds = new Set( integrations.filter((i) => i.status === "active").map((i) => i.mcp_id), ); const requiredMcps = job.required_mcp_slugs .map((slug) => mcps.find((m) => m.slug === slug)) .filter((m): m is NonNullable => !!m); const missingMcps = requiredMcps.filter((m) => !connectedMcpIds.has(m.id)); const isActive = job.status === "active"; const isAutoPaused = job.status === "auto_paused"; const hasRuntimeError = job.runtime_state === "error" || job.runtime_last_status === "error"; const runtimeErrorText = job.runtime_last_delivery_error ?? job.runtime_last_error; async function toggle() { try { await updateMut.mutateAsync({ id: job.id, status: isActive ? "paused" : "active", }); toast.success(isActive ? "Automação pausada." : "Automação ativada."); const { error: syncError } = await syncAgentRuntime(job.agent_instance_id, "cronjobs"); if (syncError) { toast.warning("Status salvo, mas o runtime do agente não sincronizou."); } } catch (e) { toast.error(e instanceof Error ? e.message : "Erro ao atualizar."); } } async function remove() { try { const result = await deleteMut.mutateAsync(job.id); if (result.runtime_sync_warning) { toast.warning("Automação removida do painel; limpeza final do runtime pendente."); } else { toast.success("Automação excluída."); } setConfirmDelete(false); } catch (e) { toast.error(e instanceof Error ? e.message : "Erro ao excluir."); } } return (

{job.name}

{isActive && Ativa} {job.status === "paused" && Pausada} {job.status === "error" && Erro} {isAutoPaused && ( Auto-pausada )} {hasRuntimeError && !isAutoPaused && ( Runtime com erro )}

{job.human_readable}

{isAutoPaused && job.auto_paused_reason && (

{job.auto_paused_reason}

)} {hasRuntimeError && runtimeErrorText && (

{runtimeErrorText}

)} {missingMcps.length > 0 && isActive && (
Integrações faltando:{" "} {missingMcps.map((m) => m.name).join(", ")}
)} {requiredMcps.length > 0 && (
{requiredMcps.map((m) => { const ok = connectedMcpIds.has(m.id); return ( {m.name} ); })}
)}
{job.cron_expression} {job.next_run_at && ( {new Date(job.next_run_at).toLocaleString("pt-BR", { timeZone: job.timezone, dateStyle: "short", timeStyle: "short", })} )}
Excluir automação? Esta ação remove permanentemente a automação “{job.name}”. Cancelar { e.preventDefault(); remove(); }} disabled={deleteMut.isPending} className="bg-destructive text-destructive-foreground hover:bg-destructive/90" > {deleteMut.isPending ? "Excluindo..." : "Excluir"}
); }