diff --git a/src/app/hq-command/dashboard/page.tsx b/src/app/hq-command/dashboard/page.tsx index af755fd..9aa4d21 100644 --- a/src/app/hq-command/dashboard/page.tsx +++ b/src/app/hq-command/dashboard/page.tsx @@ -18,15 +18,65 @@ import { Server, Image as ImageIcon, Settings as SettingsIcon, + TrendingUp, + TrendingDown, + Minus, + MailCheck, + AlertCircle, + UserCheck, + Package, + Sparkles, + Stethoscope, + KeyRound, } from "lucide-react"; import { prisma } from "@/lib/prisma"; -import { logoutAdmin } from "@/app/hq-command/login/actions"; +import { logoutAdmin } from "@/app/hq-command/login/actions"; -export const revalidate = 0; +export const revalidate = 0; export default async function DashboardPage() { - const nodesCount = await prisma.globalNode.count({ where: { isActive: true } }); - const appsCount = await prisma.application.count({ where: { isActive: true } }); + const now = new Date(); + const thirtyDaysAgo = new Date(now.getTime() - 30 * 24 * 60 * 60 * 1000); + const sixtyDaysAgo = new Date(now.getTime() - 60 * 24 * 60 * 60 * 1000); + + let nodesCount = 0, appsCount = 0; + let signalsPending = 0, signalsThisMonth = 0, signalsLastMonth = 0; + let signalsOrders = 0, signalsDiag = 0, signalsConsult = 0, signalsAccess = 0; + let emailSent = 0, emailFailed = 0; + let clientsTotal = 0, clientsApproved = 0; + + try { + [ + nodesCount, appsCount, + signalsPending, signalsThisMonth, signalsLastMonth, + signalsOrders, signalsDiag, signalsConsult, signalsAccess, + emailSent, emailFailed, + clientsTotal, clientsApproved, + ] = await Promise.all([ + prisma.globalNode.count({ where: { isActive: true } }), + prisma.application.count({ where: { isActive: true } }), + prisma.operationsSignal.count({ where: { status: "PENDING" } }), + prisma.operationsSignal.count({ where: { createdAt: { gte: thirtyDaysAgo } } }), + prisma.operationsSignal.count({ where: { createdAt: { gte: sixtyDaysAgo, lt: thirtyDaysAgo } } }), + prisma.operationsSignal.count({ where: { type: "ORDER" } }), + prisma.operationsSignal.count({ where: { type: "DIAGNOSTIC" } }), + prisma.operationsSignal.count({ where: { type: "CONSULTATION" } }), + prisma.operationsSignal.count({ where: { type: "ACCESS_REQUEST" } }), + prisma.operationsSignal.count({ where: { emailSentAt: { not: null } } }), + prisma.operationsSignal.count({ where: { emailError: { not: null }, emailSentAt: null } }), + prisma.clientUser.count(), + prisma.clientUser.count({ where: { isApproved: true } }), + ]); + } catch (e) { + console.error("[dashboard] Analytics query failed:", e); + } + + const signalsTotal = signalsOrders + signalsDiag + signalsConsult + signalsAccess; + const emailTotal = emailSent + emailFailed; + const emailRate = emailTotal > 0 ? Math.round((emailSent / emailTotal) * 100) : 100; + const monthTrend = signalsLastMonth > 0 + ? Math.round(((signalsThisMonth - signalsLastMonth) / signalsLastMonth) * 100) + : signalsThisMonth > 0 ? 100 : 0; const modules = [ { @@ -184,6 +234,90 @@ export default async function DashboardPage() { + {/* ── Operations Intelligence ─────────────────────────────────────── */} +