import type { Metadata } from "next";
import Link from "next/link";
import { cookies } from "next/headers";
import {
UserPlus,
LayoutGrid,
ClipboardCheck,
ChevronRight,
MessageSquareText,
FileText,
Users,
LayoutDashboard,
Coins,
} from "lucide-react";
import {
fetchOverview,
fetchAdminPendingCounts,
type PendingCounts,
} from "@/lib/api";
import { authCookieHeader } from "@/lib/cookies";
import { getMeCached } from "@/lib/serverData";
import { canModerateAny, isSuperOrOwner, isAdminOrAbove } from "@/lib/roles";
import Avatar from "@/components/Avatar";
import { AdminPageHeader, AdminStatCard } from "@/components/admin";
import { formatRelative } from "@/lib/format";
export const metadata: Metadata = {
title: "管理仪表盘",
};
const fmt = (n: number) => n.toLocaleString("zh-CN");
function greeting(hour: number) {
if (hour < 6) return "凌晨好";
if (hour < 12) return "上午好";
if (hour < 14) return "中午好";
if (hour < 18) return "下午好";
return "晚上好";
}
function PanelHead({ title, extra }: { title: string; extra?: string }) {
return (
{title}
{extra && (
{extra}
)}
);
}
/** 待办:待审帖子 / 评论,进入内容管理的待审筛选 */
function PendingWorkPanel({ counts }: { counts: PendingCounts }) {
const total = counts.posts + counts.comments;
const idle = total === 0;
return (
0 || counts.comments === 0
? "/admin/content?status=pending"
: "/admin/content?status=pending&kind=comments"
}
className="panel hover-lift relative block p-4 sm:p-5 mb-4 group"
>
待审内容
{idle ? (
暂无待审
) : (
{fmt(total)} 项待处理
)}
待审帖子
{fmt(counts.posts)}
待审评论
{fmt(counts.comments)}
);
}
export default async function AdminDashboardPage() {
const cookie = authCookieHeader(await cookies());
const me = await getMeCached(cookie || undefined);
const canMod = canModerateAny(me.user);
const canUsers = isSuperOrOwner(me.user?.role);
const canEconomy = isAdminOrAbove(me.user?.role);
const [overview, pendingCounts] = await Promise.all([
fetchOverview(cookie).catch(() => null),
canMod
? fetchAdminPendingCounts(cookie).catch(() => ({ posts: 0, comments: 0 }))
: Promise.resolve(null),
]);
const now = new Date();
const dateLabel = new Intl.DateTimeFormat("zh-CN", {
year: "numeric",
month: "long",
day: "numeric",
weekday: "long",
}).format(now);
const stats = overview?.stats;
const boards = overview?.boards ?? [];
const newUsers = overview?.new_users ?? [];
const topBoards = [...boards].sort((a, b) => b.post_count - a.post_count).slice(0, 6);
const maxBoardCount = Math.max(1, ...topBoards.map((b) => b.post_count));
return (
{canMod && pendingCounts &&
}
{canEconomy && (
}
color="var(--gold)"
soft="var(--gold-soft)"
/>
{stats && (
}
/>
)}
)}
{newUsers.length === 0 ? (
暂无成员数据
) : (
newUsers.slice(0, 6).map((u) => (
{u.nickname}
@{u.username}
{formatRelative(u.created_at)}加入
))
)}
{canUsers && (
前往用户权限
)}
{topBoards.length === 0 ? (
暂无板块数据
) : (
topBoards.map((b) => (
{b.name}
{fmt(b.post_count)}
))
)}
);
}