import { redirect } from "next/navigation"; import { getTranslations } from "next-intl/server"; import { StatusCard } from "@/components/admin/dashboard"; import { TicketQueueBanner } from "@/components/admin/ticket-queue-banner"; 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 TicketRow, TicketsTable } from "./tickets-table"; export const dynamic = "force-dynamic"; export default async function AdminTicketsPage({ 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.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] = 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), ]); 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")}
) : ( )}
); }