Ajusta timeline, comentários internos e contadores de trabalho

This commit is contained in:
Esdras Renan 2025-10-07 22:12:18 -03:00
parent ee18619519
commit ef25cbe799
7 changed files with 212 additions and 69 deletions

View file

@ -27,14 +27,16 @@ interface TicketCommentsProps {
ticket: TicketWithDetails
}
const badgeInternal = "gap-1 rounded-full border border-slate-300 bg-neutral-900 px-2 py-0.5 text-xs font-semibold uppercase tracking-wide text-white"
const badgeInternal = "gap-1 rounded-full border border-slate-300 bg-neutral-900 px-2 py-0.5 text-xs font-semibold tracking-wide text-white"
const selectTriggerClass = "h-8 w-[140px] rounded-lg border border-slate-300 bg-white px-3 text-left text-sm font-medium text-neutral-800 shadow-sm focus:ring-0 data-[state=open]:border-[#00d6eb]"
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, isStaff, role } = useAuth()
const isManager = role === "manager"
const normalizedRole = role ?? null
const isManager = normalizedRole === "manager"
const canSeeInternalComments = normalizedRole === "admin" || normalizedRole === "agent"
const addComment = useMutation(api.tickets.addComment)
const removeAttachment = useMutation(api.tickets.removeCommentAttachment)
const updateComment = useMutation(api.tickets.updateComment)
@ -119,7 +121,7 @@ export function TicketComments({ ticket }: TicketCommentsProps) {
event.preventDefault()
if (!convexUserId) return
const now = new Date()
const selectedVisibility = isManager ? "PUBLIC" : visibility
const selectedVisibility = canSeeInternalComments ? visibility : "PUBLIC"
const attachments = attachmentsToSend.map((item) => ({ ...item }))
const previewsToRevoke = attachments
.map((attachment) => attachment.previewUrl)
@ -226,9 +228,22 @@ export function TicketComments({ ticket }: TicketCommentsProps) {
const isPending = commentId.startsWith("temp-")
const canEdit = Boolean(convexUserId && String(comment.author.id) === convexUserId && !isPending)
const hasBody = bodyPlain.length > 0 || isEditing
const isInternal = comment.visibility === "INTERNAL" && canSeeInternalComments
const containerClass = isInternal
? "group/comment flex gap-3 rounded-2xl border border-amber-200/80 bg-amber-50/80 px-3 py-3 shadow-[0_0_0_1px_rgba(217,119,6,0.15)]"
: "group/comment flex gap-3"
const bodyClass = isInternal
? "relative break-words rounded-xl border border-amber-200/80 bg-white px-3 py-2 text-sm leading-relaxed text-neutral-700 shadow-[0_0_0_1px_rgba(217,119,6,0.08)]"
: "relative break-words rounded-xl border border-slate-200 bg-white px-3 py-2 text-sm leading-relaxed text-neutral-700"
const bodyEditButtonClass = isInternal
? "absolute right-2 top-2 inline-flex size-7 items-center justify-center rounded-full border border-amber-200 bg-white text-amber-700 opacity-0 transition hover:border-amber-500 hover:text-amber-900 focus-visible:outline-none focus-visible:ring-2 focus-visible:ring-amber-500/30 group-hover/comment:opacity-100"
: "absolute right-2 top-2 inline-flex size-7 items-center justify-center rounded-full border border-slate-300 bg-white text-neutral-700 opacity-0 transition hover:border-black hover:text-black focus-visible:outline-none focus-visible:ring-2 focus-visible:ring-black/20 group-hover/comment:opacity-100"
const addContentButtonClass = isInternal
? "inline-flex items-center gap-2 text-sm font-medium text-amber-700 transition hover:text-amber-900"
: "inline-flex items-center gap-2 text-sm font-medium text-neutral-600 transition hover:text-neutral-900"
return (
<div key={comment.id} className="group/comment flex gap-3">
<div key={comment.id} className={containerClass}>
<Avatar className="size-9 border border-slate-200">
<AvatarImage src={comment.author.avatarUrl} alt={comment.author.name} />
<AvatarFallback>{initials}</AvatarFallback>
@ -236,7 +251,7 @@ export function TicketComments({ ticket }: TicketCommentsProps) {
<div className="flex flex-col gap-2">
<div className="flex flex-wrap items-center gap-2 text-sm">
<span className="font-semibold text-neutral-900">{comment.author.name}</span>
{comment.visibility === "INTERNAL" ? (
{comment.visibility === "INTERNAL" && canSeeInternalComments ? (
<Badge className={badgeInternal}>
<IconLock className="size-3 text-[#00e8ff]" /> Interno
</Badge>
@ -245,8 +260,19 @@ export function TicketComments({ ticket }: TicketCommentsProps) {
{formatDistanceToNow(comment.createdAt, { addSuffix: true, locale: ptBR })}
</span>
</div>
{isInternal ? (
<span className="text-xs font-semibold tracking-wide text-amber-700/80">
Comentário interno visível apenas para administradores e agentes
</span>
) : null}
{isEditing ? (
<div className="rounded-xl border border-slate-200 bg-white px-3 py-2">
<div
className={
isInternal
? "rounded-xl border border-amber-200/80 bg-white px-3 py-2 shadow-[0_0_0_1px_rgba(217,119,6,0.08)]"
: "rounded-xl border border-slate-200 bg-white px-3 py-2"
}
>
<RichTextEditor
value={editingComment?.value ?? ""}
onChange={(next) =>
@ -276,12 +302,12 @@ export function TicketComments({ ticket }: TicketCommentsProps) {
</div>
</div>
) : hasBody ? (
<div className="relative break-words rounded-xl border border-slate-200 bg-white px-3 py-2 text-sm leading-relaxed text-neutral-700">
<div className={bodyClass}>
{canEdit ? (
<button
type="button"
onClick={() => startEditingComment(commentId, storedBody)}
className="absolute right-2 top-2 inline-flex size-7 items-center justify-center rounded-full border border-slate-300 bg-white text-neutral-700 opacity-0 transition hover:border-black hover:text-black focus-visible:outline-none focus-visible:ring-2 focus-visible:ring-black/20 group-hover/comment:opacity-100"
className={bodyEditButtonClass}
aria-label="Editar comentário"
>
<PencilLine className="size-3.5" />
@ -290,11 +316,17 @@ export function TicketComments({ ticket }: TicketCommentsProps) {
<RichTextContent html={storedBody} />
</div>
) : canEdit ? (
<div className="rounded-xl border border-dashed border-slate-300 bg-white/60 px-3 py-2 text-sm text-neutral-500">
<div
className={
isInternal
? "rounded-xl border border-dashed border-amber-300 bg-amber-50/60 px-3 py-2 text-sm text-amber-700"
: "rounded-xl border border-dashed border-slate-300 bg-white/60 px-3 py-2 text-sm text-neutral-500"
}
>
<button
type="button"
onClick={() => startEditingComment(commentId, storedBody)}
className="inline-flex items-center gap-2 text-sm font-medium text-neutral-600 transition hover:text-neutral-900"
className={addContentButtonClass}
>
<PencilLine className="size-4" />
Adicionar conteúdo ao comentário
@ -418,17 +450,17 @@ export function TicketComments({ ticket }: TicketCommentsProps) {
<Select
value={visibility}
onValueChange={(value) => {
if (isManager) return
if (!canSeeInternalComments) return
setVisibility(value as "PUBLIC" | "INTERNAL")
}}
disabled={isManager}
disabled={!canSeeInternalComments}
>
<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>
{!isManager ? <SelectItem value="INTERNAL">Interna</SelectItem> : null}
{canSeeInternalComments ? <SelectItem value="INTERNAL">Interna</SelectItem> : null}
</SelectContent>
</Select>
</div>