fix: improve closure templates and hover styling
This commit is contained in:
parent
3012ad4348
commit
81657e52d8
5 changed files with 69 additions and 79 deletions
|
|
@ -1,58 +1,66 @@
|
||||||
"use client"
|
"use client"
|
||||||
|
|
||||||
import { useEffect, useMemo, useState } from "react"
|
import { useCallback, useEffect, useMemo, useState } from "react"
|
||||||
import { useMutation, useQuery } from "convex/react"
|
import { useMutation, useQuery } from "convex/react"
|
||||||
import { api } from "@/convex/_generated/api"
|
import { api } from "@/convex/_generated/api"
|
||||||
import type { Id } from "@/convex/_generated/dataModel"
|
import type { Id } from "@/convex/_generated/dataModel"
|
||||||
import { Dialog, DialogContent, DialogDescription, DialogFooter, DialogHeader, DialogTitle } from "@/components/ui/dialog"
|
import { Dialog, DialogContent, DialogDescription, DialogFooter, DialogHeader, DialogTitle } from "@/components/ui/dialog"
|
||||||
import { Button } from "@/components/ui/button"
|
import { Button } from "@/components/ui/button"
|
||||||
import { Spinner } from "@/components/ui/spinner"
|
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"
|
import { toast } from "sonner"
|
||||||
|
|
||||||
type ClosingTemplate = { id: string; title: string; body: string }
|
type ClosingTemplate = { id: string; title: string; body: string }
|
||||||
|
|
||||||
const DEFAULT_PHONE_NUMBER = "(11) 4173-5368"
|
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[] = [
|
const DEFAULT_CLOSING_TEMPLATES: ClosingTemplate[] = [
|
||||||
{
|
{
|
||||||
id: "default-standard",
|
id: "default-standard",
|
||||||
title: "Encerramento padrão",
|
title: "Encerramento padrão",
|
||||||
body: sanitizeEditorHtml(`
|
body: sanitizeTemplate(`
|
||||||
<p>Olá {{cliente}},</p>
|
<p>Olá {{cliente}},</p>
|
||||||
<p>A equipe da Raven agradece o contato. Este ticket está sendo encerrado.</p>
|
<p>A equipe da ${DEFAULT_COMPANY_NAME} agradece o contato. Este ticket está sendo encerrado.</p>
|
||||||
<p>Se surgirem novas questões, você pode reabrir o ticket em até 7 dias ou nos contatar pelo número <strong>${DEFAULT_PHONE_NUMBER}</strong>. Obrigado.</p>
|
<p>Se surgirem novas questões, você pode reabrir o ticket em até 7 dias ou nos contatar pelo número <strong>${DEFAULT_PHONE_NUMBER}</strong>. Obrigado.</p>
|
||||||
<p>👍 👀 🙌<br />Gabriel Henrique · Raven</p>
|
<p>{{agente}} · ${DEFAULT_COMPANY_NAME}</p>
|
||||||
`),
|
`),
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
id: "default-no-contact",
|
id: "default-no-contact",
|
||||||
title: "Tentativa de contato sem sucesso",
|
title: "Tentativa de contato sem sucesso",
|
||||||
body: sanitizeEditorHtml(`
|
body: sanitizeTemplate(`
|
||||||
<p>Prezado(a) {{cliente}},</p>
|
<p>Prezado(a) {{cliente}},</p>
|
||||||
<p>Realizamos uma tentativa de contato, mas não obtivemos sucesso.</p>
|
<p>Realizamos uma tentativa de contato, mas não obtivemos sucesso.</p>
|
||||||
<p>Por favor, retorne assim que possível para seguirmos com as verificações necessárias.</p>
|
<p>Por favor, retorne assim que possível para seguirmos com as verificações necessárias.</p>
|
||||||
<p>Este ticket será encerrado após 3 tentativas realizadas sem sucesso.</p>
|
<p>Este ticket será encerrado após 3 tentativas realizadas sem sucesso.</p>
|
||||||
<p>Telefone para contato: <strong>${DEFAULT_PHONE_NUMBER}</strong>.</p>
|
<p>Telefone para contato: <strong>${DEFAULT_PHONE_NUMBER}</strong>.</p>
|
||||||
<p>👍 👀 🙌<br />Gabriel Henrique · Raven</p>
|
<p>{{agente}} · ${DEFAULT_COMPANY_NAME}</p>
|
||||||
`),
|
`),
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
id: "default-closed-after-attempts",
|
id: "default-closed-after-attempts",
|
||||||
title: "Encerramento após 3 tentativas",
|
title: "Encerramento após 3 tentativas",
|
||||||
body: sanitizeEditorHtml(`
|
body: sanitizeTemplate(`
|
||||||
<p>Prezado(a) {{cliente}},</p>
|
<p>Prezado(a) {{cliente}},</p>
|
||||||
<p>Esse ticket está sendo encerrado pois realizamos 3 tentativas sem retorno.</p>
|
<p>Esse ticket está sendo encerrado pois realizamos 3 tentativas sem retorno.</p>
|
||||||
<p>Você pode reabrir este ticket em até 7 dias ou entrar em contato pelo telefone <strong>${DEFAULT_PHONE_NUMBER}</strong> quando preferir.</p>
|
<p>Você pode reabrir este ticket em até 7 dias ou entrar em contato pelo telefone <strong>${DEFAULT_PHONE_NUMBER}</strong> quando preferir.</p>
|
||||||
<p>👍 👀 🙌<br />Gabriel Henrique · Raven</p>
|
<p>{{agente}} · ${DEFAULT_COMPANY_NAME}</p>
|
||||||
`),
|
`),
|
||||||
},
|
},
|
||||||
]
|
]
|
||||||
|
|
||||||
function applyTemplatePlaceholders(html: string, customerName?: string | null) {
|
function applyTemplatePlaceholders(html: string, customerName?: string | null, agentName?: string | null) {
|
||||||
const normalizedName = customerName?.trim()
|
const normalizedCustomer = customerName?.trim()
|
||||||
const fallback = normalizedName && normalizedName.length > 0 ? normalizedName : "cliente"
|
const customerFallback = normalizedCustomer && normalizedCustomer.length > 0 ? normalizedCustomer : "cliente"
|
||||||
return html.replace(/{{\s*(cliente|customer|customername|nome|nomecliente)\s*}}/gi, fallback)
|
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({
|
export function CloseTicketDialog({
|
||||||
|
|
@ -62,6 +70,7 @@ export function CloseTicketDialog({
|
||||||
tenantId,
|
tenantId,
|
||||||
actorId,
|
actorId,
|
||||||
requesterName,
|
requesterName,
|
||||||
|
agentName,
|
||||||
onSuccess,
|
onSuccess,
|
||||||
}: {
|
}: {
|
||||||
open: boolean
|
open: boolean
|
||||||
|
|
@ -70,6 +79,7 @@ export function CloseTicketDialog({
|
||||||
tenantId: string
|
tenantId: string
|
||||||
actorId: Id<"users"> | null
|
actorId: Id<"users"> | null
|
||||||
requesterName?: string | null
|
requesterName?: string | null
|
||||||
|
agentName?: string | null
|
||||||
onSuccess: () => void
|
onSuccess: () => void
|
||||||
}) {
|
}) {
|
||||||
const updateStatus = useMutation(api.tickets.updateStatus)
|
const updateStatus = useMutation(api.tickets.updateStatus)
|
||||||
|
|
@ -92,6 +102,11 @@ export function CloseTicketDialog({
|
||||||
const [message, setMessage] = useState<string>("")
|
const [message, setMessage] = useState<string>("")
|
||||||
const [isSubmitting, setIsSubmitting] = useState(false)
|
const [isSubmitting, setIsSubmitting] = useState(false)
|
||||||
|
|
||||||
|
const hydrateTemplateBody = useCallback((templateHtml: string) => {
|
||||||
|
const withPlaceholders = applyTemplatePlaceholders(templateHtml, requesterName, agentName)
|
||||||
|
return stripLeadingEmptyParagraphs(sanitizeEditorHtml(withPlaceholders))
|
||||||
|
}, [requesterName, agentName])
|
||||||
|
|
||||||
useEffect(() => {
|
useEffect(() => {
|
||||||
if (!open) {
|
if (!open) {
|
||||||
setSelectedTemplateId(null)
|
setSelectedTemplateId(null)
|
||||||
|
|
@ -101,16 +116,15 @@ export function CloseTicketDialog({
|
||||||
}
|
}
|
||||||
if (templates.length > 0 && !selectedTemplateId && !message) {
|
if (templates.length > 0 && !selectedTemplateId && !message) {
|
||||||
const first = templates[0]
|
const first = templates[0]
|
||||||
const hydrated = sanitizeEditorHtml(applyTemplatePlaceholders(first.body, requesterName))
|
const hydrated = hydrateTemplateBody(first.body)
|
||||||
setSelectedTemplateId(first.id)
|
setSelectedTemplateId(first.id)
|
||||||
setMessage(hydrated)
|
setMessage(hydrated)
|
||||||
}
|
}
|
||||||
}, [open, templates, requesterName, selectedTemplateId, message])
|
}, [open, templates, selectedTemplateId, message, hydrateTemplateBody])
|
||||||
|
|
||||||
const handleTemplateSelect = (template: ClosingTemplate) => {
|
const handleTemplateSelect = (template: ClosingTemplate) => {
|
||||||
setSelectedTemplateId(template.id)
|
setSelectedTemplateId(template.id)
|
||||||
const filled = sanitizeEditorHtml(applyTemplatePlaceholders(template.body, requesterName))
|
setMessage(hydrateTemplateBody(template.body))
|
||||||
setMessage(filled)
|
|
||||||
}
|
}
|
||||||
|
|
||||||
const handleSubmit = async () => {
|
const handleSubmit = async () => {
|
||||||
|
|
@ -122,8 +136,8 @@ export function CloseTicketDialog({
|
||||||
toast.loading("Encerrando ticket...", { id: "close-ticket" })
|
toast.loading("Encerrando ticket...", { id: "close-ticket" })
|
||||||
try {
|
try {
|
||||||
await updateStatus({ ticketId: ticketId as unknown as Id<"tickets">, status: "RESOLVED", actorId })
|
await updateStatus({ ticketId: ticketId as unknown as Id<"tickets">, status: "RESOLVED", actorId })
|
||||||
const withPlaceholders = applyTemplatePlaceholders(message, requesterName)
|
const withPlaceholders = applyTemplatePlaceholders(message, requesterName, agentName)
|
||||||
const sanitized = sanitizeEditorHtml(withPlaceholders)
|
const sanitized = stripLeadingEmptyParagraphs(sanitizeEditorHtml(withPlaceholders))
|
||||||
const hasContent = sanitized.replace(/<[^>]*>/g, "").trim().length > 0
|
const hasContent = sanitized.replace(/<[^>]*>/g, "").trim().length > 0
|
||||||
if (hasContent) {
|
if (hasContent) {
|
||||||
await addComment({
|
await addComment({
|
||||||
|
|
|
||||||
|
|
@ -1,8 +1,6 @@
|
||||||
"use client"
|
"use client"
|
||||||
|
|
||||||
import { useEffect, useMemo, useState } from "react"
|
import { useEffect, useState } from "react"
|
||||||
import { useMutation, useQuery } from "convex/react"
|
|
||||||
import { api } from "@/convex/_generated/api"
|
|
||||||
import type { Id } from "@/convex/_generated/dataModel"
|
import type { Id } from "@/convex/_generated/dataModel"
|
||||||
import type { TicketStatus } from "@/lib/schemas/ticket"
|
import type { TicketStatus } from "@/lib/schemas/ticket"
|
||||||
import { useAuth } from "@/lib/auth-client"
|
import { useAuth } from "@/lib/auth-client"
|
||||||
|
|
@ -12,7 +10,6 @@ import { Tooltip, TooltipContent, TooltipTrigger } from "@/components/ui/tooltip
|
||||||
import { CheckCircle2 } from "lucide-react"
|
import { CheckCircle2 } from "lucide-react"
|
||||||
import { CloseTicketDialog } from "@/components/tickets/close-ticket-dialog"
|
import { CloseTicketDialog } from "@/components/tickets/close-ticket-dialog"
|
||||||
import { cn } from "@/lib/utils"
|
import { cn } from "@/lib/utils"
|
||||||
import { sanitizeEditorHtml } from "@/components/ui/rich-text-editor"
|
|
||||||
|
|
||||||
type StatusKey = TicketStatus | "NEW" | "OPEN" | "ON_HOLD";
|
type StatusKey = TicketStatus | "NEW" | "OPEN" | "ON_HOLD";
|
||||||
|
|
||||||
|
|
@ -29,54 +26,6 @@ const statusStyles: Record<StatusKey, { label: string; badgeClass: string }> = {
|
||||||
const baseBadgeClass =
|
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"
|
"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(`
|
|
||||||
<p>Olá {{cliente}},</p>
|
|
||||||
<p>A equipe da Raven agradece o contato. Este ticket está sendo encerrado.</p>
|
|
||||||
<p>Se surgirem novas questões, você pode reabrir o ticket em até 7 dias ou nos contatar pelo número <strong>${DEFAULT_PHONE_NUMBER}</strong>. Obrigado.</p>
|
|
||||||
<p>👍 👀 🙌<br />Gabriel Henrique · Raven</p>
|
|
||||||
`),
|
|
||||||
},
|
|
||||||
{
|
|
||||||
id: "default-no-contact",
|
|
||||||
title: "Tentativa de contato sem sucesso",
|
|
||||||
body: sanitizeEditorHtml(`
|
|
||||||
<p>Prezado(a) {{cliente}},</p>
|
|
||||||
<p>Realizamos uma tentativa de contato, mas não obtivemos sucesso.</p>
|
|
||||||
<p>Por favor, retorne assim que possível para seguirmos com as verificações necessárias.</p>
|
|
||||||
<p>Este ticket será encerrado após 3 tentativas realizadas sem sucesso.</p>
|
|
||||||
<p>Telefone para contato: <strong>${DEFAULT_PHONE_NUMBER}</strong>.</p>
|
|
||||||
<p>👍 👀 🙌<br />Gabriel Henrique · Raven</p>
|
|
||||||
`),
|
|
||||||
},
|
|
||||||
{
|
|
||||||
id: "default-closed-after-attempts",
|
|
||||||
title: "Encerramento após 3 tentativas",
|
|
||||||
body: sanitizeEditorHtml(`
|
|
||||||
<p>Prezado(a) {{cliente}},</p>
|
|
||||||
<p>Esse ticket está sendo encerrado pois realizamos 3 tentativas sem retorno.</p>
|
|
||||||
<p>Você pode reabrir este ticket em até 7 dias ou entrar em contato pelo telefone <strong>${DEFAULT_PHONE_NUMBER}</strong> quando preferir.</p>
|
|
||||||
<p>👍 👀 🙌<br />Gabriel Henrique · Raven</p>
|
|
||||||
`),
|
|
||||||
},
|
|
||||||
]
|
|
||||||
|
|
||||||
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({
|
export function StatusSelect({
|
||||||
ticketId,
|
ticketId,
|
||||||
|
|
@ -91,8 +40,16 @@ export function StatusSelect({
|
||||||
requesterName?: string | null
|
requesterName?: string | null
|
||||||
showCloseButton?: boolean
|
showCloseButton?: boolean
|
||||||
}) {
|
}) {
|
||||||
const { convexUserId } = useAuth()
|
const { convexUserId, session, machineContext } = useAuth()
|
||||||
const actorId = (convexUserId ?? null) as Id<"users"> | null
|
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<TicketStatus>(value)
|
const [status, setStatus] = useState<TicketStatus>(value)
|
||||||
const [closeDialogOpen, setCloseDialogOpen] = useState(false)
|
const [closeDialogOpen, setCloseDialogOpen] = useState(false)
|
||||||
|
|
||||||
|
|
@ -136,6 +93,7 @@ export function StatusSelect({
|
||||||
tenantId={tenantId}
|
tenantId={tenantId}
|
||||||
actorId={actorId}
|
actorId={actorId}
|
||||||
requesterName={requesterName}
|
requesterName={requesterName}
|
||||||
|
agentName={agentName}
|
||||||
onSuccess={() => {
|
onSuccess={() => {
|
||||||
setStatus("RESOLVED")
|
setStatus("RESOLVED")
|
||||||
setCloseDialogOpen(false)
|
setCloseDialogOpen(false)
|
||||||
|
|
|
||||||
|
|
@ -16,7 +16,7 @@ import { Card, CardContent, CardHeader, CardTitle } from "@/components/ui/card"
|
||||||
import { Button } from "@/components/ui/button"
|
import { Button } from "@/components/ui/button"
|
||||||
import { toast } from "sonner"
|
import { toast } from "sonner"
|
||||||
import { Dropzone } from "@/components/ui/dropzone"
|
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 { Dialog, DialogClose, DialogContent, DialogDescription, DialogHeader, DialogTitle } from "@/components/ui/dialog"
|
||||||
import { DropdownMenu, DropdownMenuContent, DropdownMenuItem, DropdownMenuTrigger } from "@/components/ui/dropdown-menu"
|
import { DropdownMenu, DropdownMenuContent, DropdownMenuItem, DropdownMenuTrigger } from "@/components/ui/dropdown-menu"
|
||||||
import { Select, SelectTrigger, SelectValue, SelectContent, SelectItem } from "@/components/ui/select"
|
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 canUseTemplates = Boolean(convexUserId && isStaff)
|
||||||
|
|
||||||
const insertTemplateIntoBody = (html: string) => {
|
const insertTemplateIntoBody = (html: string) => {
|
||||||
const sanitized = sanitizeEditorHtml(html)
|
const sanitizedTemplate = stripLeadingEmptyParagraphs(sanitizeEditorHtml(html))
|
||||||
setBody((current) => {
|
setBody((current) => {
|
||||||
if (!current) return sanitized
|
if (!sanitizedTemplate) return current
|
||||||
const merged = `${current}<p><br /></p>${sanitized}`
|
if (!current) return sanitizedTemplate
|
||||||
return sanitizeEditorHtml(merged)
|
const merged = `${current}<p><br /></p>${sanitizedTemplate}`
|
||||||
|
return stripLeadingEmptyParagraphs(sanitizeEditorHtml(merged))
|
||||||
})
|
})
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
|
||||||
|
|
@ -95,10 +95,18 @@ function formatDuration(durationMs: number) {
|
||||||
}
|
}
|
||||||
|
|
||||||
export function TicketSummaryHeader({ ticket }: TicketHeaderProps) {
|
export function TicketSummaryHeader({ ticket }: TicketHeaderProps) {
|
||||||
const { convexUserId, role, isStaff } = useAuth()
|
const { convexUserId, role, isStaff, session, machineContext } = useAuth()
|
||||||
const normalizedRole = (role ?? "").toLowerCase()
|
const normalizedRole = (role ?? "").toLowerCase()
|
||||||
const isManager = normalizedRole === "manager"
|
const isManager = normalizedRole === "manager"
|
||||||
const isAdmin = normalizedRole === "admin"
|
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)
|
useDefaultQueues(ticket.tenantId)
|
||||||
const changeAssignee = useMutation(api.tickets.changeAssignee)
|
const changeAssignee = useMutation(api.tickets.changeAssignee)
|
||||||
const changeQueue = useMutation(api.tickets.changeQueue)
|
const changeQueue = useMutation(api.tickets.changeQueue)
|
||||||
|
|
@ -621,7 +629,7 @@ export function TicketSummaryHeader({ ticket }: TicketHeaderProps) {
|
||||||
<div className="absolute right-6 top-6 flex items-center gap-3">
|
<div className="absolute right-6 top-6 flex items-center gap-3">
|
||||||
<Button
|
<Button
|
||||||
type="button"
|
type="button"
|
||||||
className="inline-flex items-center gap-2 rounded-lg border border-[var(--sidebar-primary)] bg-[var(--sidebar-primary)] px-3 py-1.5 text-sm font-semibold text-black transition hover:bg-[var(--sidebar-primary)]/90 focus-visible:outline-none focus-visible:ring-2 focus-visible:ring-[var(--sidebar-ring)]/30"
|
className="inline-flex items-center gap-2 rounded-lg border border-[var(--sidebar-primary)] bg-[var(--sidebar-primary)] px-3 py-1.5 text-sm font-semibold text-black shadow-sm transition-all duration-200 ease-out hover:-translate-y-0.5 hover:bg-[var(--sidebar-primary)]/90 hover:shadow-[0_12px_22px_-12px_rgba(15,23,42,0.45)] focus-visible:outline-none focus-visible:ring-2 focus-visible:ring-[var(--sidebar-ring)]/30 active:translate-y-0 active:shadow-sm"
|
||||||
onClick={() => setCloseOpen(true)}
|
onClick={() => setCloseOpen(true)}
|
||||||
>
|
>
|
||||||
<CheckCircle2 className="size-4" /> Encerrar
|
<CheckCircle2 className="size-4" /> Encerrar
|
||||||
|
|
@ -675,6 +683,7 @@ export function TicketSummaryHeader({ ticket }: TicketHeaderProps) {
|
||||||
tenantId={ticket.tenantId}
|
tenantId={ticket.tenantId}
|
||||||
actorId={convexUserId as Id<"users"> | null}
|
actorId={convexUserId as Id<"users"> | null}
|
||||||
requesterName={ticket.requester?.name ?? ticket.requester?.email ?? null}
|
requesterName={ticket.requester?.name ?? ticket.requester?.email ?? null}
|
||||||
|
agentName={agentName}
|
||||||
onSuccess={() => {}}
|
onSuccess={() => {}}
|
||||||
/>
|
/>
|
||||||
<div className="flex flex-wrap items-start justify-between gap-3">
|
<div className="flex flex-wrap items-start justify-between gap-3">
|
||||||
|
|
|
||||||
|
|
@ -318,6 +318,14 @@ export function sanitizeEditorHtml(html: string): string {
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
export function stripLeadingEmptyParagraphs(html: string): string {
|
||||||
|
if (!html) return ""
|
||||||
|
return html.replace(
|
||||||
|
/^(?:\s| | |<br\s*\/?>|<p>(?:\s| | |<br\s*\/?>)*<\/p>)+/gi,
|
||||||
|
""
|
||||||
|
)
|
||||||
|
}
|
||||||
|
|
||||||
export function RichTextContent({ html, className }: { html: string; className?: string }) {
|
export function RichTextContent({ html, className }: { html: string; className?: string }) {
|
||||||
return (
|
return (
|
||||||
<div
|
<div
|
||||||
|
|
|
||||||
Loading…
Add table
Add a link
Reference in a new issue