import { redirect } from "next/navigation"; import { getTranslations } from "next-intl/server"; import { StatusCard } from "@/components/admin/dashboard"; import type { Prisma } from "@/generated/prisma/client"; import { calcPagination, parseListParams } from "@/lib/admin-helpers"; import { formatDate } from "@/lib/format-date"; import { canAccess, getAdminContext, PERMS } from "@/lib/permissions"; import { prisma } from "@/lib/prisma"; import { type HelpTicketRow, HelpTicketsTable } from "./help-tickets-table"; export const dynamic = "force-dynamic"; export default async function AdminHelpTicketsPage({ searchParams, }: { searchParams: Promise>; }) { const { session, permissions } = await getAdminContext(); if (!canAccess(permissions, PERMS.TICKETS_VIEW, session.user.rank)) { redirect("/admin"); } 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: Prisma.WebsiteHelpCenterTicketsWhereInput[] = []; 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: Prisma.WebsiteHelpCenterTicketsWhereInput = conditions.length === 0 ? {} : conditions.length === 1 ? conditions[0] : { AND: conditions }; const SORT_MAP: Record< string, Prisma.WebsiteHelpCenterTicketsOrderByWithRelationInput > = { 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 Prisma.WebsiteHelpCenterTicketsOrderByWithRelationInput; const [total, openCount, closedCount] = 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), ]); 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 (

{t("queueHint")}

{t("queueOtherCta")}

{total === 0 && !parsed.search && statusFilter === "open" ? (
{t("noTickets")}
) : ( )}
); }