Розроблення конструктора курсів для LMS
Конструктор курсів — це інтерфейс для викладачів, де вони створюють структуру курсу: додають розділи, уроки різних типів (відео, текст, тест, завдання), налаштовують порядок проходження та умови розблокування. Ключове завдання — зробити це інтуїтивно без технічних знань.
Модель даних
CREATE TABLE courses (
id UUID PRIMARY KEY DEFAULT gen_random_uuid(),
title VARCHAR(500) NOT NULL,
description TEXT,
instructor_id UUID REFERENCES users(id),
status VARCHAR(50) DEFAULT 'draft',
settings JSONB DEFAULT '{}',
created_at TIMESTAMPTZ DEFAULT now()
);
CREATE TABLE course_sections (
id UUID PRIMARY KEY DEFAULT gen_random_uuid(),
course_id UUID REFERENCES courses(id) ON DELETE CASCADE,
title VARCHAR(500) NOT NULL,
position INTEGER NOT NULL
);
CREATE TABLE lessons (
id UUID PRIMARY KEY DEFAULT gen_random_uuid(),
section_id UUID REFERENCES course_sections(id) ON DELETE CASCADE,
title VARCHAR(500) NOT NULL,
type VARCHAR(50) NOT NULL,
content JSONB NOT NULL DEFAULT '{}',
position INTEGER NOT NULL,
unlock_condition JSONB
);
API управління курсом
// Створення уроку
app.post('/api/courses/:courseId/sections/:sectionId/lessons', authenticate, async (req, res) => {
const { title, type, position } = req.body;
const defaultContent: Record<string, unknown> = {
video: { videoUrl: '', duration: 0, subtitles: [] },
text: { body: '' },
quiz: { questions: [], passingScore: 70, timeLimit: null },
assignment: { description: '', maxScore: 100, dueDate: null, rubric: [] },
};
const lesson = await db.lessons.create({
sectionId: req.params.sectionId,
title,
type,
content: defaultContent[type] ?? {},
position: position ?? await getNextPosition(req.params.sectionId),
});
res.json(lesson);
});
// Переупорядкування уроків (drag-and-drop)
app.patch('/api/courses/:courseId/reorder', authenticate, async (req, res) => {
const { sections } = req.body;
await db.transaction(async (trx) => {
for (const section of sections) {
for (const lesson of section.lessons) {
await trx.query(
'UPDATE lessons SET position = $1, section_id = $2 WHERE id = $3',
[lesson.position, section.id, lesson.id]
);
}
}
});
res.json({ ok: true });
});
React компонент конструктора
import { DndContext, closestCenter, DragEndEvent } from '@dnd-kit/core';
import { SortableContext, verticalListSortingStrategy, arrayMove } from '@dnd-kit/sortable';
function CourseBuilder({ courseId }: { courseId: string }) {
const [sections, setSections] = useState<Section[]>([]);
const handleDragEnd = async (event: DragEndEvent) => {
const { active, over } = event;
if (!over || active.id === over.id) return;
// Переупорядкування розділів або уроків
const reordered = arrayMove(sections,
sections.findIndex(s => s.id === active.id),
sections.findIndex(s => s.id === over.id)
).map((s, i) => ({ ...s, position: i }));
setSections(reordered);
await saveOrder(reordered);
};
const LESSON_TYPES = [
{ type: 'video', label: 'Відеоурок', icon: '🎬' },
{ type: 'text', label: 'Текстовий урок', icon: '📄' },
{ type: 'quiz', label: 'Тест', icon: '📝' },
{ type: 'assignment', label: 'Завдання', icon: '📋' },
{ type: 'live', label: 'Живий урок', icon: '📡' },
];
return (
<div className="max-w-4xl mx-auto p-6">
<DndContext collisionDetection={closestCenter} onDragEnd={handleDragEnd}>
<SortableContext items={sections.map(s => s.id)} strategy={verticalListSortingStrategy}>
{sections.map(section => (
<SortableSection key={section.id} section={section} />
))}
</SortableContext>
</DndContext>
<button onClick={addSection} className="mt-4 w-full border-2 border-dashed border-gray-300 py-4">
+ Додати розділ
</button>
</div>
);
}
Строки виконання
Базовий конструктор курсів (розділи + уроки + drag-drop) — 1–2 тижні. З попереднім переглядом, умовними розблокуваннями та публікацією — 3–4 тижні.







