"use client"; import { useState } from "react"; import { Link, useNavigate } from "@tanstack/react-router"; import { useMutation, useQueryClient } from "@tanstack/react-query"; import { MoreVertical, Edit, Play, Power, Copy, Archive, RotateCcw, Trash2 } from "lucide-react"; import { formatDistanceToNow } from "date-fns"; import { ptBR } from "date-fns/locale"; import { toast } from "sonner"; import { supabase } from "@/integrations/supabase/client"; import { syncAgentSkills } from "@/lib/sync-agent-skills"; import { deleteSkill } from "@/lib/delete-skill"; import { Button } from "@/components/ui/button"; import { DropdownMenu, DropdownMenuContent, DropdownMenuItem, DropdownMenuSeparator, DropdownMenuTrigger, } from "@/components/ui/dropdown-menu"; import { AlertDialog, AlertDialogAction, AlertDialogCancel, AlertDialogContent, AlertDialogDescription, AlertDialogFooter, AlertDialogHeader, AlertDialogTitle, } from "@/components/ui/alert-dialog"; import { SkillStatusBadge } from "./SkillStatusBadge"; import type { Skill } from "@/hooks/use-skills"; import { SkillTestPanel } from "./SkillTestPanel"; export function SkillCard({ skill }: { skill: Skill }) { const navigate = useNavigate(); const qc = useQueryClient(); const [confirmArchive, setConfirmArchive] = useState(false); const [confirmDelete, setConfirmDelete] = useState(false); const [testOpen, setTestOpen] = useState(false); const isArchived = skill.status === "archived"; async function syncRuntimeAfterMutation(actionLabel: string) { const { error } = await syncAgentSkills(skill.agent_instance_id); if (error) { toast.warning(`${actionLabel}, mas o sync com o container falhou.`, { description: error.message, }); } } const updateStatus = useMutation({ mutationFn: async (newStatus: string) => { const { error } = await supabase .from("skills") .update({ status: newStatus, updated_at: new Date().toISOString() }) .eq("id", skill.id); if (error) throw error; }, onSuccess: () => { qc.invalidateQueries({ queryKey: ["skills"] }); qc.invalidateQueries({ queryKey: ["user-limits"] }); }, }); const handleToggleActive = () => { const next = skill.status === "active" ? "disabled" : "active"; updateStatus.mutate(next, { onSuccess: async () => { const actionLabel = next === "active" ? "Skill ativada" : "Skill desativada"; toast.success(actionLabel); await syncRuntimeAfterMutation(actionLabel); }, onError: (e: unknown) => toast.error(e instanceof Error ? e.message : "Erro ao atualizar"), }); }; const handleArchive = () => { handleArchiveMutation.mutate("archive"); }; const handleArchiveMutation = useMutation({ mutationFn: async (action: "archive" | "delete") => { const { data, error } = await deleteSkill(skill.id, action); if (error) throw new Error(error.message); if (!data?.success) throw new Error("Erro ao atualizar skill."); return data; }, onSuccess: (data, action) => { if (data.runtime_sync_warning) { toast.warning( action === "delete" ? "Skill arquivada; exclusão final do runtime pendente." : "Skill arquivada no painel; sync com o runtime pendente.", ); } else { toast.success(action === "delete" ? "Skill deletada" : "Skill arquivada"); } if (action === "archive") { setConfirmArchive(false); } else { setConfirmDelete(false); } qc.invalidateQueries({ queryKey: ["skills"] }); qc.invalidateQueries({ queryKey: ["user-limits"] }); }, onError: (e: unknown) => toast.error(e instanceof Error ? e.message : "Erro ao atualizar"), }); const handleRestore = () => { updateStatus.mutate("draft", { onSuccess: async () => { toast.success("Skill restaurada como rascunho"); await syncRuntimeAfterMutation("Skill restaurada como rascunho"); }, onError: (e: unknown) => { if ((e as { code?: string })?.code === "23505") { toast.error("Já existe outra skill ativa com esse nome. Renomeie antes de restaurar."); } else { toast.error(e instanceof Error ? e.message : "Erro ao restaurar"); } }, }); }; const handleDelete = useMutation({ mutationFn: async () => { const { data, error } = await deleteSkill(skill.id, "delete"); if (error) throw new Error(error.message); if (!data?.success) throw new Error("Erro ao deletar skill."); return data; }, onSuccess: (data) => { if (data.runtime_sync_warning) { toast.warning("Skill arquivada; exclusão final do runtime pendente."); } else { toast.success("Skill deletada"); } qc.invalidateQueries({ queryKey: ["skills"] }); qc.invalidateQueries({ queryKey: ["user-limits"] }); setConfirmDelete(false); }, onError: (e: unknown) => toast.error(e instanceof Error ? e.message : "Erro ao deletar"), }); return ( <>

{skill.description}

{isArchived ? ( <> Restaurar setConfirmDelete(true)} className="cursor-pointer text-destructive focus:text-destructive" > Deletar ) : ( <> Editar setTestOpen(true)} disabled={!skill.current_version_id} className="cursor-pointer" > Testar {skill.status === "active" ? "Desativar" : "Ativar"} Duplicar setConfirmArchive(true)} className="cursor-pointer text-destructive focus:text-destructive" > Arquivar )}
{(skill as Skill & { is_default?: boolean }).is_default && ( Padrão )}
{formatDistanceToNow(new Date(skill.updated_at), { addSuffix: true, locale: ptBR })}
Arquivar esta skill? A skill {skill.name} ficará invisível para o agente. Você poderá restaurá-la depois sem perder o histórico. Cancelar Arquivar Deletar definitivamente? Esta ação remove {skill.name} e todo o histórico de versões. Não é possível desfazer. Cancelar handleDelete.mutate()} className="bg-destructive text-destructive-foreground hover:bg-destructive/90" > Deletar {testOpen && skill.current_version_id && ( )} ); }