543d5b2d5e
Admin: - HomeworkEditor in lesson page: create/update/delete assignment description Student: - HomeworkSection in lesson page: view assignment, submit text + files - Resubmission allowed until curator gives feedback - Shows feedback from curator with date and name Curator: - New layout with Second Brain dark sidebar (replaces green theme) - /curator/dashboard: stats cards (pending, total, reviewed this week) - /curator/homework: list of all submissions, pending highlighted - /curator/homework/[id]: review submission, write feedback, redirect after send Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
133 lines
5.1 KiB
TypeScript
133 lines
5.1 KiB
TypeScript
import { prisma } from "@/lib/prisma";
|
||
import { notFound } from "next/navigation";
|
||
import Link from "next/link";
|
||
import { FeedbackForm } from "./feedback-form";
|
||
|
||
interface Props {
|
||
params: Promise<{ submissionId: string }>;
|
||
}
|
||
|
||
function formatSize(bytes: number) {
|
||
if (bytes < 1024) return `${bytes} Б`;
|
||
if (bytes < 1024 * 1024) return `${(bytes / 1024).toFixed(0)} КБ`;
|
||
return `${(bytes / 1024 / 1024).toFixed(1)} МБ`;
|
||
}
|
||
|
||
export default async function SubmissionPage({ params }: Props) {
|
||
const { submissionId } = await params;
|
||
|
||
const submission = await prisma.homeworkSubmission.findUnique({
|
||
where: { id: submissionId },
|
||
include: {
|
||
user: { select: { name: true, email: true } },
|
||
feedbacks: {
|
||
include: { curator: { select: { name: true } } },
|
||
orderBy: { createdAt: "desc" },
|
||
},
|
||
homework: {
|
||
include: {
|
||
lesson: {
|
||
select: {
|
||
title: true,
|
||
module: { select: { title: true, course: { select: { title: true } } } },
|
||
},
|
||
},
|
||
},
|
||
},
|
||
},
|
||
});
|
||
|
||
if (!submission) notFound();
|
||
|
||
const files = (submission.files as { name: string; url: string; size: number }[]) ?? [];
|
||
const isReviewed = submission.feedbacks.length > 0;
|
||
|
||
return (
|
||
<div className="p-8 max-w-2xl">
|
||
<nav className="text-xs mb-6 uppercase tracking-widest" style={{ color: "var(--muted-foreground)" }}>
|
||
<Link href="/curator/homework" className="hover:underline">ДЗ на проверку</Link>
|
||
<span className="mx-2">/</span>
|
||
<span style={{ color: "var(--foreground)" }}>{submission.user.name}</span>
|
||
</nav>
|
||
|
||
{/* Meta */}
|
||
<div className="mb-6">
|
||
<h1 className="text-xl font-bold">{submission.homework.lesson.title}</h1>
|
||
<p className="text-sm mt-1" style={{ color: "var(--muted-foreground)" }}>
|
||
{submission.homework.lesson.module.course.title} · {submission.homework.lesson.module.title}
|
||
</p>
|
||
</div>
|
||
|
||
{/* Student info */}
|
||
<div className="flex items-center justify-between px-4 py-3 mb-6" style={{ border: "2px solid var(--border)", background: "var(--color-surface)" }}>
|
||
<div>
|
||
<p className="font-medium text-sm">{submission.user.name}</p>
|
||
<p className="text-xs" style={{ color: "var(--muted-foreground)" }}>{submission.user.email}</p>
|
||
</div>
|
||
<p className="text-xs" style={{ color: "var(--muted-foreground)" }}>
|
||
Сдано {new Date(submission.submittedAt).toLocaleDateString("ru-RU")}
|
||
</p>
|
||
</div>
|
||
|
||
{/* Homework description */}
|
||
<div className="mb-4">
|
||
<p className="text-xs font-bold uppercase tracking-widest mb-2" style={{ color: "var(--muted-foreground)" }}>Задание</p>
|
||
<div className="px-4 py-3 text-sm whitespace-pre-wrap" style={{ border: "2px solid var(--border)", background: "var(--color-surface)" }}>
|
||
{submission.homework.description}
|
||
</div>
|
||
</div>
|
||
|
||
{/* Student answer */}
|
||
<div className="mb-4">
|
||
<p className="text-xs font-bold uppercase tracking-widest mb-2" style={{ color: "var(--muted-foreground)" }}>Ответ студента</p>
|
||
{submission.text ? (
|
||
<div className="px-4 py-3 text-sm whitespace-pre-wrap" style={{ border: "2px solid var(--border)" }}>
|
||
{submission.text}
|
||
</div>
|
||
) : (
|
||
<p className="text-sm italic" style={{ color: "var(--muted-foreground)" }}>Текст не добавлен</p>
|
||
)}
|
||
</div>
|
||
|
||
{/* Files */}
|
||
{files.length > 0 && (
|
||
<div className="mb-6">
|
||
<p className="text-xs font-bold uppercase tracking-widest mb-2" style={{ color: "var(--muted-foreground)" }}>Прикреплённые файлы</p>
|
||
<div className="space-y-1">
|
||
{files.map((f) => (
|
||
<a
|
||
key={f.url}
|
||
href={f.url}
|
||
target="_blank"
|
||
rel="noopener noreferrer"
|
||
className="flex items-center gap-3 px-3 py-2 text-sm"
|
||
style={{ border: "2px solid var(--border)" }}
|
||
>
|
||
<span>📎</span>
|
||
<span className="flex-1 underline">{f.name}</span>
|
||
<span className="text-xs" style={{ color: "var(--muted-foreground)" }}>{formatSize(f.size)}</span>
|
||
</a>
|
||
))}
|
||
</div>
|
||
</div>
|
||
)}
|
||
|
||
{/* Existing feedback */}
|
||
{submission.feedbacks.map((fb) => (
|
||
<div key={fb.id} className="mb-4 px-4 py-3" style={{ border: "2px solid var(--foreground)", background: "var(--accent)" }}>
|
||
<div className="flex items-center justify-between mb-1">
|
||
<p className="text-xs font-bold uppercase tracking-widest">Фидбек</p>
|
||
<span className="text-xs" style={{ color: "var(--muted-foreground)" }}>
|
||
{fb.curator.name} · {new Date(fb.createdAt).toLocaleDateString("ru-RU")}
|
||
</span>
|
||
</div>
|
||
<p className="text-sm whitespace-pre-wrap">{fb.text}</p>
|
||
</div>
|
||
))}
|
||
|
||
{/* Feedback form */}
|
||
{!isReviewed && <FeedbackForm submissionId={submissionId} />}
|
||
</div>
|
||
);
|
||
}
|