Add admin quiz attempts viewer

- /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 <noreply@anthropic.com>
This commit is contained in:
2026-04-27 12:05:01 +05:00
parent d2150153df
commit 7242a989ba
3 changed files with 197 additions and 0 deletions
+118
View File
@@ -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 (
<div className="p-8 max-w-4xl">
<nav
className="text-xs mb-6 uppercase tracking-widest"
style={{ color: "var(--muted-foreground)" }}
>
<Link href="/admin/quizzes" className="hover:underline">
Тесты
</Link>
<span className="mx-2">/</span>
<span style={{ color: "var(--foreground)" }}>{quiz.lesson.title}</span>
</nav>
<div className="mb-6">
<h1 className="text-lg font-bold">{quiz.lesson.title}</h1>
<p className="text-xs mt-1 uppercase tracking-widest" style={{ color: "var(--muted-foreground)" }}>
{quiz.lesson.module.course.title} · {quiz.questions.length} вопросов · {quiz.attempts.length} ответов
</p>
</div>
{quiz.attempts.length === 0 ? (
<p className="text-sm" style={{ color: "var(--muted-foreground)" }}>
Ответов пока нет
</p>
) : (
<div className="space-y-4">
{quiz.attempts.map((attempt) => {
const answers = attempt.answers as Record<string, string>;
const date = new Date(attempt.createdAt).toLocaleString("ru-RU", {
day: "2-digit",
month: "2-digit",
year: "numeric",
hour: "2-digit",
minute: "2-digit",
});
return (
<div
key={attempt.id}
className="px-4 py-4 space-y-3"
style={{ border: "2px solid var(--border)" }}
>
<div className="flex items-center justify-between gap-2">
<div>
<p className="text-sm font-medium">{attempt.user.name}</p>
<p className="text-xs" style={{ color: "var(--muted-foreground)" }}>
{attempt.user.email}
</p>
</div>
<span className="text-xs" style={{ color: "var(--muted-foreground)" }}>
{date}
</span>
</div>
<div className="space-y-2 pt-1" style={{ borderTop: "1px solid var(--border)" }}>
{quiz.questions.map((q, idx) => (
<div key={q.id}>
<p
className="text-xs font-medium mb-0.5"
style={{ color: "var(--muted-foreground)" }}
>
{idx + 1}. {q.text}
</p>
<p className="text-sm whitespace-pre-wrap">
{answers[q.id]?.trim() || <span style={{ color: "var(--muted-foreground)" }}></span>}
</p>
</div>
))}
</div>
</div>
);
})}
</div>
)}
</div>
);
}