Add course tree, lesson actions, and module description schema

- CourseTree: expandable module/lesson overview with Eye/Video icons
- SortableLessons: Kinescope ID in create form, published toggle, move-to-module dropdown
- Actions: toggleLessonPublished, moveLessonToModule, updateModule with description
- Schema: add description field to Module model + migration

Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
This commit is contained in:
2026-04-08 13:32:30 +05:00
parent f0024c4243
commit 768a38b9d3
6 changed files with 351 additions and 39 deletions
+2 -1
View File
@@ -27,7 +27,8 @@ export async function createModule(courseId: string, formData: FormData) {
export async function updateModule(moduleId: string, courseId: string, formData: FormData) {
await requireAdmin();
const title = formData.get("title") as string;
await prisma.module.update({ where: { id: moduleId }, data: { title } });
const description = (formData.get("description") as string | null)?.trim() || null;
await prisma.module.update({ where: { id: moduleId }, data: { title, description } });
revalidatePath(`/admin/courses/${courseId}`);
}
@@ -14,8 +14,11 @@ async function requireAdmin() {
export async function createLesson(moduleId: string, courseId: string, formData: FormData) {
await requireAdmin();
const title = formData.get("title") as string;
const kinescopeId = (formData.get("kinescopeId") as string | null)?.trim() || null;
const count = await prisma.lesson.count({ where: { moduleId } });
const lesson = await prisma.lesson.create({ data: { moduleId, title, order: count } });
const lesson = await prisma.lesson.create({
data: { moduleId, title, kinescopeId, order: count },
});
revalidatePath(`/admin/courses/${courseId}/modules/${moduleId}`);
redirect(`/admin/courses/${courseId}/modules/${moduleId}/lessons/${lesson.id}`);
}
@@ -42,3 +45,46 @@ export async function reorderLessons(moduleId: string, courseId: string, ordered
);
revalidatePath(`/admin/courses/${courseId}/modules/${moduleId}`);
}
export async function toggleLessonPublished(
lessonId: string,
courseId: string,
moduleId: string,
currentValue: boolean
) {
await requireAdmin();
await prisma.lesson.update({
where: { id: lessonId },
data: { published: !currentValue },
});
revalidatePath(`/admin/courses/${courseId}/modules/${moduleId}`);
revalidatePath(`/admin/courses/${courseId}`);
}
export async function moveLessonToModule(
lessonId: string,
targetModuleId: string,
courseId: string,
sourceModuleId: string
) {
await requireAdmin();
// verify target module belongs to same course
const target = await prisma.module.findFirst({
where: { id: targetModuleId, courseId },
});
if (!target) throw new Error("Module not found");
const maxOrder = await prisma.lesson.aggregate({
where: { moduleId: targetModuleId },
_max: { order: true },
});
await prisma.lesson.update({
where: { id: lessonId },
data: { moduleId: targetModuleId, order: (maxOrder._max.order ?? -1) + 1 },
});
revalidatePath(`/admin/courses/${courseId}/modules/${sourceModuleId}`);
revalidatePath(`/admin/courses/${courseId}/modules/${targetModuleId}`);
revalidatePath(`/admin/courses/${courseId}`);
}