import Link from "next/link";
import { redirect } from "next/navigation";
import { desc, eq } from "drizzle-orm";
import { getSession } from "@/lib/session";
import { db } from "@/db";
import { activityLog, contactMessages, members, membershipRequests, promotions } from "@/db/schema";
import { activityNodes } from "@/lib/activity";
import { can, isStaff } from "@/lib/rbac";
import { expiryStatus, getSocialAccounts, SOCIAL_LABELS } from "@/lib/social-accounts";
export const dynamic = "force-dynamic";
function StatCard({
label,
value,
hint,
valueColor = "#13324F",
hintColor = "#9a8d72",
}: {
label: string;
value: number | string;
hint: string;
valueColor?: string;
hintColor?: string;
}) {
return (
);
}
export default async function DashboardPage() {
const session = await getSession();
if (!isStaff(session?.user.role)) {
redirect("/backend/espace");
}
const [activeMembers, livePromos, pendingPromos, requests, newMessages, activity] = await Promise.all([
db.select({ id: members.id }).from(members).where(eq(members.status, "active")),
db.select({ id: promotions.id }).from(promotions).where(eq(promotions.status, "live")),
db.select({ id: promotions.id }).from(promotions).where(eq(promotions.status, "pending")),
db.select({ id: membershipRequests.id }).from(membershipRequests).where(eq(membershipRequests.status, "new")),
db.select({ id: contactMessages.id }).from(contactMessages).where(eq(contactMessages.status, "new")),
db.select().from(activityLog).orderBy(desc(activityLog.createdAt)).limit(6),
]);
const pendingCount = pendingPromos.length;
// Jetons réseaux à renouveler : sans alerte, on ne découvre l'expiration
// qu'au moment où une publication échoue.
const expiringNetworks = can(session?.user.role, "manageSettings")
? (await getSocialAccounts())
.filter((a) => a.accessToken && a.targetId)
.map((a) => ({ network: a.network, status: expiryStatus(a.expiresAt) }))
.filter((a) => a.status === "soon" || a.status === "expired")
: [];
function timeAgo(date: Date) {
const diff = Date.now() - new Date(date).getTime();
const h = Math.floor(diff / 3_600_000);
if (h < 1) return "à l'instant";
if (h < 24) return `il y a ${h} h`;
const d = Math.floor(h / 24);
return d === 1 ? "hier" : `il y a ${d} j`;
}
return (
{expiringNetworks.length > 0 && (
{expiringNetworks.map((n) => (
{SOCIAL_LABELS[n.network]} {" "}
{n.status === "expired"
? "— le jeton a expiré, les publications échoueront."
: "— le jeton expire dans moins de 7 jours."}{" "}
Reconnecter le compte →
))}
)}
Activité récente
{activity.length === 0 && (
Aucune activité récente.
)}
{activity.map((a) => (
{activityNodes(a.message)}
{timeAgo(a.createdAt)}
))}
Promotions à modérer
{pendingCount} en attente de validation.
Ouvrir la modération
Espace adhérent
);
}