Add homework review workflow: statuses, audio, file attachments, tabs
- HomeworkSubmission: add status (PENDING/REVIEWING/APPROVED/REJECTED) + statusAt - HomeworkFeedback: add files (Json) + audioUrl fields - Curator detail page: meta table, content tabs, feedback history with audio/files - FeedbackForm: file upload, audio recorder (Web Audio API + S3), action buttons - AudioRecorder component: record → preview → upload to S3 - ContentTabs: toggle between homework description and lesson content (TipTap read-only) - Homework list: 4-color status badges with proper filtering - API routes: /api/curator/upload and /api/curator/audio-upload Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
This commit is contained in:
@@ -6,17 +6,43 @@ import { headers } from "next/headers";
|
||||
import { revalidatePath } from "next/cache";
|
||||
import { sendFeedbackReceivedEmail } from "@/lib/email";
|
||||
|
||||
export async function submitFeedback(submissionId: string, text: string) {
|
||||
async function requireCurator() {
|
||||
const session = await auth.api.getSession({ headers: await headers() });
|
||||
if (!session || (session.user.role !== "curator" && session.user.role !== "admin")) {
|
||||
throw new Error("Forbidden");
|
||||
}
|
||||
return session;
|
||||
}
|
||||
|
||||
await prisma.homeworkFeedback.create({
|
||||
data: { submissionId, curatorId: session.user.id, text },
|
||||
});
|
||||
export async function submitFeedback(
|
||||
submissionId: string,
|
||||
data: {
|
||||
text: string;
|
||||
files?: { name: string; url: string; size: number }[];
|
||||
audioUrl?: string | null;
|
||||
action: "approve" | "reject";
|
||||
}
|
||||
) {
|
||||
const session = await requireCurator();
|
||||
const status = data.action === "approve" ? "APPROVED" : "REJECTED";
|
||||
|
||||
// Send email to student
|
||||
await prisma.$transaction([
|
||||
prisma.homeworkFeedback.create({
|
||||
data: {
|
||||
submissionId,
|
||||
curatorId: session.user.id,
|
||||
text: data.text,
|
||||
files: data.files ?? [],
|
||||
audioUrl: data.audioUrl ?? null,
|
||||
},
|
||||
}),
|
||||
prisma.homeworkSubmission.update({
|
||||
where: { id: submissionId },
|
||||
data: { status, statusAt: new Date() },
|
||||
}),
|
||||
]);
|
||||
|
||||
// Send email notification to student
|
||||
const submission = await prisma.homeworkSubmission.findUnique({
|
||||
where: { id: submissionId },
|
||||
include: {
|
||||
@@ -26,6 +52,7 @@ export async function submitFeedback(submissionId: string, text: string) {
|
||||
lesson: {
|
||||
select: {
|
||||
title: true,
|
||||
id: true,
|
||||
module: { select: { course: { select: { slug: true } } } },
|
||||
},
|
||||
},
|
||||
@@ -36,12 +63,12 @@ export async function submitFeedback(submissionId: string, text: string) {
|
||||
|
||||
if (submission) {
|
||||
const { lesson } = submission.homework;
|
||||
const lessonUrl = `${process.env.BETTER_AUTH_URL ?? "https://school.second-brain.ru"}/courses/${lesson.module.course.slug}/lessons/${submission.homework.lessonId}`;
|
||||
const lessonUrl = `${process.env.BETTER_AUTH_URL ?? "https://school.second-brain.ru"}/courses/${lesson.module.course.slug}/lessons/${lesson.id}`;
|
||||
await sendFeedbackReceivedEmail(
|
||||
submission.user.email,
|
||||
submission.user.name,
|
||||
lesson.title,
|
||||
text,
|
||||
data.text,
|
||||
lessonUrl
|
||||
);
|
||||
}
|
||||
@@ -49,3 +76,19 @@ export async function submitFeedback(submissionId: string, text: string) {
|
||||
revalidatePath("/curator/homework");
|
||||
revalidatePath(`/curator/homework/${submissionId}`);
|
||||
}
|
||||
|
||||
export async function setReviewing(submissionId: string) {
|
||||
await requireCurator();
|
||||
await prisma.homeworkSubmission.update({
|
||||
where: { id: submissionId },
|
||||
data: { status: "REVIEWING", statusAt: new Date() },
|
||||
});
|
||||
revalidatePath("/curator/homework");
|
||||
revalidatePath(`/curator/homework/${submissionId}`);
|
||||
}
|
||||
|
||||
export async function deleteSubmission(submissionId: string) {
|
||||
await requireCurator();
|
||||
await prisma.homeworkSubmission.delete({ where: { id: submissionId } });
|
||||
revalidatePath("/curator/homework");
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user