Add lesson complete button with homework-aware logic

- Show "Отметить как пройденный" button only on lessons without homework
- Show static "Пройдено" badge on homework lessons completed via approval
- Auto-create LessonProgress when curator/admin approves homework submission
- Revalidate student lesson, course, and dashboard pages on approval

Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
This commit is contained in:
2026-04-26 14:00:47 +05:00
parent 39d84a3db2
commit 3ed7bc147b
3 changed files with 61 additions and 30 deletions
@@ -179,9 +179,12 @@ export default async function LessonPage({ params }: Props) {
<div /> <div />
)} )}
{!isAdmin && ( {!isAdmin && !lesson.homework && (
<LessonCompleteButton lessonId={lessonId} slug={slug} isCompleted={isCompleted} /> <LessonCompleteButton lessonId={lessonId} slug={slug} isCompleted={isCompleted} />
)} )}
{!isAdmin && lesson.homework && isCompleted && (
<LessonCompleteButton lessonId={lessonId} slug={slug} isCompleted={true} readOnly={true} />
)}
{nextLesson ? ( {nextLesson ? (
<Link <Link
@@ -26,27 +26,10 @@ export async function submitFeedback(
const session = await requireCurator(); const session = await requireCurator();
const status = data.action === "approve" ? "APPROVED" : "REJECTED"; const status = data.action === "approve" ? "APPROVED" : "REJECTED";
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({ const submission = await prisma.homeworkSubmission.findUnique({
where: { id: submissionId }, where: { id: submissionId },
include: { include: {
user: { select: { email: true, name: true } }, user: { select: { id: true, email: true, name: true } },
homework: { homework: {
include: { include: {
lesson: { lesson: {
@@ -61,7 +44,39 @@ export async function submitFeedback(
}, },
}); });
if (submission) { if (!submission) throw new Error("Submission not found");
await prisma.$transaction(async (tx) => {
await tx.homeworkFeedback.create({
data: {
submissionId,
curatorId: session.user.id,
text: data.text,
files: data.files ?? [],
audioUrl: data.audioUrl ?? null,
},
});
await tx.homeworkSubmission.update({
where: { id: submissionId },
data: { status, statusAt: new Date() },
});
if (status === "APPROVED") {
await tx.lessonProgress.upsert({
where: {
userId_lessonId: {
userId: submission.user.id,
lessonId: submission.homework.lesson.id,
},
},
create: {
userId: submission.user.id,
lessonId: submission.homework.lesson.id,
},
update: {},
});
}
});
const { lesson } = submission.homework; const { lesson } = submission.homework;
const lessonUrl = `${process.env.BETTER_AUTH_URL ?? "https://school.second-brain.ru"}/courses/${lesson.module.course.slug}/lessons/${lesson.id}`; const lessonUrl = `${process.env.BETTER_AUTH_URL ?? "https://school.second-brain.ru"}/courses/${lesson.module.course.slug}/lessons/${lesson.id}`;
await sendFeedbackReceivedEmail( await sendFeedbackReceivedEmail(
@@ -71,10 +86,12 @@ export async function submitFeedback(
data.text, data.text,
lessonUrl lessonUrl
); );
}
revalidatePath("/curator/homework"); revalidatePath("/curator/homework");
revalidatePath(`/curator/homework/${submissionId}`); revalidatePath(`/curator/homework/${submissionId}`);
revalidatePath(`/courses/${lesson.module.course.slug}/lessons/${lesson.id}`);
revalidatePath(`/courses/${lesson.module.course.slug}`);
revalidatePath("/dashboard");
} }
export async function setReviewing(submissionId: string) { export async function setReviewing(submissionId: string) {
@@ -8,13 +8,24 @@ export function LessonCompleteButton({
lessonId, lessonId,
slug, slug,
isCompleted, isCompleted,
readOnly = false,
}: { }: {
lessonId: string; lessonId: string;
slug: string; slug: string;
isCompleted: boolean; isCompleted: boolean;
readOnly?: boolean;
}) { }) {
const [pending, startTransition] = useTransition(); const [pending, startTransition] = useTransition();
if (readOnly) {
return (
<div className="btn-aubade btn-aubade-accent flex items-center gap-2 px-5 py-2.5 text-sm cursor-default select-none">
<Check size={15} strokeWidth={3} />
Пройдено
</div>
);
}
return ( return (
<button <button
onClick={() => startTransition(() => toggleLessonProgress(lessonId, slug))} onClick={() => startTransition(() => toggleLessonProgress(lessonId, slug))}