feat(editor): enable ticket mentions on new-ticket forms and fix @mention popup layering\n\n- New Ticket page/dialog/portal now support @ to link tickets\n- Mention popup uses fixed strategy + high z-index\n- Add minimal Tippy box styling to globals.css\n- Keeps existing server-side permissions for mentions
This commit is contained in:
parent
b0f57009ac
commit
904134604c
5 changed files with 25 additions and 2 deletions
|
|
@ -154,6 +154,11 @@
|
||||||
}
|
}
|
||||||
|
|
||||||
@layer components {
|
@layer components {
|
||||||
|
/* Tippy.js (menções do editor) */
|
||||||
|
.tippy-box {
|
||||||
|
@apply rounded-lg border border-slate-200 bg-white shadow-lg;
|
||||||
|
}
|
||||||
|
.tippy-content { @apply p-0; }
|
||||||
/* Tipografia básica para conteúdos rich text (Tiptap) */
|
/* Tipografia básica para conteúdos rich text (Tiptap) */
|
||||||
.rich-text {
|
.rich-text {
|
||||||
@apply text-foreground;
|
@apply text-foreground;
|
||||||
|
|
|
||||||
|
|
@ -28,7 +28,7 @@ import { CategorySelectFields } from "@/components/tickets/category-select"
|
||||||
|
|
||||||
export default function NewTicketPage() {
|
export default function NewTicketPage() {
|
||||||
const router = useRouter()
|
const router = useRouter()
|
||||||
const { convexUserId, isStaff } = useAuth()
|
const { convexUserId, isStaff, role } = useAuth()
|
||||||
const queuesEnabled = Boolean(isStaff && convexUserId)
|
const queuesEnabled = Boolean(isStaff && convexUserId)
|
||||||
const queueArgs = queuesEnabled
|
const queueArgs = queuesEnabled
|
||||||
? { tenantId: DEFAULT_TENANT_ID, viewerId: convexUserId as Id<"users"> }
|
? { tenantId: DEFAULT_TENANT_ID, viewerId: convexUserId as Id<"users"> }
|
||||||
|
|
@ -79,6 +79,11 @@ export default function NewTicketPage() {
|
||||||
if (hasChamados) setQueueName("Chamados")
|
if (hasChamados) setQueueName("Chamados")
|
||||||
}, [queueOptions, queueName])
|
}, [queueOptions, queueName])
|
||||||
|
|
||||||
|
const allowTicketMentions = useMemo(() => {
|
||||||
|
const normalized = (role ?? "").toLowerCase()
|
||||||
|
return normalized === "admin" || normalized === "agent" || normalized === "collaborator"
|
||||||
|
}, [role])
|
||||||
|
|
||||||
async function submit(event: React.FormEvent) {
|
async function submit(event: React.FormEvent) {
|
||||||
event.preventDefault()
|
event.preventDefault()
|
||||||
if (!convexUserId || loading) return
|
if (!convexUserId || loading) return
|
||||||
|
|
@ -201,6 +206,7 @@ export default function NewTicketPage() {
|
||||||
}
|
}
|
||||||
}}
|
}}
|
||||||
placeholder="Detalhe o problema, passos para reproduzir, links, etc."
|
placeholder="Detalhe o problema, passos para reproduzir, links, etc."
|
||||||
|
ticketMention={{ enabled: allowTicketMentions }}
|
||||||
/>
|
/>
|
||||||
{descriptionError ? <p className="text-xs font-medium text-red-500">{descriptionError}</p> : null}
|
{descriptionError ? <p className="text-xs font-medium text-red-500">{descriptionError}</p> : null}
|
||||||
</div>
|
</div>
|
||||||
|
|
|
||||||
|
|
@ -198,6 +198,7 @@ export function PortalTicketForm() {
|
||||||
placeholder="Compartilhe passos para reproduzir, mensagens de erro ou informações adicionais."
|
placeholder="Compartilhe passos para reproduzir, mensagens de erro ou informações adicionais."
|
||||||
className="rounded-2xl border border-slate-200 shadow-sm focus-within:border-neutral-900 focus-within:ring-neutral-900/20"
|
className="rounded-2xl border border-slate-200 shadow-sm focus-within:border-neutral-900 focus-within:ring-neutral-900/20"
|
||||||
disabled={machineInactive || isSubmitting}
|
disabled={machineInactive || isSubmitting}
|
||||||
|
ticketMention={{ enabled: allowTicketMentions }}
|
||||||
/>
|
/>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
|
|
@ -252,3 +253,4 @@ export function PortalTicketForm() {
|
||||||
</Card>
|
</Card>
|
||||||
)
|
)
|
||||||
}
|
}
|
||||||
|
const allowTicketMentions = true
|
||||||
|
|
|
||||||
|
|
@ -57,7 +57,7 @@ export function NewTicketDialog({ triggerClassName }: { triggerClassName?: strin
|
||||||
},
|
},
|
||||||
mode: "onTouched",
|
mode: "onTouched",
|
||||||
})
|
})
|
||||||
const { convexUserId, isStaff } = useAuth()
|
const { convexUserId, isStaff, role } = useAuth()
|
||||||
const queuesEnabled = Boolean(isStaff && convexUserId)
|
const queuesEnabled = Boolean(isStaff && convexUserId)
|
||||||
const queueArgs = queuesEnabled
|
const queueArgs = queuesEnabled
|
||||||
? { tenantId: DEFAULT_TENANT_ID, viewerId: convexUserId as Id<"users"> }
|
? { tenantId: DEFAULT_TENANT_ID, viewerId: convexUserId as Id<"users"> }
|
||||||
|
|
@ -297,6 +297,7 @@ export function NewTicketDialog({ triggerClassName }: { triggerClassName?: strin
|
||||||
})
|
})
|
||||||
}
|
}
|
||||||
placeholder="Detalhe o problema, passos para reproduzir, links, etc."
|
placeholder="Detalhe o problema, passos para reproduzir, links, etc."
|
||||||
|
ticketMention={{ enabled: allowTicketMentions }}
|
||||||
/>
|
/>
|
||||||
<FieldError
|
<FieldError
|
||||||
errors={
|
errors={
|
||||||
|
|
@ -440,3 +441,7 @@ export function NewTicketDialog({ triggerClassName }: { triggerClassName?: strin
|
||||||
</Dialog>
|
</Dialog>
|
||||||
)
|
)
|
||||||
}
|
}
|
||||||
|
const allowTicketMentions = useMemo(() => {
|
||||||
|
const normalized = (role ?? "").toLowerCase()
|
||||||
|
return normalized === "admin" || normalized === "agent" || normalized === "collaborator"
|
||||||
|
}, [role])
|
||||||
|
|
|
||||||
|
|
@ -16,6 +16,9 @@ import Placeholder from "@tiptap/extension-placeholder"
|
||||||
import Mention from "@tiptap/extension-mention"
|
import Mention from "@tiptap/extension-mention"
|
||||||
import { ReactRenderer } from "@tiptap/react"
|
import { ReactRenderer } from "@tiptap/react"
|
||||||
import tippy, { type Instance, type Props as TippyProps } from "tippy.js"
|
import tippy, { type Instance, type Props as TippyProps } from "tippy.js"
|
||||||
|
// Nota: o CSS do Tippy não é obrigatório, mas melhora muito a renderização
|
||||||
|
// do popover de sugestões. O componente aplica um z-index alto e estratégia
|
||||||
|
// "fixed" para evitar problemas de sobreposição/scrolling mesmo sem CSS global.
|
||||||
import { cn } from "@/lib/utils"
|
import { cn } from "@/lib/utils"
|
||||||
import sanitize from "sanitize-html"
|
import sanitize from "sanitize-html"
|
||||||
import { Button } from "@/components/ui/button"
|
import { Button } from "@/components/ui/button"
|
||||||
|
|
@ -357,6 +360,8 @@ const TicketMentionExtension = Mention.extend({
|
||||||
interactive: true,
|
interactive: true,
|
||||||
trigger: "manual",
|
trigger: "manual",
|
||||||
placement: "bottom-start",
|
placement: "bottom-start",
|
||||||
|
zIndex: 99999,
|
||||||
|
popperOptions: { strategy: "fixed" },
|
||||||
})
|
})
|
||||||
},
|
},
|
||||||
onUpdate(props) {
|
onUpdate(props) {
|
||||||
|
|
|
||||||
Loading…
Add table
Add a link
Reference in a new issue