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 (
{label}
{value}
{hint}
); } 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
); }