Files
Epicnabbo-Catalogus-Updated…/src/app/admin/calendar/page.tsx
T
SimoandCursor d2c0619f2f Clean up admin hub UX: tabs, chrome, and redundant headers.
Wire orphan routes into hub tabs, group Radio into primary/tools rows, show theme/language once on desktop, and remove duplicate page titles under AdminHubChrome.

Co-authored-by: Cursor <[email protected]>
2026-07-17 18:01:01 +02:00

126 lines
3.6 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 { redirect } from "next/navigation";
import { canAccess, getAdminContext, PERMS } from "@/lib/permissions";
export const dynamic = "force-dynamic";
type Campaign = {
id: number;
name: string;
image: string;
startTimestamp: number;
totalDays: number;
lockExpired: string;
enabled: string;
};
function fmtDate(ts: number): string {
if (!ts) return "—";
return new Date(ts * 1000).toISOString().slice(0, 10);
}
export default async function AdminCalendar() {
const { session, permissions } = await getAdminContext();
if (!canAccess(permissions, PERMS.SHOP_VIEW, session.user.rank)) {
redirect("/admin");
}
const t = await getTranslations("pages.admin.calendar");
let campaigns: Campaign[] = [];
try {
campaigns = await prisma.calendarCampaigns.findMany({
select: {
id: true,
name: true,
image: true,
startTimestamp: true,
totalDays: true,
lockExpired: true,
enabled: true,
},
orderBy: { id: "desc" },
});
} catch {
campaigns = [];
}
const rewardCounts = new Map<number, number>();
let totalRewards = 0;
try {
const rewards = await prisma.calendarRewards.findMany({
select: { campaignId: true },
});
totalRewards = rewards.length;
for (const r of rewards) {
rewardCounts.set(r.campaignId, (rewardCounts.get(r.campaignId) ?? 0) + 1);
}
} catch {}
const enabledCount = campaigns.filter((c) => c.enabled === "1").length;
return (
<main>
<div className="grid grid-cols-[repeat(auto-fit,minmax(180px,1fr))] gap-3.5 mb-6">
<StatusCard label={t("campaigns")} value={campaigns.length} icon="📅" />
<StatusCard
label={t("enabled")}
value={enabledCount}
state={enabledCount > 0 ? "ok" : "neutral"}
icon="✅"
/>
<StatusCard label={t("totalRewards")} value={totalRewards} icon="🎁" />
</div>
{campaigns.length === 0 ? (
<div className="admin-empty">{t("noCampaigns")}</div>
) : (
<div className="grid grid-cols-1 gap-4 md:grid-cols-3">
{campaigns.map((c) => {
const enabled = c.enabled === "1";
const count = rewardCounts.get(c.id) ?? 0;
return (
<Link
key={c.id}
href={`/admin/calendar/${c.id}`}
className="admin-card hover article"
>
{c.image ? (
// eslint-disable-next-line @next/next/no-img-element
<img className="article-img" src={c.image} alt={c.name} />
) : (
<div className="article-img" />
)}
<div className="body">
<div className="flex items-center justify-between gap-2 mb-1">
<h3 className="m-0">{c.name || `#${c.id}`}</h3>
<span
className={`inline-block text-[0.72rem] font-bold px-2 py-0.5 rounded-full ${enabled ? "bg-[var(--admin-success-subtle)] text-[var(--admin-text)]" : "bg-[var(--admin-error-subtle)] text-[var(--admin-text)]"}`}
>
{enabled ? t("statusEnabled") : t("statusDisabled")}
</span>
</div>
<p className="text-xs theme-text-muted dark:theme-text-muted m-0">
{t("campaignMeta", {
id: c.id,
days: c.totalDays,
rewards: count,
})}
</p>
<p className="text-xs theme-text-muted dark:theme-text-muted mt-1">
{t("startsAt", { date: fmtDate(c.startTimestamp) })}
{c.lockExpired === "1" ? ` · ${t("locksExpired")}` : ""}
</p>
</div>
</Link>
);
})}
</div>
)}
</main>
);
}