fix(mentions): search numeric references directly in DB and avoid Tiptap duplicate 'link' extension by configuring link via StarterKit

This commit is contained in:
codex-bot 2025-10-23 10:17:24 -03:00
parent 4374b1c777
commit e6c841383e
2 changed files with 46 additions and 36 deletions

View file

@ -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 })
}

View file

@ -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 }),
]