137 lines
3.3 KiB
TypeScript
137 lines
3.3 KiB
TypeScript
import { notFound, redirect } from "next/navigation";
|
|
import { getTranslations } from "next-intl/server";
|
|
import { TicketQueueBanner } from "@/components/admin/ticket-queue-banner";
|
|
import { getMinStaffRank } from "@/lib/admin/min-staff-rank";
|
|
import { positiveBigInt } from "@/lib/api";
|
|
import { canAccess, getAdminContext, PERMS } from "@/lib/permissions";
|
|
import { prisma } from "@/lib/prisma";
|
|
import { AdminHelpTicketDetail } from "./admin-help-ticket-detail";
|
|
|
|
export const dynamic = "force-dynamic";
|
|
|
|
export default async function AdminHelpTicketDetailPage({
|
|
params,
|
|
}: {
|
|
params: Promise<{ id: string }>;
|
|
}) {
|
|
const { session, permissions } = await getAdminContext();
|
|
if (!canAccess(permissions, PERMS.TICKETS_VIEW, session.user.rank)) {
|
|
redirect("/admin");
|
|
}
|
|
|
|
const t = await getTranslations("pages.admin.helpTickets");
|
|
const { id } = await params;
|
|
const ticketId = positiveBigInt(id);
|
|
if (!ticketId) notFound();
|
|
|
|
const ticket = await prisma.websiteHelpCenterTickets.findUnique({
|
|
where: { id: ticketId },
|
|
select: {
|
|
id: true,
|
|
userId: true,
|
|
title: true,
|
|
content: true,
|
|
open: true,
|
|
createdAt: true,
|
|
updatedAt: true,
|
|
},
|
|
});
|
|
|
|
if (!ticket) notFound();
|
|
|
|
const replies = await prisma.websiteHelpCenterTicketReplies.findMany({
|
|
where: { ticketId: ticket.id },
|
|
orderBy: { id: "asc" },
|
|
select: {
|
|
id: true,
|
|
userId: true,
|
|
content: true,
|
|
createdAt: true,
|
|
},
|
|
});
|
|
|
|
const authorIds = [
|
|
...new Set([
|
|
...(ticket.userId != null ? [ticket.userId] : []),
|
|
...replies.map((r) => r.userId),
|
|
]),
|
|
];
|
|
|
|
const users =
|
|
authorIds.length > 0
|
|
? await prisma.user.findMany({
|
|
where: { id: { in: authorIds } },
|
|
select: {
|
|
id: true,
|
|
username: true,
|
|
rank: true,
|
|
mail: true,
|
|
},
|
|
})
|
|
: [];
|
|
|
|
const userById = new Map(users.map((u) => [u.id, u]));
|
|
const openerId = ticket.userId;
|
|
const minStaffRank = await getMinStaffRank();
|
|
|
|
const messages = [
|
|
{
|
|
key: "opening",
|
|
userId: openerId ?? 0,
|
|
username:
|
|
openerId != null
|
|
? (userById.get(openerId)?.username ?? `#${openerId}`)
|
|
: "—",
|
|
isStaff: false,
|
|
content: ticket.content,
|
|
createdAt: (ticket.createdAt ?? new Date()).toISOString(),
|
|
},
|
|
...replies.map((r) => {
|
|
const user = userById.get(r.userId);
|
|
const isStaff =
|
|
r.userId !== openerId && (user?.rank ?? 0) >= minStaffRank;
|
|
return {
|
|
key: String(r.id),
|
|
userId: r.userId,
|
|
username: user?.username ?? `#${r.userId}`,
|
|
isStaff,
|
|
content: r.content,
|
|
createdAt: (r.createdAt ?? new Date()).toISOString(),
|
|
};
|
|
}),
|
|
];
|
|
|
|
const creator = openerId != null ? userById.get(openerId) : undefined;
|
|
|
|
const canEdit = canAccess(permissions, PERMS.TICKETS_EDIT, session.user.rank);
|
|
|
|
return (
|
|
<div className="space-y-6">
|
|
<TicketQueueBanner
|
|
hint={t("queueHint")}
|
|
otherHref="/admin/tickets"
|
|
otherLabel={t("queueOtherCta")}
|
|
/>
|
|
<AdminHelpTicketDetail
|
|
ticket={{
|
|
id: String(ticket.id),
|
|
title: ticket.title,
|
|
open: ticket.open,
|
|
createdAt: (ticket.createdAt ?? new Date()).toISOString(),
|
|
updatedAt: (ticket.updatedAt ?? new Date()).toISOString(),
|
|
creator: creator
|
|
? {
|
|
id: creator.id,
|
|
username: creator.username,
|
|
rank: creator.rank,
|
|
mail: creator.mail ?? "",
|
|
}
|
|
: null,
|
|
}}
|
|
messages={messages}
|
|
canEdit={canEdit}
|
|
/>
|
|
</div>
|
|
);
|
|
}
|