Files
EpicNext-Cms/src/app/admin/catalog/page.tsx
T

149 lines
4.9 KiB
TypeScript

import { asc, count, eq, or } from "drizzle-orm";
import { Crown, Store, Wrench } from "lucide-react";
import { unstable_cache } from "next/cache";
import Link from "next/link";
import { redirect } from "next/navigation";
import { getTranslations } from "next-intl/server";
import { AdminPageShell } from "@/components/admin/admin-page-shell";
import { OrganizeImportsLauncher } from "@/components/admin/studio/organize-imports-launcher";
import { CatalogSyncStatus } from "@/features/catalog/components/catalog-sync-status";
import { CatalogPackagesDialog } from "@/features/catalog/components/packages/catalog-packages-dialog";
import {
CatalogItems,
CatalogItemsBc,
CatalogPages,
CatalogPagesBc,
db,
} from "@/lib/db";
import { canAccess, getAdminContext, PERMS } from "@/lib/permissions";
import type { RootTab } from "@/types/catalog";
import { CatalogManagerWorkspace } from "./catalog-manager-dialog-wrapper";
interface Totals {
totalPages: number;
enabledPages: number;
totalItems: number;
}
async function loadTotals(catalogType: "normal" | "bc"): Promise<Totals> {
const pagesTable = catalogType === "bc" ? CatalogPagesBc : CatalogPages;
const itemsTable = catalogType === "bc" ? CatalogItemsBc : CatalogItems;
const [pages, items, enabled] = await Promise.all([
db.select({ total: count() }).from(pagesTable),
db.select({ total: count() }).from(itemsTable),
db
.select({ total: count() })
.from(pagesTable)
.where(eq(pagesTable.enabled, "1")),
]);
return {
totalPages: Number(pages[0]?.total ?? 0),
totalItems: Number(items[0]?.total ?? 0),
enabledPages: Number(enabled[0]?.total ?? 0),
};
}
const getCachedTotals = unstable_cache(
async (catalogType: "normal" | "bc") => loadTotals(catalogType),
["admin-catalog-totals"],
{ revalidate: 30 },
);
async function loadRootPages(catalogType: "normal" | "bc"): Promise<RootTab[]> {
const pagesTable = catalogType === "bc" ? CatalogPagesBc : CatalogPages;
const rootRaw = await db
.select()
.from(pagesTable)
.where(or(eq(pagesTable.parentId, -1), eq(pagesTable.parentId, 0)))
.orderBy(asc(pagesTable.orderNum), asc(pagesTable.id));
return rootRaw.map((p) => ({
id: p.id,
caption: p.caption,
iconImage: p.iconImage,
enabled: String(p.enabled),
orderNum: p.orderNum,
}));
}
export default async function CatalogPagesPage({
params: paramsPromise,
searchParams,
}: {
params: Promise<{ locale: string }>;
searchParams: Promise<Record<string, string>>;
}) {
await paramsPromise;
const { session, permissions } = await getAdminContext();
if (!canAccess(permissions, PERMS.CATALOG_VIEW, session.user.rank)) {
redirect("/admin");
}
const canEdit = canAccess(permissions, PERMS.CATALOG_EDIT, session.user.rank);
const t = await getTranslations("pages.admin.catalog");
const sp = await searchParams;
const catalogType = sp.catalog === "bc" ? "bc" : "normal";
const [totals, rootPages] = await Promise.all([
getCachedTotals(catalogType),
loadRootPages(catalogType),
]);
return (
<AdminPageShell
icon={Store}
title={catalogType === "bc" ? "Builder Club Catalog" : t("title")}
subtitle={t("subtitle")}
actions={
<div className="flex flex-wrap items-center gap-2">
<div className="flex items-center rounded-lg border border-[var(--admin-border)] p-0.5">
<Link
href="/admin/catalog"
className={
catalogType === "normal"
? "inline-flex items-center gap-1.5 rounded-md bg-[var(--admin-accent)]/10 px-3 py-1.5 text-sm font-medium text-[var(--admin-text)]"
: "inline-flex items-center gap-1.5 rounded-md px-3 py-1.5 text-sm font-medium text-[var(--admin-text-muted)] hover:text-[var(--admin-text)]"
}
>
<Store className="h-3.5 w-3.5" />
Normal
</Link>
<Link
href="/admin/catalog?catalog=bc"
className={
catalogType === "bc"
? "inline-flex items-center gap-1.5 rounded-md bg-[var(--admin-accent)]/10 px-3 py-1.5 text-sm font-medium text-[var(--admin-text)]"
: "inline-flex items-center gap-1.5 rounded-md px-3 py-1.5 text-sm font-medium text-[var(--admin-text-muted)] hover:text-[var(--admin-text)]"
}
>
<Crown className="h-3.5 w-3.5" />
Builder Club
</Link>
</div>
{catalogType === "normal" && (
<CatalogPackagesDialog canEdit={canEdit} />
)}
<OrganizeImportsLauncher />
<Link
className="inline-flex min-h-10 items-center gap-2 rounded-lg border border-[var(--admin-border)] px-3 text-sm font-medium text-[var(--admin-text-muted)] hover:bg-[var(--admin-accent)]/10"
href="/admin/studio/maintenance"
>
<Wrench className="h-3.5 w-3.5" />
{t("workspace.diagnostics")}
</Link>
</div>
}
>
<CatalogSyncStatus canEdit={canEdit} />
<div className="h-[calc(100dvh-13rem)] min-h-[26rem]">
<CatalogManagerWorkspace
catalogType={catalogType}
canEdit={canEdit}
totals={totals}
initialRoots={rootPages}
/>
</div>
</AdminPageShell>
);
}