feat: seed real agents and enable comment templates
This commit is contained in:
parent
df8c4e29bb
commit
409cbea7b9
13 changed files with 1722 additions and 29 deletions
|
|
@ -3,9 +3,9 @@
|
|||
import { useCallback, useEffect, useMemo, useRef, useState } from "react"
|
||||
import { formatDistanceToNow } from "date-fns"
|
||||
import { ptBR } from "date-fns/locale"
|
||||
import { IconLock, IconMessage } from "@tabler/icons-react"
|
||||
import { IconLock, IconMessage, IconFileText } from "@tabler/icons-react"
|
||||
import { FileIcon, Image as ImageIcon, PencilLine, Trash2, X } from "lucide-react"
|
||||
import { useAction, useMutation } from "convex/react"
|
||||
import { useAction, useMutation, useQuery } from "convex/react"
|
||||
// @ts-expect-error Convex runtime API lacks TypeScript declarations
|
||||
import { api } from "@/convex/_generated/api"
|
||||
import { useAuth } from "@/lib/auth-client"
|
||||
|
|
@ -19,6 +19,7 @@ import { toast } from "sonner"
|
|||
import { Dropzone } from "@/components/ui/dropzone"
|
||||
import { RichTextEditor, RichTextContent, sanitizeEditorHtml } from "@/components/ui/rich-text-editor"
|
||||
import { Dialog, 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"
|
||||
import { Empty, EmptyDescription, EmptyHeader, EmptyMedia, EmptyTitle } from "@/components/ui/empty"
|
||||
import { Spinner } from "@/components/ui/spinner"
|
||||
|
|
@ -33,7 +34,7 @@ const submitButtonClass =
|
|||
"inline-flex items-center gap-2 rounded-lg border border-black bg-black px-3 py-2 text-sm font-semibold text-white transition hover:bg-[#18181b]/85 focus-visible:outline-none focus-visible:ring-2 focus-visible:ring-[#18181b]/30"
|
||||
|
||||
export function TicketComments({ ticket }: TicketCommentsProps) {
|
||||
const { convexUserId } = useAuth()
|
||||
const { convexUserId, isStaff } = useAuth()
|
||||
const addComment = useMutation(api.tickets.addComment)
|
||||
const removeAttachment = useMutation(api.tickets.removeCommentAttachment)
|
||||
const updateComment = useMutation(api.tickets.updateComment)
|
||||
|
|
@ -48,6 +49,25 @@ export function TicketComments({ ticket }: TicketCommentsProps) {
|
|||
const [savingCommentId, setSavingCommentId] = useState<string | null>(null)
|
||||
const [localBodies, setLocalBodies] = useState<Record<string, string>>({})
|
||||
|
||||
const templateArgs = convexUserId && isStaff
|
||||
? { tenantId: ticket.tenantId, viewerId: convexUserId as Id<"users"> }
|
||||
: "skip"
|
||||
const templatesResult = useQuery(convexUserId && isStaff ? api.commentTemplates.list : "skip", templateArgs) as
|
||||
| { id: string; title: string; body: string }[]
|
||||
| undefined
|
||||
const templates = templatesResult ?? []
|
||||
const templatesLoading = Boolean(convexUserId && isStaff) && templatesResult === undefined
|
||||
const canUseTemplates = Boolean(convexUserId && isStaff)
|
||||
|
||||
const insertTemplateIntoBody = (html: string) => {
|
||||
const sanitized = sanitizeEditorHtml(html)
|
||||
setBody((current) => {
|
||||
if (!current) return sanitized
|
||||
const merged = `${current}<p><br /></p>${sanitized}`
|
||||
return sanitizeEditorHtml(merged)
|
||||
})
|
||||
}
|
||||
|
||||
const startEditingComment = useCallback((commentId: string, currentBody: string) => {
|
||||
setEditingComment({ id: commentId, value: currentBody || "" })
|
||||
}, [])
|
||||
|
|
@ -352,18 +372,58 @@ export function TicketComments({ ticket }: TicketCommentsProps) {
|
|||
})}
|
||||
</div>
|
||||
) : null}
|
||||
<div className="flex items-center justify-between gap-2">
|
||||
<div className="flex items-center gap-2 text-xs text-neutral-600">
|
||||
Visibilidade:
|
||||
<Select value={visibility} onValueChange={(value) => setVisibility(value as "PUBLIC" | "INTERNAL")}>
|
||||
<SelectTrigger className={selectTriggerClass}>
|
||||
<SelectValue placeholder="Visibilidade" />
|
||||
</SelectTrigger>
|
||||
<SelectContent className="rounded-lg border border-slate-200 bg-white text-neutral-800 shadow-sm">
|
||||
<SelectItem value="PUBLIC">Pública</SelectItem>
|
||||
<SelectItem value="INTERNAL">Interna</SelectItem>
|
||||
</SelectContent>
|
||||
</Select>
|
||||
<div className="flex flex-wrap items-center justify-between gap-3">
|
||||
<div className="flex flex-wrap items-center gap-3 text-xs text-neutral-600">
|
||||
{canUseTemplates ? (
|
||||
<DropdownMenu>
|
||||
<DropdownMenuTrigger asChild>
|
||||
<Button
|
||||
type="button"
|
||||
variant="outline"
|
||||
size="sm"
|
||||
className="inline-flex items-center gap-2 border-slate-200 text-sm text-neutral-700 hover:bg-slate-50"
|
||||
disabled={templatesLoading}
|
||||
>
|
||||
<IconFileText className="size-4" />
|
||||
Inserir template
|
||||
</Button>
|
||||
</DropdownMenuTrigger>
|
||||
<DropdownMenuContent align="start" className="w-72">
|
||||
{templatesLoading ? (
|
||||
<div className="flex items-center gap-2 px-3 py-2 text-sm text-neutral-500">
|
||||
<Spinner className="size-4" />
|
||||
Carregando templates...
|
||||
</div>
|
||||
) : templates.length === 0 ? (
|
||||
<div className="px-3 py-2 text-sm text-neutral-500">
|
||||
Nenhum template disponível. Cadastre novos em configurações.
|
||||
</div>
|
||||
) : (
|
||||
templates.map((template) => (
|
||||
<DropdownMenuItem
|
||||
key={template.id}
|
||||
className="flex flex-col items-start whitespace-normal py-2"
|
||||
onSelect={() => insertTemplateIntoBody(template.body)}
|
||||
>
|
||||
<span className="text-sm font-medium text-neutral-800">{template.title}</span>
|
||||
</DropdownMenuItem>
|
||||
))
|
||||
)}
|
||||
</DropdownMenuContent>
|
||||
</DropdownMenu>
|
||||
) : null}
|
||||
<div className="flex items-center gap-2">
|
||||
Visibilidade:
|
||||
<Select value={visibility} onValueChange={(value) => setVisibility(value as "PUBLIC" | "INTERNAL")}>
|
||||
<SelectTrigger className={selectTriggerClass}>
|
||||
<SelectValue placeholder="Visibilidade" />
|
||||
</SelectTrigger>
|
||||
<SelectContent className="rounded-lg border border-slate-200 bg-white text-neutral-800 shadow-sm">
|
||||
<SelectItem value="PUBLIC">Pública</SelectItem>
|
||||
<SelectItem value="INTERNAL">Interna</SelectItem>
|
||||
</SelectContent>
|
||||
</Select>
|
||||
</div>
|
||||
</div>
|
||||
<Button type="submit" size="sm" className={submitButtonClass}>
|
||||
Enviar
|
||||
|
|
|
|||
Loading…
Add table
Add a link
Reference in a new issue