import { getTranslations } from "next-intl/server"; import { type TicketRow, TicketsTable, } from "@/app/admin/tickets/tickets-table"; import { StatusCard } from "@/components/admin/dashboard"; import { TicketQueueBanner } from "@/components/admin/ticket-queue-banner"; import type { Prisma } from "@/generated/prisma/client"; 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 const dynamic = "force-dynamic"; export default async function ModTicketsPage({ searchParams, }: { searchParams: Promise>; }) { await requireModPermission([PERMS.MOD_TICKETS_VIEW, PERMS.TICKETS_VIEW]); const t = await getTranslations("pages.admin.tickets"); const raw = await searchParams; const parsed = parseListParams(new URLSearchParams(raw)); const statusFilter = raw.filter_status || raw.status || "active"; const conditions: Prisma.WebsiteTicketWhereInput[] = []; if (statusFilter === "active") { conditions.push({ status: { not: "closed" } }); } else if (statusFilter && statusFilter !== "all") { conditions.push({ status: statusFilter }); } if (parsed.search.trim()) { const q = parsed.search.trim(); const asId = Number(q); conditions.push({ OR: [ { subject: { contains: q } }, { category: { contains: q } }, { creator: { username: { contains: q } } }, { assignee: { username: { contains: q } } }, ...(Number.isFinite(asId) && asId > 0 ? [{ id: asId }] : []), ], }); } const where: Prisma.WebsiteTicketWhereInput = conditions.length === 0 ? {} : conditions.length === 1 ? conditions[0] : { AND: conditions }; const SORT_MAP: Record = { subject: { subject: "asc" }, status: { status: "asc" }, priority: { priority: "asc" }, assignee: { assigneeId: "asc" }, date: { createdAt: "desc" }, id: { id: "desc" }, }; const baseOrder = SORT_MAP[parsed.sort ?? "date"] ?? { createdAt: "desc" }; const orderField = Object.keys(baseOrder)[0] as keyof typeof baseOrder; const orderBy = { [orderField]: parsed.order, } as Prisma.WebsiteTicketOrderByWithRelationInput; const [total, openCount, inProgressCount, waitingCount, closedCount, queues] = await Promise.all([ prisma.websiteTicket.count({ where }).catch(() => 0), prisma.websiteTicket.count({ where: { status: "open" } }).catch(() => 0), prisma.websiteTicket .count({ where: { status: "in_progress" } }) .catch(() => 0), prisma.websiteTicket .count({ where: { status: "waiting" } }) .catch(() => 0), prisma.websiteTicket .count({ where: { status: "closed" } }) .catch(() => 0), fetchTicketQueueOpenCounts(), ]); const pagination = calcPagination(total, parsed.page, parsed.perPage); const tickets = await prisma.websiteTicket .findMany({ where, orderBy, skip: pagination.offset, take: pagination.perPage, include: { creator: { select: { id: true, username: true } }, assignee: { select: { id: true, username: true } }, _count: { select: { messages: true } }, }, }) .catch(() => []); const rows: TicketRow[] = tickets.map((ticket) => ({ id: ticket.id, subject: ticket.subject, creator: ticket.creator.username, creatorId: ticket.creatorId, category: ticket.category, messages: ticket._count.messages, status: ticket.status, priority: ticket.priority, assignee: ticket.assignee?.username ?? "—", date: formatDate(ticket.createdAt, "date"), })); return (
0 ? "warn" : "neutral"} icon="⏳" /> 0 ? "warn" : "neutral"} icon="🕒" />
{total === 0 && !parsed.search && statusFilter === "active" ? (
{t("noTickets")}
) : ( )}
); }