Reapply "Add missing admin action files and navigation links"
This reverts commit 4d515bc400.
This commit is contained in:
1 parent
96ed768f14
commit
5b4228261a
338 files changed
+28143
-5948
No files matched your search
+114
-153
@@ -1,164 +1,125 @@
|
||||
import Link from "next/link";
|
||||
import { getTranslations } from "next-intl/server";
|
||||
import { notFound } from "next/navigation";
|
||||
import { prisma } from "@/lib/prisma";
|
||||
import { ChevronRight } from 'lucide-react'
|
||||
import Link from 'next/link'
|
||||
import { notFound, redirect } from 'next/navigation'
|
||||
import { Fragment } from 'react'
|
||||
import { canAccess, getAdminContext, PERMS } from '@/lib/permissions'
|
||||
import { prisma } from '@/lib/prisma'
|
||||
import { loadCatalogItemsData } from '@/lib/services/catalog-items-loader'
|
||||
import { getAncestors } from '@/lib/services/catalog-tree'
|
||||
import { CatalogDetailTabs } from './catalog-detail-tabs'
|
||||
|
||||
export const dynamic = "force-dynamic";
|
||||
|
||||
type CatalogPageRow = {
|
||||
id: number;
|
||||
caption: string;
|
||||
parentId: number;
|
||||
pageLayout: string;
|
||||
minRank: number;
|
||||
visible: string;
|
||||
enabled: string;
|
||||
clubOnly: string;
|
||||
vipOnly: string;
|
||||
};
|
||||
|
||||
type CatalogItemRow = {
|
||||
id: number;
|
||||
catalogName: string;
|
||||
itemIds: string;
|
||||
amount: number;
|
||||
costCredits: number;
|
||||
costPoints: number;
|
||||
pointsType: number;
|
||||
limitedStack: number;
|
||||
limitedSells: number;
|
||||
orderNumber: number;
|
||||
clubOnly: string;
|
||||
};
|
||||
|
||||
export default async function AdminCatalogPage({
|
||||
export default async function CatalogEditPage({
|
||||
params,
|
||||
}: {
|
||||
params: Promise<{ id: string }>;
|
||||
params: Promise<{ locale: string; id: string }>
|
||||
}) {
|
||||
const t = await getTranslations("pages.admin.catalog");
|
||||
|
||||
const { id } = await params;
|
||||
const pageId = Number(id);
|
||||
if (!Number.isInteger(pageId)) notFound();
|
||||
|
||||
let page: CatalogPageRow | null = null;
|
||||
try {
|
||||
page = await prisma.catalogPages.findUnique({
|
||||
where: { id: pageId },
|
||||
select: {
|
||||
id: true,
|
||||
caption: true,
|
||||
parentId: true,
|
||||
pageLayout: true,
|
||||
minRank: true,
|
||||
visible: true,
|
||||
enabled: true,
|
||||
clubOnly: true,
|
||||
vipOnly: true,
|
||||
},
|
||||
});
|
||||
} catch {
|
||||
page = null;
|
||||
}
|
||||
if (!page) notFound();
|
||||
|
||||
let items: CatalogItemRow[] = [];
|
||||
try {
|
||||
items = await prisma.catalogItems.findMany({
|
||||
where: { pageId },
|
||||
select: {
|
||||
id: true,
|
||||
catalogName: true,
|
||||
itemIds: true,
|
||||
amount: true,
|
||||
costCredits: true,
|
||||
costPoints: true,
|
||||
pointsType: true,
|
||||
limitedStack: true,
|
||||
limitedSells: true,
|
||||
orderNumber: true,
|
||||
clubOnly: true,
|
||||
},
|
||||
orderBy: [{ orderNumber: "asc" }, { id: "asc" }],
|
||||
take: 500,
|
||||
});
|
||||
} catch {
|
||||
items = [];
|
||||
const { id } = await params
|
||||
const { session, permissions } = await getAdminContext()
|
||||
if (!canAccess(permissions, PERMS.CATALOG_VIEW, session.user.rank)) {
|
||||
redirect('/admin')
|
||||
}
|
||||
|
||||
const hidden = page.visible !== "1" || page.enabled !== "1";
|
||||
const pageId = parseInt(id, 10)
|
||||
if (Number.isNaN(pageId)) notFound()
|
||||
|
||||
const catalogPage = await prisma.catalogPage.findUnique({
|
||||
where: { id: pageId },
|
||||
})
|
||||
|
||||
if (!catalogPage) notFound()
|
||||
|
||||
const [children, itemsData, ancestors] = await Promise.all([
|
||||
prisma.catalogPage.findMany({
|
||||
where: { parentId: pageId },
|
||||
orderBy: { orderNum: 'asc' },
|
||||
select: { id: true, caption: true, enabled: true },
|
||||
}),
|
||||
loadCatalogItemsData(pageId),
|
||||
getAncestors(catalogPage.parentId),
|
||||
])
|
||||
|
||||
const {
|
||||
items: catalogItems,
|
||||
baseItems,
|
||||
catalogNameMap,
|
||||
furniDataIdList,
|
||||
furniDescriptionMap,
|
||||
furniRevisionMap,
|
||||
interactionTypes,
|
||||
allPages,
|
||||
} = itemsData
|
||||
|
||||
const canEdit = canAccess(permissions, PERMS.CATALOG_EDIT, session.user.rank)
|
||||
|
||||
return (
|
||||
<main>
|
||||
<p className="muted">
|
||||
<Link href="/admin/catalog">← {t("title")}</Link>
|
||||
</p>
|
||||
<h1>{page.caption}</h1>
|
||||
<p className="muted">
|
||||
{t("pageLabel", { id: page.id })} · {t("layoutLabel", { layout: page.pageLayout })} · {t("minRankLabel", { rank: page.minRank })} ·{" "}
|
||||
{hidden ? t("statusHidden") : t("statusVisible")}
|
||||
{page.parentId > 0 ? ` · ${t("parentLabel", { id: page.parentId })}` : ""}
|
||||
{page.clubOnly === "1" ? ` · ${t("hcOnly")}` : ""}
|
||||
{page.vipOnly === "1" ? ` · ${t("vipOnly")}` : ""}
|
||||
</p>
|
||||
<div className="space-y-6">
|
||||
{/* Breadcrumb */}
|
||||
<nav className="flex items-center gap-1 text-sm text-muted-foreground">
|
||||
<Link href={'/admin/catalog'} className="hover:text-foreground transition-colors">
|
||||
Catalog
|
||||
</Link>
|
||||
{ancestors.map((bc) => (
|
||||
<Fragment key={bc.id}>
|
||||
<ChevronRight className="h-3 w-3 flex-shrink-0" />
|
||||
<Link
|
||||
href={`/admin/catalog/${bc.id}`}
|
||||
className="hover:text-foreground transition-colors"
|
||||
>
|
||||
{bc.caption}
|
||||
</Link>
|
||||
</Fragment>
|
||||
))}
|
||||
<ChevronRight className="h-3 w-3 flex-shrink-0" />
|
||||
<span className="text-foreground font-medium">{catalogPage.caption}</span>
|
||||
</nav>
|
||||
|
||||
<h2 style={{ marginTop: "1.5rem" }}>{t("items")}</h2>
|
||||
{items.length === 0 ? (
|
||||
<p className="muted">{t("noItems")}</p>
|
||||
) : (
|
||||
<table style={{ width: "100%", borderCollapse: "collapse" }}>
|
||||
<thead>
|
||||
<tr style={{ textAlign: "left", color: "var(--muted)" }}>
|
||||
<th>{t("colId")}</th>
|
||||
<th>{t("catalogName")}</th>
|
||||
<th>{t("itemIds")}</th>
|
||||
<th>{t("amount")}</th>
|
||||
<th>{t("colCostCredits")}</th>
|
||||
<th>{t("colCostPoints")}</th>
|
||||
<th>{t("colLimited")}</th>
|
||||
<th>{t("colOrder")}</th>
|
||||
</tr>
|
||||
</thead>
|
||||
<tbody>
|
||||
{items.map((it) => (
|
||||
<tr key={it.id} style={{ borderTop: "1px solid var(--border-subtle)" }}>
|
||||
<td>{it.id}</td>
|
||||
<td>
|
||||
{it.catalogName}
|
||||
{it.clubOnly === "1" ? (
|
||||
<span className="muted"> · {t("hc")}</span>
|
||||
) : null}
|
||||
</td>
|
||||
<td className="muted">{it.itemIds}</td>
|
||||
<td>{it.amount}</td>
|
||||
<td>
|
||||
{it.costCredits > 0 ? (
|
||||
<span className="currency">{it.costCredits}</span>
|
||||
) : (
|
||||
<span className="muted">—</span>
|
||||
)}
|
||||
</td>
|
||||
<td className="muted">
|
||||
{it.costPoints > 0
|
||||
? `${it.costPoints} (${t("typeLabel", { type: it.pointsType })})`
|
||||
: "—"}
|
||||
</td>
|
||||
<td className="muted">
|
||||
{it.limitedStack > 0
|
||||
? `${it.limitedSells}/${it.limitedStack}`
|
||||
: "—"}
|
||||
</td>
|
||||
<td>{it.orderNumber}</td>
|
||||
</tr>
|
||||
))}
|
||||
</tbody>
|
||||
</table>
|
||||
)}
|
||||
{/* Title */}
|
||||
<div>
|
||||
<h1 className="text-3xl font-bold tracking-tight">
|
||||
{catalogPage.caption}
|
||||
<span className="ml-2 text-base font-normal text-muted-foreground">
|
||||
#{catalogPage.id}
|
||||
</span>
|
||||
</h1>
|
||||
<p className="text-muted-foreground">
|
||||
Layout: {catalogPage.pageLayout} · {catalogItems.length} items
|
||||
</p>
|
||||
</div>
|
||||
|
||||
<p className="muted" style={{ marginTop: "1rem" }}>
|
||||
{t("itemCount", { count: items.length })}
|
||||
</p>
|
||||
</main>
|
||||
);
|
||||
{/* Tabs */}
|
||||
<CatalogDetailTabs
|
||||
catalogPage={{
|
||||
id: catalogPage.id,
|
||||
caption: catalogPage.caption,
|
||||
parentId: catalogPage.parentId,
|
||||
pageLayout: catalogPage.pageLayout,
|
||||
enabled: catalogPage.enabled,
|
||||
visible: catalogPage.visible,
|
||||
minRank: catalogPage.minRank,
|
||||
clubOnly: catalogPage.clubOnly,
|
||||
orderNum: catalogPage.orderNum,
|
||||
iconImage: catalogPage.iconImage,
|
||||
iconColor: catalogPage.iconColor,
|
||||
pageHeadline: catalogPage.pageHeadline,
|
||||
pageTeaser: catalogPage.pageTeaser,
|
||||
pageSpecial: catalogPage.pageSpecial,
|
||||
pageText1: catalogPage.pageText1,
|
||||
pageText2: catalogPage.pageText2,
|
||||
pageTextDetails: catalogPage.pageTextDetails,
|
||||
pageTextTeaser: catalogPage.pageTextTeaser,
|
||||
}}
|
||||
items={catalogItems}
|
||||
baseItems={baseItems}
|
||||
catalogNameMap={catalogNameMap}
|
||||
childPages={children}
|
||||
pageId={catalogPage.id}
|
||||
canEdit={canEdit}
|
||||
furniDataIdList={furniDataIdList}
|
||||
furniDescriptionMap={furniDescriptionMap}
|
||||
furniRevisionMap={furniRevisionMap}
|
||||
allPages={allPages}
|
||||
interactionTypes={interactionTypes}
|
||||
/>
|
||||
</div>
|
||||
)
|
||||
}
|
||||
Reference in new issue
Block a user