diff --git a/src/app/(admin)/dashboard/ticket-board.tsx b/src/app/(admin)/dashboard/ticket-board.tsx index 006e5c1..34709ea 100644 --- a/src/app/(admin)/dashboard/ticket-board.tsx +++ b/src/app/(admin)/dashboard/ticket-board.tsx @@ -243,6 +243,17 @@ function TicketCard({ ticket }: { ticket: TicketDTO }) { {formatRelativeTime(ticket.lastMessageAt)} + +
+ {ticket.assigneeName ? ( + <> + + {ticket.assigneeName} + + ) : ( + Не назначено + )} +
); diff --git a/src/app/(admin)/layout.tsx b/src/app/(admin)/layout.tsx index e06ad7a..50f1fcc 100644 --- a/src/app/(admin)/layout.tsx +++ b/src/app/(admin)/layout.tsx @@ -22,7 +22,7 @@ export default async function AdminLayout({ children }: { children: React.ReactN return (
-
+
@@ -41,7 +41,7 @@ export default async function AdminLayout({ children }: { children: React.ReactN
-
{children}
+
{children}
diff --git a/src/lib/tickets/service.ts b/src/lib/tickets/service.ts index 2b343cf..2e60e5c 100644 --- a/src/lib/tickets/service.ts +++ b/src/lib/tickets/service.ts @@ -32,7 +32,12 @@ type AttachmentInput = { isInline?: boolean; }; -function toTicketDTO(ticket: typeof tickets.$inferSelect, customerName: string, tags: TagDTO[]): TicketDTO { +function toTicketDTO( + ticket: typeof tickets.$inferSelect, + customerName: string, + tags: TagDTO[], + assigneeName: string | null = null, +): TicketDTO { return { id: ticket.id, subject: ticket.subject, @@ -42,6 +47,7 @@ function toTicketDTO(ticket: typeof tickets.$inferSelect, customerName: string, customerId: ticket.customerId, customerName, assigneeId: ticket.assigneeId, + assigneeName, tags, createdAt: ticket.createdAt.getTime(), updatedAt: ticket.updatedAt.getTime(), @@ -49,6 +55,13 @@ function toTicketDTO(ticket: typeof tickets.$inferSelect, customerName: string, }; } +/** Single-ticket lookups only — listTickets/listTicketsForCustomer LEFT JOIN users directly instead, to avoid N+1 queries across a whole board. */ +async function getAssigneeName(assigneeId: string | null): Promise { + if (!assigneeId) return null; + const user = await db.query.users.findFirst({ where: eq(users.id, assigneeId) }); + return user?.name ?? null; +} + function toMessageDTO(message: typeof messages.$inferSelect, attachments: AttachmentDTO[] = []): MessageDTO { return { id: message.id, @@ -170,7 +183,7 @@ async function appendMessage(params: { const ticket = await touchTicket(params.ticket.id, { lastMessageAt: new Date() }); const name = await customerName(ticket.customerId); const ticketTagsList = await getTagsForTicket(ticket.id); - const dto = toTicketDTO(ticket, name, ticketTagsList); + const dto = toTicketDTO(ticket, name, ticketTagsList, await getAssigneeName(ticket.assigneeId)); const messageDto = toMessageDTO(message, attachmentDTOs); publishTicketEvent({ @@ -437,14 +450,14 @@ export async function setTicketStatus( status: (typeof tickets.$inferSelect)["status"], ) { const ticket = await touchTicket(ticketId, { status }); - const dto = toTicketDTO(ticket, await customerName(ticket.customerId), await getTagsForTicket(ticket.id)); + const dto = toTicketDTO(ticket, await customerName(ticket.customerId), await getTagsForTicket(ticket.id), await getAssigneeName(ticket.assigneeId)); publishTicketEvent({ type: "ticket.updated", ticket: dto }); return dto; } export async function assignTicket(ticketId: string, assigneeId: string | null) { const ticket = await touchTicket(ticketId, { assigneeId }); - const dto = toTicketDTO(ticket, await customerName(ticket.customerId), await getTagsForTicket(ticket.id)); + const dto = toTicketDTO(ticket, await customerName(ticket.customerId), await getTagsForTicket(ticket.id), await getAssigneeName(ticket.assigneeId)); publishTicketEvent({ type: "ticket.updated", ticket: dto }); return dto; } @@ -485,9 +498,10 @@ export async function listTickets( } const rows = await db - .select({ ticket: tickets, customerName: customers.displayName }) + .select({ ticket: tickets, customerName: customers.displayName, assigneeName: users.name }) .from(tickets) .innerJoin(customers, eq(tickets.customerId, customers.id)) + .leftJoin(users, eq(tickets.assigneeId, users.id)) .orderBy(desc(tickets.lastMessageAt)); const idsToKeep = matchingTicketIds; @@ -498,7 +512,9 @@ export async function listTickets( } const tagsMap = await getTagsForTickets(filteredRows.map((r) => r.ticket.id)); - return filteredRows.map((row) => toTicketDTO(row.ticket, row.customerName, tagsMap.get(row.ticket.id) ?? [])); + return filteredRows.map((row) => + toTicketDTO(row.ticket, row.customerName, tagsMap.get(row.ticket.id) ?? [], row.assigneeName ?? null), + ); } export async function getTicketWithMessages(ticketId: string) { @@ -519,7 +535,7 @@ export async function getTicketWithMessages(ticketId: string) { const ticketTagsList = await getTagsForTicket(ticketId); return { - ticket: toTicketDTO(row[0].ticket, row[0].customerName, ticketTagsList), + ticket: toTicketDTO(row[0].ticket, row[0].customerName, ticketTagsList, await getAssigneeName(row[0].ticket.assigneeId)), messages: ticketMessages.map((m) => toMessageDTO(m, attachmentsMap.get(m.id) ?? [])), }; } @@ -729,7 +745,12 @@ export async function setTicketTags(ticketId: string, tagIds: string[]): Promise const ticket = await db.query.tickets.findFirst({ where: eq(tickets.id, ticketId) }); if (ticket) { - const dto = toTicketDTO(ticket, await customerName(ticket.customerId), await getTagsForTicket(ticketId)); + const dto = toTicketDTO( + ticket, + await customerName(ticket.customerId), + await getTagsForTicket(ticketId), + await getAssigneeName(ticket.assigneeId), + ); publishTicketEvent({ type: "ticket.updated", ticket: dto }); } return getTagsForTicket(ticketId); diff --git a/src/lib/tickets/types.ts b/src/lib/tickets/types.ts index caee864..55530e5 100644 --- a/src/lib/tickets/types.ts +++ b/src/lib/tickets/types.ts @@ -22,6 +22,7 @@ export interface TicketDTO { customerId: string; customerName: string; assigneeId: string | null; + assigneeName: string | null; tags: TagDTO[]; createdAt: number; updatedAt: number;