From e6c841383e14f7b66dabc04ecffdde373c851d1d Mon Sep 17 00:00:00 2001 From: codex-bot Date: Thu, 23 Oct 2025 10:17:24 -0300 Subject: [PATCH] fix(mentions): search numeric references directly in DB and avoid Tiptap duplicate 'link' extension by configuring link via StarterKit --- src/app/api/tickets/mentions/route.ts | 68 +++++++++++++++----------- src/components/ui/rich-text-editor.tsx | 14 +++--- 2 files changed, 46 insertions(+), 36 deletions(-) diff --git a/src/app/api/tickets/mentions/route.ts b/src/app/api/tickets/mentions/route.ts index 3c3a973..ff271d7 100644 --- a/src/app/api/tickets/mentions/route.ts +++ b/src/app/api/tickets/mentions/route.ts @@ -43,35 +43,46 @@ export async function GET(request: Request) { const numericQuery = /^\d+$/.test(query) - const take = numericQuery ? MAX_RESULTS : MAX_SCAN - - const tickets = await prisma.ticket.findMany({ - where: whereBase, - include: { - assignee: { select: { name: true } }, - requester: { select: { name: true } }, - company: { select: { name: true } }, - }, - orderBy: { updatedAt: "desc" }, - take, - }) - - const lowered = query.toLowerCase() - - const filtered = tickets - .filter((ticket) => { - if (!query) return true - const referenceMatch = String(ticket.reference).includes(query) - if (referenceMatch) return true - const subject = ticket.subject ?? "" - if (subject.toLowerCase().includes(lowered)) return true - const requesterName = ticket.requester?.name ?? "" - if (requesterName.toLowerCase().includes(lowered)) return true - const companyName = ticket.company?.name ?? "" - if (companyName.toLowerCase().includes(lowered)) return true - return false + // Fast path for numeric query: exact reference match at DB level + let filtered: any[] = [] + if (numericQuery) { + const ref = Number(query) + const exact = await prisma.ticket.findMany({ + where: { ...whereBase, reference: ref }, + include: { + assignee: { select: { name: true } }, + requester: { select: { name: true } }, + company: { select: { name: true } }, + }, + take: MAX_RESULTS, }) - .slice(0, MAX_RESULTS) + filtered = exact + } else { + const tickets = await prisma.ticket.findMany({ + where: whereBase, + include: { + assignee: { select: { name: true } }, + requester: { select: { name: true } }, + company: { select: { name: true } }, + }, + orderBy: { updatedAt: "desc" }, + take: MAX_SCAN, + }) + + const lowered = query.toLowerCase() + filtered = tickets + .filter((ticket) => { + if (!query) return true + const subject = ticket.subject ?? "" + if (subject.toLowerCase().includes(lowered)) return true + const requesterName = ticket.requester?.name ?? "" + if (requesterName.toLowerCase().includes(lowered)) return true + const companyName = ticket.company?.name ?? "" + if (companyName.toLowerCase().includes(lowered)) return true + return false + }) + .slice(0, MAX_RESULTS) + } const basePath = isAgentOrAdmin ? "/tickets" : "/portal/tickets" @@ -93,4 +104,3 @@ export async function GET(request: Request) { return NextResponse.json({ items }) } - diff --git a/src/components/ui/rich-text-editor.tsx b/src/components/ui/rich-text-editor.tsx index 96ad54a..0737e5e 100644 --- a/src/components/ui/rich-text-editor.tsx +++ b/src/components/ui/rich-text-editor.tsx @@ -11,7 +11,6 @@ import { import type { ReactNode } from "react" import { useEditor, EditorContent } from "@tiptap/react" import StarterKit from "@tiptap/starter-kit" -import Link from "@tiptap/extension-link" import Placeholder from "@tiptap/extension-placeholder" import Mention from "@tiptap/extension-mention" import { ReactRenderer } from "@tiptap/react" @@ -412,12 +411,13 @@ export function RichTextEditor({ StarterKit.configure({ bulletList: { keepMarks: true }, orderedList: { keepMarks: true }, - }), - Link.configure({ - openOnClick: true, - autolink: true, - protocols: ["http", "https", "mailto"], - HTMLAttributes: { rel: "noopener noreferrer", target: "_blank" }, + // Configure built-in link from StarterKit to avoid duplicate extension + link: { + openOnClick: true, + autolink: true, + protocols: ["http", "https", "mailto"], + HTMLAttributes: { rel: "noopener noreferrer", target: "_blank" }, + }, }), Placeholder.configure({ placeholder }), ]