Files
EpicNext-Cms/src/app/admin/tickets/page.tsx
T
SimoandCursor d8199ea1e4
CI / check (push) Successful in 22s
CI / release (push) Skipped
CI / deploy (push) Successful in 57s
feat(admin): unified ticket inbox over CMS and help-center queues
Merged read-model inbox at /admin/tickets and /mod/tickets with type badges and deep links; CMS-only lists moved to /desk. No DB schema merge.

Co-authored-by: Cursor <[email protected]>
2026-08-01 14:49:19 +02:00

78 lines
2.3 KiB
TypeScript

import { redirect } from "next/navigation";
import { getTranslations } from "next-intl/server";
import { StatusCard } from "@/components/admin/dashboard";
import { UnifiedTicketsTable } from "@/components/admin/unified-tickets-table";
import {
fetchUnifiedTicketInbox,
type TicketInboxTypeFilter,
} from "@/lib/admin/ticket-inbox";
import { fetchTicketQueueOpenCounts } from "@/lib/admin/ticket-queue-counts";
import { parseListParams } from "@/lib/admin-helpers";
import { canAccess, getAdminContext, PERMS } from "@/lib/permissions";
export default async function AdminTicketInboxPage({
searchParams,
}: {
searchParams: Promise<Record<string, string>>;
}) {
const { session, permissions } = await getAdminContext();
if (!canAccess(permissions, PERMS.TICKETS_VIEW, session.user.rank)) {
redirect("/admin");
}
const t = await getTranslations("pages.admin.ticketInbox");
const raw = await searchParams;
const parsed = parseListParams(new URLSearchParams(raw));
const typeRaw = raw.filter_type || "all";
const type: TicketInboxTypeFilter =
typeRaw === "cms" || typeRaw === "help" ? typeRaw : "all";
const statusFilter = raw.filter_status || "open";
const openOnly = statusFilter !== "all";
const [inbox, queues] = await Promise.all([
fetchUnifiedTicketInbox({
page: parsed.page,
perPage: parsed.perPage,
search: parsed.search,
type,
openOnly,
base: "admin",
}),
fetchTicketQueueOpenCounts(),
]);
return (
<div className="space-y-6">
<p className="m-0 text-sm text-[var(--admin-text-muted)]">{t("hint")}</p>
<div className="grid grid-cols-[repeat(auto-fit,minmax(180px,1fr))] gap-3.5">
<StatusCard label={t("statCmsOpen")} value={queues.cmsOpen} icon="🎫" />
<StatusCard
label={t("statHelpOpen")}
value={queues.helpOpen}
state={queues.helpOpen > 0 ? "warn" : "neutral"}
icon="🛟"
/>
<StatusCard
label={t("statTotal")}
value={queues.cmsOpen + queues.helpOpen}
icon="📥"
/>
</div>
{inbox.total === 0 && !parsed.search ? (
<div className="admin-empty">{t("noTickets")}</div>
) : (
<UnifiedTicketsTable
data={{
rows: inbox.rows,
total: inbox.total,
page: inbox.page,
perPage: inbox.perPage,
lastPage: inbox.lastPage,
}}
/>
)}
</div>
);
}