From 7242a989baa86eb1a9611b722131fd84e1e505d3 Mon Sep 17 00:00:00 2001 From: dmitriylaukhin Date: Mon, 27 Apr 2026 12:05:01 +0500 Subject: [PATCH] Add admin quiz attempts viewer MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit - /admin/quizzes: list all quizzes with question and attempt counts - /admin/quizzes/[quizId]: view all student attempts with answers per question - Add "Тесты" link to admin sidebar navigation Co-Authored-By: Claude Sonnet 4.6 --- src/app/admin/quizzes/[quizId]/page.tsx | 118 ++++++++++++++++++++++++ src/app/admin/quizzes/page.tsx | 78 ++++++++++++++++ src/components/admin/admin-nav.tsx | 1 + 3 files changed, 197 insertions(+) create mode 100644 src/app/admin/quizzes/[quizId]/page.tsx create mode 100644 src/app/admin/quizzes/page.tsx diff --git a/src/app/admin/quizzes/[quizId]/page.tsx b/src/app/admin/quizzes/[quizId]/page.tsx new file mode 100644 index 0000000..32ebb07 --- /dev/null +++ b/src/app/admin/quizzes/[quizId]/page.tsx @@ -0,0 +1,118 @@ +import { prisma } from "@/lib/prisma"; +import { notFound } from "next/navigation"; +import Link from "next/link"; + +interface Props { + params: Promise<{ quizId: string }>; +} + +export default async function AdminQuizAttemptsPage({ params }: Props) { + const { quizId } = await params; + + const quiz = await prisma.quiz.findUnique({ + where: { id: quizId }, + include: { + questions: { orderBy: { order: "asc" } }, + attempts: { + orderBy: { createdAt: "desc" }, + include: { + user: { select: { name: true, email: true } }, + }, + }, + lesson: { + select: { + id: true, + title: true, + module: { + select: { + id: true, + course: { select: { id: true, title: true } }, + }, + }, + }, + }, + }, + }); + + if (!quiz) notFound(); + + const courseId = quiz.lesson.module.course.id; + const moduleId = quiz.lesson.module.id; + + return ( +
+ + +
+

{quiz.lesson.title}

+

+ {quiz.lesson.module.course.title} · {quiz.questions.length} вопросов · {quiz.attempts.length} ответов +

+
+ + {quiz.attempts.length === 0 ? ( +

+ Ответов пока нет +

+ ) : ( +
+ {quiz.attempts.map((attempt) => { + const answers = attempt.answers as Record; + const date = new Date(attempt.createdAt).toLocaleString("ru-RU", { + day: "2-digit", + month: "2-digit", + year: "numeric", + hour: "2-digit", + minute: "2-digit", + }); + + return ( +
+
+
+

{attempt.user.name}

+

+ {attempt.user.email} +

+
+ + {date} + +
+ +
+ {quiz.questions.map((q, idx) => ( +
+

+ {idx + 1}. {q.text} +

+

+ {answers[q.id]?.trim() || } +

+
+ ))} +
+
+ ); + })} +
+ )} +
+ ); +} diff --git a/src/app/admin/quizzes/page.tsx b/src/app/admin/quizzes/page.tsx new file mode 100644 index 0000000..3c3d1e3 --- /dev/null +++ b/src/app/admin/quizzes/page.tsx @@ -0,0 +1,78 @@ +import { prisma } from "@/lib/prisma"; +import Link from "next/link"; + +export const metadata = { title: "Тесты" }; + +export default async function AdminQuizzesPage() { + const quizzes = await prisma.quiz.findMany({ + orderBy: { createdAt: "desc" }, + include: { + _count: { select: { questions: true, attempts: true } }, + lesson: { + select: { + title: true, + module: { + select: { + course: { select: { title: true, slug: true } }, + }, + }, + }, + }, + }, + }); + + return ( +
+
+

+ Тесты +

+

+ {quizzes.length} тестов +

+
+ + {quizzes.length === 0 ? ( +

+ Тестов нет +

+ ) : ( +
+ {quizzes.map((quiz) => ( + +
+

+ {quiz.lesson.title} +

+

+ {quiz.lesson.module.course.title} +

+
+
+ {quiz._count.questions} вопр. + 0 ? "var(--foreground)" : undefined }} + > + {quiz._count.attempts} ответов + + +
+ + ))} +
+ )} +
+ ); +} diff --git a/src/components/admin/admin-nav.tsx b/src/components/admin/admin-nav.tsx index f16c1b6..edc55b4 100644 --- a/src/components/admin/admin-nav.tsx +++ b/src/components/admin/admin-nav.tsx @@ -9,6 +9,7 @@ const links = [ { href: "/admin/categories", label: "Категории" }, { href: "/admin/users", label: "Пользователи" }, { href: "/curator/homework", label: "ДЗ на проверку" }, + { href: "/admin/quizzes", label: "Тесты" }, { href: "/admin/comments", label: "Комментарии" }, { href: "/admin/import-export", label: "Импорт / Экспорт" }, { href: "/admin/settings", label: "Настройки" },