Ajusta timeline, comentários internos e contadores de trabalho
This commit is contained in:
parent
ee18619519
commit
ef25cbe799
7 changed files with 212 additions and 69 deletions
|
|
@ -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>
|
||||
|
|
|
|||
Loading…
Add table
Add a link
Reference in a new issue