diff --git a/src/app/curator/homework/[submissionId]/actions.ts b/src/app/curator/homework/[submissionId]/actions.ts index cb2dfe2..372b359 100644 --- a/src/app/curator/homework/[submissionId]/actions.ts +++ b/src/app/curator/homework/[submissionId]/actions.ts @@ -108,6 +108,51 @@ export async function setReviewing(submissionId: string) { revalidatePath(`/curator/homework/${submissionId}`); } +/** + * Принять ДЗ без письменного ответа: ставим APPROVED и засчитываем урок, + * фидбек не создаём и студенту письмо не шлём (тихое принятие). + */ +export async function approveWithoutFeedback(submissionId: string) { + await requireCurator(); + + const submission = await prisma.homeworkSubmission.findUnique({ + where: { id: submissionId }, + include: { + homework: { + include: { + lesson: { + select: { + id: true, + module: { select: { course: { select: { slug: true } } } }, + }, + }, + }, + }, + }, + }); + if (!submission) throw new Error("Submission not found"); + + const lesson = submission.homework.lesson; + + await prisma.$transaction(async (tx) => { + await tx.homeworkSubmission.update({ + where: { id: submissionId }, + data: { status: "APPROVED", statusAt: new Date() }, + }); + await tx.lessonProgress.upsert({ + where: { userId_lessonId: { userId: submission.userId, lessonId: lesson.id } }, + create: { userId: submission.userId, lessonId: lesson.id }, + update: {}, + }); + }); + + revalidatePath("/curator/homework"); + revalidatePath(`/curator/homework/${submissionId}`); + revalidatePath(`/courses/${lesson.module.course.slug}/lessons/${lesson.id}`); + revalidatePath(`/courses/${lesson.module.course.slug}`); + revalidatePath("/dashboard"); +} + export async function deleteSubmission(submissionId: string) { await requireCurator(); await prisma.homeworkSubmission.delete({ where: { id: submissionId } }); diff --git a/src/app/curator/homework/[submissionId]/feedback-form.tsx b/src/app/curator/homework/[submissionId]/feedback-form.tsx index 5db9c8e..d86080f 100644 --- a/src/app/curator/homework/[submissionId]/feedback-form.tsx +++ b/src/app/curator/homework/[submissionId]/feedback-form.tsx @@ -3,7 +3,7 @@ import { useState, useTransition, useRef } from "react"; import { useRouter } from "next/navigation"; import Link from "next/link"; -import { submitFeedback, setReviewing } from "./actions"; +import { submitFeedback, setReviewing, approveWithoutFeedback } from "./actions"; import { AudioRecorder } from "@/components/curator/audio-recorder"; interface FileItem { @@ -69,6 +69,13 @@ export function FeedbackForm({ }); } + function handleApproveNoFeedback() { + startTransition(async () => { + await approveWithoutFeedback(submissionId); + router.push("/curator/homework"); + }); + } + const isWorking = pending || uploading; return ( @@ -164,6 +171,17 @@ export function FeedbackForm({ {pending ? "Отправка..." : "Отправить ответ"} + + Принять и отметить выполненным + + {currentStatus !== "REVIEWING" && ( 0; + const isApproved = submission?.status === "APPROVED"; + // Финализировано: есть фидбек ИЛИ ДЗ принято кнопкой без ответа. Нельзя пересдавать. + const isLocked = Boolean(isReviewed || isApproved); const inputStyle = { border: "2px solid var(--border)", @@ -138,8 +142,45 @@ export function HomeworkSection({ homework, submission, slug, lessonId, allowAud )} + {/* Принято без письменного ответа */} + {isApproved && !isReviewed && submission && ( + + + + ДЗ принято ✓ + + + Сдано {new Date(submission.submittedAt).toLocaleDateString("ru-RU")} + + + {submission.text && ( + + {submission.text} + + )} + {submission.audioUrl && ( + + Аудио-ответ: + + + )} + {submission.files.length > 0 && ( + + {submission.files.map((f) => ( + + 📎 + {f.name} + {formatSize(f.size)} + + ))} + + )} + + )} + {/* Submitted, pending review */} - {submission && !isReviewed && !editing && ( + {submission && !isLocked && !editing && ( @@ -182,7 +223,7 @@ export function HomeworkSection({ homework, submission, slug, lessonId, allowAud )} {/* Form */} - {editing && !isReviewed && ( + {editing && !isLocked && ( 0) { + if ((existing?.feedbacks && existing.feedbacks.length > 0) || existing?.status === "APPROVED") { throw new Error("Работа уже проверена"); }
Аудио-ответ: