import { getTranslations } from "next-intl/server"; import { type HelpTicketRow, HelpTicketsTable, } from "@/app/admin/help-tickets/help-tickets-table"; import { StatusCard } from "@/components/admin/dashboard"; import { TicketQueueBanner } from "@/components/admin/ticket-queue-banner"; import { requireModPermission } from "@/lib/admin/guard"; import { fetchTicketQueueOpenCounts } from "@/lib/admin/ticket-queue-counts"; import { calcPagination, parseListParams } from "@/lib/admin-helpers"; import { formatDate } from "@/lib/format-date"; import { PERMS } from "@/lib/permissions"; import { prisma } from "@/lib/prisma"; export default async function ModHelpTicketsPage({ searchParams, }: { searchParams: Promise>; }) { await requireModPermission([PERMS.MOD_TICKETS_VIEW, PERMS.TICKETS_VIEW]); const t = await getTranslations("pages.admin.helpTickets"); const raw = await searchParams; const parsed = parseListParams(new URLSearchParams(raw)); const statusFilter = raw.filter_status || "open"; const conditions: any[] = []; if (statusFilter === "open") { conditions.push({ open: true }); } else if (statusFilter === "closed") { conditions.push({ open: false }); } if (parsed.search.trim()) { const q = parsed.search.trim(); const asId = /^\d+$/.test(q) ? BigInt(q) : null; const matchingUsers = await prisma.user .findMany({ where: { username: { contains: q } }, select: { id: true }, take: 50, }) .catch(() => []); conditions.push({ OR: [ { title: { contains: q } }, { content: { contains: q } }, ...(matchingUsers.length > 0 ? [{ userId: { in: matchingUsers.map((u) => u.id) } }] : []), ...(asId !== null ? [{ id: asId }] : []), ], }); } const where: any = conditions.length === 0 ? {} : conditions.length === 1 ? conditions[0] : { AND: conditions }; const SORT_MAP: Record = { title: { title: "asc" }, open: { open: "desc" }, user: { userId: "asc" }, date: { createdAt: "desc" }, updated: { updatedAt: "desc" }, id: { id: "desc" }, }; const baseOrder = SORT_MAP[parsed.sort ?? "updated"] ?? { updatedAt: "desc" }; const orderField = Object.keys(baseOrder)[0] as keyof typeof baseOrder; const orderBy = { [orderField]: parsed.order, } as any; const [total, openCount, closedCount, queues] = await Promise.all([ prisma.websiteHelpCenterTickets.count({ where }).catch(() => 0), prisma.websiteHelpCenterTickets .count({ where: { open: true } }) .catch(() => 0), prisma.websiteHelpCenterTickets .count({ where: { open: false } }) .catch(() => 0), fetchTicketQueueOpenCounts(), ]); const pagination = calcPagination(total, parsed.page, parsed.perPage); const tickets = await prisma.websiteHelpCenterTickets .findMany({ where, orderBy, skip: pagination.offset, take: pagination.perPage, select: { id: true, userId: true, title: true, open: true, createdAt: true, updatedAt: true, }, }) .catch(() => []); const ticketIds = tickets.map((ticket) => ticket.id); const userIds = [ ...new Set( tickets .map((ticket) => ticket.userId) .filter((id): id is number => id != null), ), ]; const [replyGroups, users] = await Promise.all([ ticketIds.length > 0 ? prisma.websiteHelpCenterTicketReplies .groupBy({ by: ["ticketId"], where: { ticketId: { in: ticketIds } }, _count: true, }) .catch(() => []) : Promise.resolve([]), userIds.length > 0 ? prisma.user .findMany({ where: { id: { in: userIds } }, select: { id: true, username: true }, }) .catch(() => []) : Promise.resolve([]), ]); const replyCountByTicket = new Map( replyGroups.map((g) => [String(g.ticketId), g._count]), ); const usernameById = new Map(users.map((u) => [u.id, u.username])); const rows: HelpTicketRow[] = tickets.map((ticket) => ({ id: String(ticket.id), title: ticket.title, user: ticket.userId != null ? (usernameById.get(ticket.userId) ?? `#${ticket.userId}`) : "—", userId: ticket.userId, replies: replyCountByTicket.get(String(ticket.id)) ?? 0, open: ticket.open, date: formatDate(ticket.createdAt, "date"), updated: formatDate(ticket.updatedAt, "date"), })); return (
{total === 0 && !parsed.search && statusFilter === "open" ? (
{t("noTickets")}
) : ( )}
); }