diff --git a/src/components/tickets/close-ticket-dialog.tsx b/src/components/tickets/close-ticket-dialog.tsx
index 3c974df..2ae6d4f 100644
--- a/src/components/tickets/close-ticket-dialog.tsx
+++ b/src/components/tickets/close-ticket-dialog.tsx
@@ -1,58 +1,66 @@
"use client"
-import { useEffect, useMemo, useState } from "react"
+import { useCallback, useEffect, useMemo, useState } from "react"
import { useMutation, useQuery } from "convex/react"
import { api } from "@/convex/_generated/api"
import type { Id } from "@/convex/_generated/dataModel"
import { Dialog, DialogContent, DialogDescription, DialogFooter, DialogHeader, DialogTitle } from "@/components/ui/dialog"
import { Button } from "@/components/ui/button"
import { Spinner } from "@/components/ui/spinner"
-import { RichTextEditor, sanitizeEditorHtml } from "@/components/ui/rich-text-editor"
+import { RichTextEditor, sanitizeEditorHtml, stripLeadingEmptyParagraphs } from "@/components/ui/rich-text-editor"
import { toast } from "sonner"
type ClosingTemplate = { id: string; title: string; body: string }
const DEFAULT_PHONE_NUMBER = "(11) 4173-5368"
+const DEFAULT_COMPANY_NAME = "Rever Tecnologia"
+
+const sanitizeTemplate = (html: string) => stripLeadingEmptyParagraphs(sanitizeEditorHtml(html.trim()))
const DEFAULT_CLOSING_TEMPLATES: ClosingTemplate[] = [
{
id: "default-standard",
title: "Encerramento padrão",
- body: sanitizeEditorHtml(`
+ body: sanitizeTemplate(`
Olá {{cliente}},
- A equipe da Raven agradece o contato. Este ticket está sendo encerrado.
+ A equipe da ${DEFAULT_COMPANY_NAME} agradece o contato. Este ticket está sendo encerrado.
Se surgirem novas questões, você pode reabrir o ticket em até 7 dias ou nos contatar pelo número ${DEFAULT_PHONE_NUMBER}. Obrigado.
- 👍 👀 🙌
Gabriel Henrique · Raven
+ {{agente}} · ${DEFAULT_COMPANY_NAME}
`),
},
{
id: "default-no-contact",
title: "Tentativa de contato sem sucesso",
- body: sanitizeEditorHtml(`
+ body: sanitizeTemplate(`
Prezado(a) {{cliente}},
Realizamos uma tentativa de contato, mas não obtivemos sucesso.
Por favor, retorne assim que possível para seguirmos com as verificações necessárias.
Este ticket será encerrado após 3 tentativas realizadas sem sucesso.
Telefone para contato: ${DEFAULT_PHONE_NUMBER}.
- 👍 👀 🙌
Gabriel Henrique · Raven
+ {{agente}} · ${DEFAULT_COMPANY_NAME}
`),
},
{
id: "default-closed-after-attempts",
title: "Encerramento após 3 tentativas",
- body: sanitizeEditorHtml(`
+ body: sanitizeTemplate(`
Prezado(a) {{cliente}},
Esse ticket está sendo encerrado pois realizamos 3 tentativas sem retorno.
Você pode reabrir este ticket em até 7 dias ou entrar em contato pelo telefone ${DEFAULT_PHONE_NUMBER} quando preferir.
- 👍 👀 🙌
Gabriel Henrique · Raven
+ {{agente}} · ${DEFAULT_COMPANY_NAME}
`),
},
]
-function applyTemplatePlaceholders(html: string, customerName?: string | null) {
- const normalizedName = customerName?.trim()
- const fallback = normalizedName && normalizedName.length > 0 ? normalizedName : "cliente"
- return html.replace(/{{\s*(cliente|customer|customername|nome|nomecliente)\s*}}/gi, fallback)
+function applyTemplatePlaceholders(html: string, customerName?: string | null, agentName?: string | null) {
+ const normalizedCustomer = customerName?.trim()
+ const customerFallback = normalizedCustomer && normalizedCustomer.length > 0 ? normalizedCustomer : "cliente"
+ const normalizedAgent = agentName?.trim()
+ const agentFallback = normalizedAgent && normalizedAgent.length > 0 ? normalizedAgent : "Equipe Rever"
+ return html
+ .replace(/{{\s*(cliente|customer|customername|nome|nomecliente)\s*}}/gi, customerFallback)
+ .replace(/{{\s*(agente|agent|atendente|responsavel|usu[aá]rio|usuario)\s*}}/gi, agentFallback)
+ .replace(/{{\s*(empresa|company|companhia)\s*}}/gi, DEFAULT_COMPANY_NAME)
}
export function CloseTicketDialog({
@@ -62,6 +70,7 @@ export function CloseTicketDialog({
tenantId,
actorId,
requesterName,
+ agentName,
onSuccess,
}: {
open: boolean
@@ -70,6 +79,7 @@ export function CloseTicketDialog({
tenantId: string
actorId: Id<"users"> | null
requesterName?: string | null
+ agentName?: string | null
onSuccess: () => void
}) {
const updateStatus = useMutation(api.tickets.updateStatus)
@@ -92,6 +102,11 @@ export function CloseTicketDialog({
const [message, setMessage] = useState("")
const [isSubmitting, setIsSubmitting] = useState(false)
+ const hydrateTemplateBody = useCallback((templateHtml: string) => {
+ const withPlaceholders = applyTemplatePlaceholders(templateHtml, requesterName, agentName)
+ return stripLeadingEmptyParagraphs(sanitizeEditorHtml(withPlaceholders))
+ }, [requesterName, agentName])
+
useEffect(() => {
if (!open) {
setSelectedTemplateId(null)
@@ -101,16 +116,15 @@ export function CloseTicketDialog({
}
if (templates.length > 0 && !selectedTemplateId && !message) {
const first = templates[0]
- const hydrated = sanitizeEditorHtml(applyTemplatePlaceholders(first.body, requesterName))
+ const hydrated = hydrateTemplateBody(first.body)
setSelectedTemplateId(first.id)
setMessage(hydrated)
}
- }, [open, templates, requesterName, selectedTemplateId, message])
+ }, [open, templates, selectedTemplateId, message, hydrateTemplateBody])
const handleTemplateSelect = (template: ClosingTemplate) => {
setSelectedTemplateId(template.id)
- const filled = sanitizeEditorHtml(applyTemplatePlaceholders(template.body, requesterName))
- setMessage(filled)
+ setMessage(hydrateTemplateBody(template.body))
}
const handleSubmit = async () => {
@@ -122,8 +136,8 @@ export function CloseTicketDialog({
toast.loading("Encerrando ticket...", { id: "close-ticket" })
try {
await updateStatus({ ticketId: ticketId as unknown as Id<"tickets">, status: "RESOLVED", actorId })
- const withPlaceholders = applyTemplatePlaceholders(message, requesterName)
- const sanitized = sanitizeEditorHtml(withPlaceholders)
+ const withPlaceholders = applyTemplatePlaceholders(message, requesterName, agentName)
+ const sanitized = stripLeadingEmptyParagraphs(sanitizeEditorHtml(withPlaceholders))
const hasContent = sanitized.replace(/<[^>]*>/g, "").trim().length > 0
if (hasContent) {
await addComment({
diff --git a/src/components/tickets/status-select.tsx b/src/components/tickets/status-select.tsx
index e247fc2..31d1102 100644
--- a/src/components/tickets/status-select.tsx
+++ b/src/components/tickets/status-select.tsx
@@ -1,8 +1,6 @@
"use client"
-import { useEffect, useMemo, useState } from "react"
-import { useMutation, useQuery } from "convex/react"
-import { api } from "@/convex/_generated/api"
+import { useEffect, useState } from "react"
import type { Id } from "@/convex/_generated/dataModel"
import type { TicketStatus } from "@/lib/schemas/ticket"
import { useAuth } from "@/lib/auth-client"
@@ -12,7 +10,6 @@ import { Tooltip, TooltipContent, TooltipTrigger } from "@/components/ui/tooltip
import { CheckCircle2 } from "lucide-react"
import { CloseTicketDialog } from "@/components/tickets/close-ticket-dialog"
import { cn } from "@/lib/utils"
-import { sanitizeEditorHtml } from "@/components/ui/rich-text-editor"
type StatusKey = TicketStatus | "NEW" | "OPEN" | "ON_HOLD";
@@ -29,54 +26,6 @@ const statusStyles: Record = {
const baseBadgeClass =
"inline-flex h-9 items-center gap-2 rounded-full border border-slate-200 px-3 text-sm font-semibold transition hover:border-slate-300"
-type ClosingTemplate = {
- id: string
- title: string
- body: string
-}
-
-const DEFAULT_PHONE_NUMBER = "(11) 4173-5368"
-
-const DEFAULT_CLOSING_TEMPLATES: ClosingTemplate[] = [
- {
- id: "default-standard",
- title: "Encerramento padrão",
- body: sanitizeEditorHtml(`
- Olá {{cliente}},
- A equipe da Raven agradece o contato. Este ticket está sendo encerrado.
- Se surgirem novas questões, você pode reabrir o ticket em até 7 dias ou nos contatar pelo número ${DEFAULT_PHONE_NUMBER}. Obrigado.
- 👍 👀 🙌
Gabriel Henrique · Raven
- `),
- },
- {
- id: "default-no-contact",
- title: "Tentativa de contato sem sucesso",
- body: sanitizeEditorHtml(`
- Prezado(a) {{cliente}},
- Realizamos uma tentativa de contato, mas não obtivemos sucesso.
- Por favor, retorne assim que possível para seguirmos com as verificações necessárias.
- Este ticket será encerrado após 3 tentativas realizadas sem sucesso.
- Telefone para contato: ${DEFAULT_PHONE_NUMBER}.
- 👍 👀 🙌
Gabriel Henrique · Raven
- `),
- },
- {
- id: "default-closed-after-attempts",
- title: "Encerramento após 3 tentativas",
- body: sanitizeEditorHtml(`
- Prezado(a) {{cliente}},
- Esse ticket está sendo encerrado pois realizamos 3 tentativas sem retorno.
- Você pode reabrir este ticket em até 7 dias ou entrar em contato pelo telefone ${DEFAULT_PHONE_NUMBER} quando preferir.
- 👍 👀 🙌
Gabriel Henrique · Raven
- `),
- },
-]
-
-function applyTemplatePlaceholders(html: string, customerName?: string | null) {
- const normalizedName = customerName?.trim()
- const fallback = normalizedName && normalizedName.length > 0 ? normalizedName : "cliente"
- return html.replace(/{{\s*(cliente|customer|customername|nome|nomecliente)\s*}}/gi, fallback)
-}
export function StatusSelect({
ticketId,
@@ -91,8 +40,16 @@ export function StatusSelect({
requesterName?: string | null
showCloseButton?: boolean
}) {
- const { convexUserId } = useAuth()
+ const { convexUserId, session, machineContext } = useAuth()
const actorId = (convexUserId ?? null) as Id<"users"> | null
+ const sessionName = session?.user?.name?.trim()
+ const machineAssignedName = machineContext?.assignedUserName?.trim()
+ const agentName =
+ sessionName && sessionName.length > 0
+ ? sessionName
+ : machineAssignedName && machineAssignedName.length > 0
+ ? machineAssignedName
+ : null
const [status, setStatus] = useState(value)
const [closeDialogOpen, setCloseDialogOpen] = useState(false)
@@ -136,6 +93,7 @@ export function StatusSelect({
tenantId={tenantId}
actorId={actorId}
requesterName={requesterName}
+ agentName={agentName}
onSuccess={() => {
setStatus("RESOLVED")
setCloseDialogOpen(false)
diff --git a/src/components/tickets/ticket-comments.rich.tsx b/src/components/tickets/ticket-comments.rich.tsx
index 7db871c..d85e2ea 100644
--- a/src/components/tickets/ticket-comments.rich.tsx
+++ b/src/components/tickets/ticket-comments.rich.tsx
@@ -16,7 +16,7 @@ import { Card, CardContent, CardHeader, CardTitle } from "@/components/ui/card"
import { Button } from "@/components/ui/button"
import { toast } from "sonner"
import { Dropzone } from "@/components/ui/dropzone"
-import { RichTextEditor, RichTextContent, sanitizeEditorHtml } from "@/components/ui/rich-text-editor"
+import { RichTextEditor, RichTextContent, sanitizeEditorHtml, stripLeadingEmptyParagraphs } from "@/components/ui/rich-text-editor"
import { Dialog, DialogClose, DialogContent, DialogDescription, DialogHeader, DialogTitle } from "@/components/ui/dialog"
import { DropdownMenu, DropdownMenuContent, DropdownMenuItem, DropdownMenuTrigger } from "@/components/ui/dropdown-menu"
import { Select, SelectTrigger, SelectValue, SelectContent, SelectItem } from "@/components/ui/select"
@@ -71,11 +71,12 @@ export function TicketComments({ ticket }: TicketCommentsProps) {
const canUseTemplates = Boolean(convexUserId && isStaff)
const insertTemplateIntoBody = (html: string) => {
- const sanitized = sanitizeEditorHtml(html)
+ const sanitizedTemplate = stripLeadingEmptyParagraphs(sanitizeEditorHtml(html))
setBody((current) => {
- if (!current) return sanitized
- const merged = `${current}
${sanitized}`
- return sanitizeEditorHtml(merged)
+ if (!sanitizedTemplate) return current
+ if (!current) return sanitizedTemplate
+ const merged = `${current}
${sanitizedTemplate}`
+ return stripLeadingEmptyParagraphs(sanitizeEditorHtml(merged))
})
}
diff --git a/src/components/tickets/ticket-summary-header.tsx b/src/components/tickets/ticket-summary-header.tsx
index e7f5454..52f7e84 100644
--- a/src/components/tickets/ticket-summary-header.tsx
+++ b/src/components/tickets/ticket-summary-header.tsx
@@ -95,10 +95,18 @@ function formatDuration(durationMs: number) {
}
export function TicketSummaryHeader({ ticket }: TicketHeaderProps) {
- const { convexUserId, role, isStaff } = useAuth()
+ const { convexUserId, role, isStaff, session, machineContext } = useAuth()
const normalizedRole = (role ?? "").toLowerCase()
const isManager = normalizedRole === "manager"
const isAdmin = normalizedRole === "admin"
+ const sessionName = session?.user?.name?.trim()
+ const machineAssignedName = machineContext?.assignedUserName?.trim()
+ const agentName =
+ sessionName && sessionName.length > 0
+ ? sessionName
+ : machineAssignedName && machineAssignedName.length > 0
+ ? machineAssignedName
+ : null
useDefaultQueues(ticket.tenantId)
const changeAssignee = useMutation(api.tickets.changeAssignee)
const changeQueue = useMutation(api.tickets.changeQueue)
@@ -621,7 +629,7 @@ export function TicketSummaryHeader({ ticket }: TicketHeaderProps) {