"use client";
import { createFileRoute, Link, useNavigate } from "@tanstack/react-router";
import { ArrowLeft, Clock, AlertTriangle, Pause, Play, Trash2 } 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,
} from "@/components/ui/alert-dialog";
import {
type ScheduledJob,
useCronjob,
useDeleteCronjob,
useUpdateCronjobStatus,
} from "@/hooks/use-cronjobs";
import { useAvailableMcps, useUserIntegrations } from "@/hooks/use-integrations";
import { syncAgentRuntime } from "@/lib/sync-agent-runtime";
export const Route = createFileRoute("/painel/cronjobs/$id")({
component: CronjobDetailPage,
});
function CronjobDetailPage() {
const { id } = Route.useParams();
const navigate = useNavigate();
const { data: job, isLoading, error } = useCronjob(id);
const { data: mcps = [] } = useAvailableMcps();
const { data: integrations = [] } = useUserIntegrations();
const updateMut = useUpdateCronjobStatus();
const deleteMut = useDeleteCronjob();
const [confirmDelete, setConfirmDelete] = useState(false);
if (isLoading) {
return
;
}
if (error || !job) {
return (
Automação não encontrada.
);
}
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";
async function toggle() {
try {
await updateMut.mutateAsync({
id: job!.id,
status: isActive ? "paused" : "active",
});
toast.success(isActive ? "Pausada." : "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");
}
}
async function remove() {
try {
await deleteMut.mutateAsync(job!.id);
toast.success("Excluída.");
const { error: syncError } = await syncAgentRuntime(job!.agent_instance_id, "cronjobs");
if (syncError) {
toast.warning("Automação removida, mas o runtime do agente não sincronizou.");
}
navigate({ to: "/painel/cronjobs" });
} catch (e) {
toast.error(e instanceof Error ? e.message : "Erro");
}
}
return (
{job.name}
{isActive &&
Ativa}
{job.status === "paused" &&
Pausada}
{job.status === "error" &&
Erro}
{isAutoPaused && (
Auto-pausada
)}
{hasRuntimeError && !isAutoPaused && (
Runtime com erro
)}
{isAutoPaused && job.auto_paused_reason && (
Auto-pausada
{job.auto_paused_reason}
)}
{hasRuntimeError && (
Falha reportada pelo runtime
{job.runtime_last_error ?? "O runtime marcou a última execução como erro."}
)}
{job.runtime_last_delivery_error && (
Falha na entrega do resultado
{job.runtime_last_delivery_error}
)}
{missingMcps.length > 0 && isActive && (
Integrações necessárias não conectadas:
{missingMcps.map((m) => (
- {m.name}
))}
)}
}
/>
{job.description &&
}
Ação enviada ao agente
{job.action_prompt}
Integrações necessárias
{requiredMcps.length === 0 ? (
Nenhuma.
) : (
{requiredMcps.map((m) => {
const ok = connectedMcpIds.has(m.id);
return (
{m.name} {ok ? "✓" : "(não conectado)"}
);
})}
)}
Excluir automação?
Esta ação remove permanentemente “{job.name}”.
Cancelar
{
e.preventDefault();
remove();
}}
disabled={deleteMut.isPending}
className="bg-destructive text-destructive-foreground hover:bg-destructive/90"
>
{deleteMut.isPending ? "Excluindo..." : "Excluir"}
);
}
function Section({
label,
value,
mono,
icon,
}: {
label: string;
value: string;
mono?: boolean;
icon?: React.ReactNode;
}) {
return (
);
}
function formatRuntimeState(state: ScheduledJob["runtime_state"]): string {
switch (state) {
case "scheduled":
return "Agendado";
case "paused":
return "Pausado";
case "completed":
return "Concluído";
case "error":
return "Erro";
default:
return "Desconhecido";
}
}
function formatRuntimeLastStatus(status: ScheduledJob["runtime_last_status"]): string {
switch (status) {
case "ok":
return "Sucesso";
case "error":
return "Erro";
default:
return "Ainda sem execução";
}
}