Add cross-module lesson navigation and admin edit button

- Admin lesson editor now navigates across module boundaries (full course flat list)
- Student lesson view shows "Edit" button for admins linking to lesson editor

Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
This commit is contained in:
2026-05-26 11:05:46 +05:00
parent f3428420e2
commit 287347168d
3 changed files with 28 additions and 20 deletions
@@ -107,6 +107,18 @@ export default async function LessonPage({ params }: Props) {
return ( return (
<article className="max-w-3xl mx-auto px-6 py-8"> <article className="max-w-3xl mx-auto px-6 py-8">
{/* Admin edit bar */}
{isAdmin && (
<div className="flex items-center justify-end mb-4">
<Link
href={`/admin/courses/${lesson.module.course.id}/modules/${lesson.moduleId}/lessons/${lessonId}`}
className="btn-aubade text-xs px-3 py-1.5"
>
Редактировать урок
</Link>
</div>
)}
{/* Title */} {/* Title */}
<h1 className="text-2xl font-bold mb-6 leading-snug">{lesson.title}</h1> <h1 className="text-2xl font-bold mb-6 leading-snug">{lesson.title}</h1>
@@ -13,7 +13,7 @@ interface Props {
export default async function LessonEditorPage({ params }: Props) { export default async function LessonEditorPage({ params }: Props) {
const { courseId, moduleId, lessonId } = await params; const { courseId, moduleId, lessonId } = await params;
const [lesson, siblings] = await Promise.all([ const [lesson, allCourseLessons] = await Promise.all([
prisma.lesson.findUnique({ prisma.lesson.findUnique({
where: { id: lessonId }, where: { id: lessonId },
include: { include: {
@@ -28,29 +28,27 @@ export default async function LessonEditorPage({ params }: Props) {
}, },
}), }),
prisma.lesson.findMany({ prisma.lesson.findMany({
where: { moduleId }, where: { module: { courseId } },
orderBy: { order: "asc" }, orderBy: [{ module: { order: "asc" } }, { order: "asc" }],
select: { id: true, title: true }, select: { id: true, title: true, moduleId: true },
}), }),
]); ]);
if (!lesson || lesson.moduleId !== moduleId) notFound(); if (!lesson || lesson.moduleId !== moduleId) notFound();
const idx = siblings.findIndex((l) => l.id === lessonId); const idx = allCourseLessons.findIndex((l) => l.id === lessonId);
const prevLesson = idx > 0 ? siblings[idx - 1] : null; const prevLesson = idx > 0 ? allCourseLessons[idx - 1] : null;
const nextLesson = idx < siblings.length - 1 ? siblings[idx + 1] : null; const nextLesson = idx < allCourseLessons.length - 1 ? allCourseLessons[idx + 1] : null;
// Serialize all Prisma proxy objects (DateTime, relations) before passing to Client Components // Serialize all Prisma proxy objects (DateTime, relations) before passing to Client Components
const plain = JSON.parse(JSON.stringify({ const plain = JSON.parse(JSON.stringify({
files: lesson.files, files: lesson.files,
homework: lesson.homework, homework: lesson.homework,
quiz: lesson.quiz, quiz: lesson.quiz,
siblings,
})) as { })) as {
files: typeof lesson.files; files: typeof lesson.files;
homework: typeof lesson.homework; homework: typeof lesson.homework;
quiz: typeof lesson.quiz; quiz: typeof lesson.quiz;
siblings: typeof siblings;
}; };
return ( return (
@@ -76,10 +74,9 @@ export default async function LessonEditorPage({ params }: Props) {
published: lesson.published, published: lesson.published,
}} }}
courseId={courseId} courseId={courseId}
moduleId={moduleId}
courseSlug={lesson.module.course.slug} courseSlug={lesson.module.course.slug}
prevLesson={plain.siblings[idx - 1] ?? null} prevLesson={prevLesson}
nextLesson={plain.siblings[idx + 1] ?? null} nextLesson={nextLesson}
/> />
</div> </div>
+7 -8
View File
@@ -21,19 +21,18 @@ interface LessonData {
interface SiblingLesson { interface SiblingLesson {
id: string; id: string;
title: string; title: string;
moduleId: string;
} }
export function LessonEditor({ export function LessonEditor({
lesson, lesson,
courseId, courseId,
moduleId,
courseSlug, courseSlug,
prevLesson, prevLesson,
nextLesson, nextLesson,
}: { }: {
lesson: LessonData; lesson: LessonData;
courseId: string; courseId: string;
moduleId: string;
courseSlug: string; courseSlug: string;
prevLesson?: SiblingLesson | null; prevLesson?: SiblingLesson | null;
nextLesson?: SiblingLesson | null; nextLesson?: SiblingLesson | null;
@@ -156,8 +155,8 @@ export function LessonEditor({
} }
} }
function navigateTo(lessonId: string) { function navigateTo(target: SiblingLesson) {
router.push(`/admin/courses/${courseId}/modules/${moduleId}/lessons/${lessonId}`); router.push(`/admin/courses/${courseId}/modules/${target.moduleId}/lessons/${target.id}`);
} }
return ( return (
@@ -213,18 +212,18 @@ export function LessonEditor({
<div className="flex items-center gap-1"> <div className="flex items-center gap-1">
<button <button
type="button" type="button"
onClick={() => prevLesson && navigateTo(prevLesson.id)} onClick={() => prevLesson && navigateTo(prevLesson)}
disabled={!prevLesson} disabled={!prevLesson}
title={prevLesson ? `${prevLesson.title}` : "Первый урок в модуле"} title={prevLesson ? `${prevLesson.title}` : "Первый урок курса"}
className="btn-aubade flex items-center gap-1 px-2 py-2 text-sm disabled:opacity-30" className="btn-aubade flex items-center gap-1 px-2 py-2 text-sm disabled:opacity-30"
> >
<ChevronLeft size={14} /> <ChevronLeft size={14} />
</button> </button>
<button <button
type="button" type="button"
onClick={() => nextLesson && navigateTo(nextLesson.id)} onClick={() => nextLesson && navigateTo(nextLesson)}
disabled={!nextLesson} disabled={!nextLesson}
title={nextLesson ? `${nextLesson.title}` : "Последний урок в модуле"} title={nextLesson ? `${nextLesson.title}` : "Последний урок курса"}
className="btn-aubade flex items-center gap-1 px-2 py-2 text-sm disabled:opacity-30" className="btn-aubade flex items-center gap-1 px-2 py-2 text-sm disabled:opacity-30"
> >
<ChevronRight size={14} /> <ChevronRight size={14} />