131 lines
4.4 KiB
TypeScript
131 lines
4.4 KiB
TypeScript
import Link from "next/link";
|
|
import { getTranslations } from "next-intl/server";
|
|
import { StatusCard } from "@/components/admin/dashboard";
|
|
import { prisma } from "@/lib/prisma";
|
|
import { Package } from "lucide-react";
|
|
|
|
export const dynamic = "force-dynamic";
|
|
|
|
type CatalogPageRow = {
|
|
id: number;
|
|
caption: string;
|
|
parentId: number;
|
|
pageLayout: string;
|
|
minRank: number;
|
|
orderNum: number;
|
|
visible: string;
|
|
enabled: string;
|
|
};
|
|
|
|
export default async function AdminCatalog() {
|
|
const t = await getTranslations("pages.admin.catalog");
|
|
|
|
let pages: CatalogPageRow[] = [];
|
|
try {
|
|
pages = await prisma.catalogPages.findMany({
|
|
select: {
|
|
id: true,
|
|
caption: true,
|
|
parentId: true,
|
|
pageLayout: true,
|
|
minRank: true,
|
|
orderNum: true,
|
|
visible: true,
|
|
enabled: true,
|
|
},
|
|
orderBy: [{ parentId: "asc" }, { orderNum: "asc" }, { id: "asc" }],
|
|
take: 500,
|
|
});
|
|
} catch {
|
|
pages = [];
|
|
}
|
|
|
|
const captionById = new Map<number, string>();
|
|
for (const p of pages) captionById.set(p.id, p.caption);
|
|
|
|
const visibleCount = pages.filter(
|
|
(p) => p.visible === "1" && p.enabled === "1",
|
|
).length;
|
|
const hiddenCount = pages.length - visibleCount;
|
|
const rootCount = pages.filter((p) => p.parentId <= 0).length;
|
|
|
|
return (
|
|
<main>
|
|
<div className="flex items-center gap-3 mb-6">
|
|
<div className="w-10 h-10 rounded-xl bg-gradient-to-br from-[var(--color-primary)]/20 to-[var(--color-primary)]/5 grid place-items-center">
|
|
<Package size={20} className="text-[var(--color-primary)]" />
|
|
</div>
|
|
<div>
|
|
<h1 className="m-0 text-xl font-extrabold text-[var(--color-text)]">{t("title")}</h1>
|
|
<p className="m-0 text-xs text-[var(--color-text-muted)] mt-0.5">{t("subtitle")}</p>
|
|
</div>
|
|
</div>
|
|
|
|
<div className="grid grid-cols-[repeat(auto-fit,minmax(180px,1fr))] gap-3.5 mb-6">
|
|
<StatusCard label={t("pages")} value={pages.length} icon="📚" />
|
|
<StatusCard label={t("visible")} value={visibleCount} state="ok" icon="👁️" />
|
|
<StatusCard
|
|
label={t("hidden")}
|
|
value={hiddenCount}
|
|
state={hiddenCount > 0 ? "warn" : "neutral"}
|
|
icon="🚫"
|
|
/>
|
|
<StatusCard label={t("rootPages")} value={rootCount} icon="🗂️" />
|
|
</div>
|
|
|
|
<section className="mt-6">
|
|
<h2>{t("pages")} ({pages.length})</h2>
|
|
{pages.length === 0 ? (
|
|
<p className="muted">{t("noPages")}</p>
|
|
) : (
|
|
<div style={{ overflowX: "auto" }}>
|
|
<table>
|
|
<thead>
|
|
<tr>
|
|
<th>{t("colId")}</th>
|
|
<th>{t("caption")}</th>
|
|
<th>{t("colParent")}</th>
|
|
<th>{t("colLayout")}</th>
|
|
<th>{t("colMinRank")}</th>
|
|
<th>{t("colOrder")}</th>
|
|
<th>{t("colStatus")}</th>
|
|
</tr>
|
|
</thead>
|
|
<tbody>
|
|
{pages.map((p) => {
|
|
const parentName =
|
|
p.parentId > 0 ? captionById.get(p.parentId) : null;
|
|
const hidden = p.visible !== "1" || p.enabled !== "1";
|
|
return (
|
|
<tr key={p.id}>
|
|
<td className="muted">{p.id}</td>
|
|
<td>
|
|
<Link href={`/admin/catalog/${p.id}`}>{p.caption}</Link>
|
|
</td>
|
|
<td className="muted">
|
|
{p.parentId <= 0
|
|
? "—"
|
|
: parentName
|
|
? `${parentName} (#${p.parentId})`
|
|
: `#${p.parentId}`}
|
|
</td>
|
|
<td className="muted">{p.pageLayout}</td>
|
|
<td>{p.minRank}</td>
|
|
<td>{p.orderNum}</td>
|
|
<td>
|
|
<span className={`inline-block text-[0.72rem] font-bold px-2 py-0.5 rounded-full ${hidden ? "bg-red-500/22 text-[var(--color-text)]" : "bg-green-600/20 text-[var(--color-text)]"}`}>
|
|
{hidden ? t("statusHidden") : t("statusVisible")}
|
|
</span>
|
|
</td>
|
|
</tr>
|
|
);
|
|
})}
|
|
</tbody>
|
|
</table>
|
|
</div>
|
|
)}
|
|
</section>
|
|
</main>
|
|
);
|
|
}
|