From ec51dd34bbcbd1a1530c892f4e24a583bf2fbdc6 Mon Sep 17 00:00:00 2001 From: dmitriylaukhin Date: Tue, 7 Apr 2026 14:58:46 +0500 Subject: [PATCH] Replace admin dashboard stub with real stats - 4 stat cards: students (+monthly), courses (published), active enrollments (expiring alert), homework pending - Recent enrollments list (last 8) - Top courses by enrollment count - Activity counters: total lessons completed, total homework submitted - All cards link to relevant admin pages Co-Authored-By: Claude Sonnet 4.6 --- src/app/admin/dashboard/page.tsx | 213 ++++++++++++++++++++++++++++--- 1 file changed, 194 insertions(+), 19 deletions(-) diff --git a/src/app/admin/dashboard/page.tsx b/src/app/admin/dashboard/page.tsx index eb96368..894ea79 100644 --- a/src/app/admin/dashboard/page.tsx +++ b/src/app/admin/dashboard/page.tsx @@ -1,27 +1,202 @@ +import { prisma } from "@/lib/prisma"; import Link from "next/link"; -export default function AdminDashboard() { +export default async function AdminDashboard() { + const now = new Date(); + const weekAgo = new Date(now.getTime() - 7 * 24 * 60 * 60 * 1000); + const monthAgo = new Date(now.getTime() - 30 * 24 * 60 * 60 * 1000); + + const [ + totalStudents, + newStudentsMonth, + totalCourses, + publishedCourses, + activeEnrollments, + expiringWeek, + homeworkPending, + homeworkTotal, + progressTotal, + ] = await Promise.all([ + prisma.user.count({ where: { role: "student" } }), + prisma.user.count({ where: { role: "student", createdAt: { gte: monthAgo } } }), + prisma.course.count(), + prisma.course.count({ where: { published: true } }), + prisma.courseEnrollment.count({ + where: { OR: [{ expiresAt: null }, { expiresAt: { gt: now } }] }, + }), + prisma.courseEnrollment.count({ + where: { expiresAt: { gt: now, lte: new Date(now.getTime() + 7 * 24 * 60 * 60 * 1000) } }, + }), + prisma.homeworkSubmission.count({ where: { feedbacks: { none: {} } } }), + prisma.homeworkSubmission.count(), + prisma.lessonProgress.count(), + ]); + + // Recent enrollments + const recentEnrollments = await prisma.courseEnrollment.findMany({ + orderBy: { enrolledAt: "desc" }, + take: 8, + include: { + user: { select: { name: true, email: true } }, + course: { select: { title: true } }, + }, + }); + + // Most active courses (by enrollment count) + const topCourses = await prisma.course.findMany({ + where: { published: true }, + include: { _count: { select: { enrollments: true, modules: true } } }, + orderBy: { enrollments: { _count: "desc" } }, + take: 5, + }); + return ( -
-

Обзор

-

Управление платформой Second Brain.

-
- -

📚

-

Курсы

-

Управление контентом

- - -

👥

-

Пользователи

-

Управление доступом

- -
-

📊

-

Аналитика

-

Этап 10

+
+

Обзор

+

+ Платформа Second Brain · {now.toLocaleDateString("ru-RU", { day: "numeric", month: "long", year: "numeric" })} +

+ + {/* Stats grid */} +
+ + + 0 ? `${expiringWeek} истекает на неделе` : "нет истекающих"} + subAccent={expiringWeek > 0} + href="/admin/courses" + /> + 0} + href="/curator/homework" + /> +
+ +
+ {/* Recent enrollments */} +
+
+

+ Последние зачисления +

+ + Все → + +
+ {recentEnrollments.length === 0 ? ( +

Нет зачислений

+ ) : ( +
+ {recentEnrollments.map((e) => ( +
+
+

{e.user.name}

+

{e.course.title}

+
+ + {new Date(e.enrolledAt).toLocaleDateString("ru-RU")} + +
+ ))} +
+ )} +
+ + {/* Top courses + progress stat */} +
+
+
+

+ Популярные курсы +

+ + Все → + +
+ {topCourses.length === 0 ? ( +

Нет курсов

+ ) : ( +
+ {topCourses.map((c) => ( +
+
+

{c.title}

+

+ {c._count.modules} модулей +

+
+
+

{c._count.enrollments}

+

студентов

+
+
+ ))} +
+ )} +
+ +
+

+ Активность +

+
+
+

{progressTotal}

+

уроков пройдено

+
+
+

{homeworkTotal}

+

работ сдано

+
+
+
); } + +function StatCard({ + label, + value, + sub, + subAccent, + href, +}: { + label: string; + value: number; + sub?: string; + subAccent?: boolean; + href?: string; +}) { + const content = ( +
+

{value}

+

+ {label} +

+ {sub && ( +

+ {sub} +

+ )} +
+ ); + + return href ? {content} : content; +}