import { getTranslations } from "next-intl/server"; import { StatusCard } from "@/components/admin/dashboard"; import { prisma } from "@/lib/prisma"; import { redirect } from "next/navigation"; import { canAccess, getAdminContext, PERMS } from "@/lib/permissions"; export const dynamic = "force-dynamic"; // navigator_flatcats โ€” emulator-owned flat (private-room) categories shown in the // navigator. The Prisma delegate exists because the table has a primary key. type Flatcat = { id: number; caption: string; captionSave: string; minRank: number; maxUserCount: number; canTrade: string; public: string; listType: number; orderNum: number; }; // navigator_publics โ€” emulator-owned public-room assignments. This table has no // primary/unique key, so Prisma marks the model `@@ignore` and generates NO // delegate (prisma.navigatorPublics does not exist). It must be read via raw SQL. type PublicRow = { public_cat_id: number; room_id: number; visible: string; }; // Reusable yes/no pill so emulator boolean-ish "1"/"0" columns read consistently. function YesNo({ on }: { on: boolean }) { return ( {on ? "Yes" : "No"} ); } export default async function AdminNavigation() { const { session, permissions } = await getAdminContext(); if (!canAccess(permissions, PERMS.PAGES_VIEW, session.user.rank)) { redirect("/admin"); } const t = await getTranslations("pages.admin.navigation"); let flatcats: Flatcat[] = []; try { flatcats = await prisma.navigatorFlatcats.findMany({ select: { id: true, caption: true, captionSave: true, minRank: true, maxUserCount: true, canTrade: true, public: true, listType: true, orderNum: true, }, orderBy: [{ orderNum: "asc" }, { id: "asc" }], }); } catch { flatcats = []; } // Raw read because navigator_publics is an `@@ignore`d (keyless) model. let publics: PublicRow[] = []; try { publics = await prisma.$queryRaw` SELECT public_cat_id, room_id, visible FROM navigator_publics ORDER BY public_cat_id ASC, room_id ASC `; } catch { publics = []; } // Resolve the public-category names and room names referenced by the rows above // so the read-only view is legible. Each lookup degrades to an empty map on error. const catIds = Array.from(new Set(publics.map((p) => p.public_cat_id))); const roomIds = Array.from(new Set(publics.map((p) => p.room_id))); let catNameById = new Map(); if (catIds.length) { try { const cats = await prisma.navigatorPubliccats.findMany({ where: { id: { in: catIds } }, select: { id: true, name: true }, }); catNameById = new Map(cats.map((c) => [c.id, c.name])); } catch { catNameById = new Map(); } } let roomNameById = new Map(); if (roomIds.length) { try { const rooms = await prisma.rooms.findMany({ where: { id: { in: roomIds } }, select: { id: true, name: true }, }); roomNameById = new Map(rooms.map((r) => [r.id, r.name])); } catch { roomNameById = new Map(); } } const publicFlatcats = flatcats.filter((c) => c.public === "1").length; const visiblePublics = publics.filter((p) => p.visible === "1").length; return (
0 ? "ok" : "neutral"} icon="๐Ÿ‘๏ธ" />
{/* โ”€โ”€ navigator_flatcats โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€ */}

{t("flatCategories")}

navigator_flatcats ยท {flatcats.length} {t("rows")}

{flatcats.length === 0 ? (
{t("noCategories")}
) : (
{flatcats.map((c) => ( ))}
{t("colId")} Caption Caption (save) Min rank Max users Can trade Public List type {t("colOrder")}
{c.id} {c.caption || "โ€”"} {c.captionSave || "โ€”"} {c.minRank} {c.maxUserCount} {c.listType} {c.orderNum}
)}
{/* โ”€โ”€ navigator_publics โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€ */}

{t("publicRooms")}

navigator_publics ยท {publics.length} {t("rows")}

{publics.length === 0 ? (
{t("noPublicRooms")}
) : (
{publics.map((p, i) => ( ))}
Public cat {t("colName")} Visible
{catNameById.get(p.public_cat_id) ?? "โ€”"}{" "} #{p.public_cat_id} {roomNameById.get(p.room_id) || t("unnamed")}{" "} #{p.room_id}
)}
); }