From 70cba994244027abfd6f6fc5a8f7145112050481 Mon Sep 17 00:00:00 2001 From: rever-tecnologia Date: Thu, 18 Dec 2025 10:51:37 -0300 Subject: [PATCH] feat(automations): historico expandivel com detalhes das acoes MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit - Adiciona linhas expandiveis no historico de execucoes - Mostra detalhes completos de cada acao (destinatarios, assunto, etc.) - Salva mais informacoes no backend para acoes de e-mail - Remove log de progresso do dashboard de reports 🤖 Generated with [Claude Code](https://claude.com/claude-code) Co-Authored-By: Claude Opus 4.5 --- convex/automations.ts | 6 + convex/reports.ts | 7 +- .../automations/automation-runs-dialog.tsx | 305 ++++++++++++++++-- 3 files changed, 279 insertions(+), 39 deletions(-) diff --git a/convex/automations.ts b/convex/automations.ts index b3ad86b..59216b2 100644 --- a/convex/automations.ts +++ b/convex/automations.ts @@ -1012,8 +1012,14 @@ async function applyActions( applied.push({ type: "SEND_EMAIL", details: { + recipients: to, toCount: to.length, + subject, + messagePreview: message.length > 100 ? `${message.slice(0, 100)}...` : message, ctaTarget: effectiveTarget, + ctaLabel, + ctaUrl, + scheduledAt: Date.now(), }, }) } diff --git a/convex/reports.ts b/convex/reports.ts index 2f16dbe..563a082 100644 --- a/convex/reports.ts +++ b/convex/reports.ts @@ -161,11 +161,8 @@ async function releaseDashboardLock(ctx: MutationCtx, lockId: Id<"analyticsLocks } } -function logDashboardProgress(processed: number, tenantId: string) { - const rssMb = Math.round((process.memoryUsage().rss ?? 0) / (1024 * 1024)); - console.log( - `[reports] dashboardAggregate tenant=${tenantId} processed=${processed} rssMB=${rssMb}`, - ); +function logDashboardProgress(_processed: number, _tenantId: string) { + // Log de progresso removido para reduzir ruido no console } function mapToChronologicalSeries(map: Map) { diff --git a/src/components/automations/automation-runs-dialog.tsx b/src/components/automations/automation-runs-dialog.tsx index 49c9c86..0e507e0 100644 --- a/src/components/automations/automation-runs-dialog.tsx +++ b/src/components/automations/automation-runs-dialog.tsx @@ -1,13 +1,15 @@ "use client" -import { useMemo, useState } from "react" +import { Fragment, useMemo, useState } from "react" import { usePaginatedQuery } from "convex/react" import { toast } from "sonner" +import { ChevronDown, ChevronRight, Mail, ArrowRight, UserCheck, MessageSquare, ListChecks, ToggleRight, FileText, AlertTriangle } from "lucide-react" import { api } from "@/convex/_generated/api" import type { Id } from "@/convex/_generated/dataModel" import { useAuth } from "@/lib/auth-client" import { DEFAULT_TENANT_ID } from "@/lib/constants" +import { cn } from "@/lib/utils" import { Badge } from "@/components/ui/badge" import { Button } from "@/components/ui/button" import { DialogClose, DialogContent, DialogDescription, DialogHeader, DialogTitle } from "@/components/ui/dialog" @@ -15,6 +17,11 @@ import { Select, SelectContent, SelectItem, SelectTrigger, SelectValue } from "@ import { Skeleton } from "@/components/ui/skeleton" import { Table, TableBody, TableCell, TableHead, TableHeader, TableRow } from "@/components/ui/table" +type ActionApplied = { + type: string + details?: Record +} + type AutomationRunRow = { id: Id<"ticketAutomationRuns"> createdAt: number @@ -22,7 +29,7 @@ type AutomationRunRow = { matched: boolean eventType: string error: string | null - actionsApplied: Array<{ type: string; details?: Record }> | null + actionsApplied: ActionApplied[] | null ticket: { id: Id<"tickets">; reference: number; subject: string } | null automation: { id: Id<"ticketAutomations">; name: string } | null } @@ -50,6 +57,208 @@ function statusBadge(status: AutomationRunRow["status"]) { return { label: "Erro", variant: "destructive" as const } } +const ACTION_TYPE_LABELS: Record = { + SEND_EMAIL: { label: "Enviar e-mail", icon: Mail }, + SET_PRIORITY: { label: "Alterar prioridade", icon: ArrowRight }, + MOVE_QUEUE: { label: "Mover para fila", icon: ArrowRight }, + ASSIGN_TO: { label: "Atribuir responsavel", icon: UserCheck }, + ADD_INTERNAL_COMMENT: { label: "Comentario interno", icon: MessageSquare }, + APPLY_CHECKLIST_TEMPLATE: { label: "Aplicar checklist", icon: ListChecks }, + SET_CHAT_ENABLED: { label: "Chat habilitado", icon: ToggleRight }, + SET_FORM_TEMPLATE: { label: "Aplicar formulario", icon: FileText }, +} + +const PRIORITY_LABELS: Record = { + LOW: "Baixa", + MEDIUM: "Media", + HIGH: "Alta", + URGENT: "Urgente", +} + +function ActionDetails({ action }: { action: ActionApplied }) { + const details = action.details ?? {} + const config = ACTION_TYPE_LABELS[action.type] ?? { label: action.type, icon: ArrowRight } + const Icon = config.icon + + return ( +
+
+
+ +
+ {config.label} +
+ + {action.type === "SEND_EMAIL" && ( +
+ {details.recipients && Array.isArray(details.recipients) && ( +
+ Destinatarios: +
+ {(details.recipients as string[]).map((email, i) => ( + + {email} + + ))} +
+
+ )} + {details.subject && ( +
+ Assunto:{" "} + {String(details.subject)} +
+ )} + {details.messagePreview && ( +
+ Mensagem:{" "} + {String(details.messagePreview)} +
+ )} + {details.ctaTarget && ( +
+ Link:{" "} + + {details.ctaTarget === "PORTAL" ? "Portal (cliente)" : details.ctaTarget === "STAFF" ? "Painel (agente)" : "Auto"} + +
+ )} + {details.scheduledAt && ( +
+ Agendado em: {formatDateTime(Number(details.scheduledAt)).date} {formatDateTime(Number(details.scheduledAt)).time} +
+ )} +
+ )} + + {action.type === "SET_PRIORITY" && details.priority && ( +
+ Nova prioridade:{" "} + {PRIORITY_LABELS[String(details.priority)] ?? details.priority} +
+ )} + + {action.type === "MOVE_QUEUE" && ( +
+ Fila:{" "} + {String(details.queueName ?? details.queueId ?? "—")} +
+ )} + + {action.type === "ASSIGN_TO" && ( +
+ Responsavel:{" "} + {String(details.assigneeName ?? "—")} +
+ )} + + {action.type === "SET_FORM_TEMPLATE" && ( +
+ Formulario:{" "} + {String(details.formTemplateLabel ?? details.formTemplate ?? "—")} +
+ )} + + {action.type === "SET_CHAT_ENABLED" && ( +
+ Chat:{" "} + + {details.enabled ? "Habilitado" : "Desabilitado"} + +
+ )} + + {action.type === "ADD_INTERNAL_COMMENT" && ( +
+ Comentario interno adicionado ao ticket +
+ )} + + {action.type === "APPLY_CHECKLIST_TEMPLATE" && ( +
+ Template:{" "} + {String(details.templateName ?? "—")} + {typeof details.added === "number" && ( + ({details.added} itens adicionados) + )} +
+ )} +
+ ) +} + +function ExpandedDetails({ run }: { run: AutomationRunRow }) { + const hasActions = run.actionsApplied && run.actionsApplied.length > 0 + + return ( +
+
+ {/* Info do Ticket */} +
+

Ticket

+ {run.ticket ? ( +
+
+ Referencia:{" "} + #{run.ticket.reference} +
+
+ Assunto:{" "} + {run.ticket.subject || "—"} +
+
+ ) : ( +

Ticket nao disponivel

+ )} +
+ + {/* Info da Execucao */} +
+

Execucao

+
+
+ Evento:{" "} + {EVENT_LABELS[run.eventType] ?? run.eventType} +
+
+ Condicoes:{" "} + + {run.matched ? "Atendidas" : "Nao atendidas"} + +
+ {run.error && ( +
+ + {run.error} +
+ )} +
+
+
+ + {/* Acoes Aplicadas */} + {hasActions && ( +
+

+ Acoes aplicadas ({run.actionsApplied!.length}) +

+
+ {run.actionsApplied!.map((action, i) => ( + + ))} +
+
+ )} + + {!hasActions && run.status === "SUCCESS" && ( +
+

Nenhuma acao foi aplicada nesta execucao.

+
+ )} +
+ ) +} + export function AutomationRunsDialog({ open, automationId, @@ -65,6 +274,7 @@ export function AutomationRunsDialog({ const tenantId = session?.user.tenantId ?? DEFAULT_TENANT_ID const [statusFilter, setStatusFilter] = useState<"all" | "SUCCESS" | "SKIPPED" | "ERROR">("all") + const [expandedId, setExpandedId] = useState | null>(null) const args = useMemo(() => { if (!open) return "skip" as const @@ -179,7 +389,7 @@ export function AutomationRunsDialog({ const eventLabel = EVENT_LABELS[run.eventType] ?? run.eventType const actionsCount = run.actionsApplied?.length ?? 0 const actionsLabel = - actionsCount === 1 ? "Aplicou 1 ação" : `Aplicou ${actionsCount} ações` + actionsCount === 1 ? "Aplicou 1 acao" : `Aplicou ${actionsCount} acoes` const createdAtLabel = formatDateTime(run.createdAt) const details = run.status === "ERROR" @@ -187,46 +397,73 @@ export function AutomationRunsDialog({ : run.status === "SKIPPED" ? run.matched ? "Ignorada" - : "Condições não atendidas" + : "Condicoes nao atendidas" : actionsCount > 0 ? actionsLabel - : "Sem alterações" + : "Sem alteracoes" + + const isExpanded = expandedId === run.id + const toggleExpand = () => setExpandedId(isExpanded ? null : run.id) return ( - - -
-
{createdAtLabel.date}
-
{createdAtLabel.time}
-
-
- - {run.ticket ? ( -
-
#{run.ticket.reference}
-
- {run.ticket.subject || "—"} + + + +
+ + {isExpanded ? ( + + ) : ( + + )} + +
+
{createdAtLabel.date}
+
{createdAtLabel.time}
+
+ + {run.ticket ? ( +
+
#{run.ticket.reference}
+
+ {run.ticket.subject || "—"} +
+
) : ( "—" )} -
- - - {eventLabel} - - - - - {badge.label} - - - {actionsCount} - - {details} - -
+ + + + {eventLabel} + + + + + {badge.label} + + + {actionsCount} + + {details} + + + {isExpanded && ( + + + + + + )} +
) }) )}