feat: textbooks — модуль учебников + чтение как ДЗ (3 фазы)
Фаза 1 — структура и каталог:
- frontend/textbooks/chemistry_9.html (Шиманович, 60 §) + physics_9.html (Исаченкова, 38 §)
- frontend/textbooks.html — каталог в стиле LearnSpace (карточки с обложками)
- Маршруты: /textbooks (каталог), /textbook/<slug> (полноэкранный учебник)
- Сайдбар: пункт «Учебники» (book-open-text)
- Feature flag feature_textbooks_enabled, hideDisabledFeatures map
Фаза 2 — прогресс в localStorage + UI чтения:
- frontend/js/textbook-tracker.js — инжектится в каждый учебник:
- «← Учебники» overlay-кнопка (top-left, semi-transparent)
- «Прочитано» чекбокс рядом с каждым §-заголовком
- Зелёный dot на pill уже прочитанных параграфов
- Авто-открытие последнего параграфа при возврате
- Каталог показывает прогресс-бар «X из Y прочитано» + кнопку «Продолжить»
Фаза 3 — серверный прогресс + назначение чтения как ДЗ:
- Таблица textbooks (slug, subject, grade, title, author, color, ...)
- Таблица textbook_progress (user_id, textbook_id, JSON read[], last_para)
- Колонки assignments.textbook_id + textbook_paragraphs
- API: GET /api/textbooks (с прогрессом), GET /:slug, POST /:slug/progress,
GET /:slug/class-progress (учитель)
- tracker.js синхронизирует прогресс через POST /progress (если залогинен)
- На каталоге у учителей кнопка «Назначить чтение» — модалка с выбором
классов + параграфы («1-5» или «1,3,5») + deadline
- bulkCreateAssignment расширен: принимает textbook_slug, резолвит в id
Миграция 004 идемпотентная; сиды двух учебников включены.
This commit is contained in:
@@ -595,7 +595,8 @@ function deleteTemplate(req, res) {
|
||||
|
||||
/* ── POST /api/assignments/bulk ── assign to multiple classes at once ───── */
|
||||
function bulkCreateAssignment(req, res) {
|
||||
const { class_ids, title, mode = 'exam', count = 25, topic_id, deadline, test_id, file_id, is_homework = 0 } = req.body;
|
||||
const { class_ids, title, mode = 'exam', count = 25, topic_id, deadline, test_id, file_id,
|
||||
is_homework = 0, textbook_slug, textbook_paragraphs } = req.body;
|
||||
let { subject_slug } = req.body;
|
||||
|
||||
if (!Array.isArray(class_ids) || !class_ids.length)
|
||||
@@ -608,6 +609,16 @@ function bulkCreateAssignment(req, res) {
|
||||
if (!t) return res.status(400).json({ error: 'Test not found' });
|
||||
subject_slug = t.subject_slug;
|
||||
}
|
||||
|
||||
// Textbook: resolve slug → id, derive subject from textbook
|
||||
let textbook_id = null;
|
||||
if (textbook_slug) {
|
||||
const tb = db.prepare('SELECT id, subject FROM textbooks WHERE slug=? AND is_active=1').get(textbook_slug);
|
||||
if (!tb) return res.status(400).json({ error: 'Учебник не найден' });
|
||||
textbook_id = tb.id;
|
||||
if (!subject_slug) subject_slug = tb.subject;
|
||||
}
|
||||
|
||||
if (!subject_slug && !is_homework) return res.status(400).json({ error: 'subject_slug required' });
|
||||
if (!subject_slug) subject_slug = 'other';
|
||||
|
||||
@@ -619,9 +630,9 @@ function bulkCreateAssignment(req, res) {
|
||||
if (req.user.role !== 'admin' && cls.teacher_id !== req.user.id) continue;
|
||||
|
||||
const r = db.prepare(`
|
||||
INSERT INTO assignments (class_id, title, subject_slug, mode, count, topic_id, deadline, created_by, test_id, file_id, is_homework)
|
||||
VALUES (?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?)
|
||||
`).run(cls.id, stripTags(title.trim()), subject_slug, mode, Number(count), topic_id || null, deadline || null, req.user.id, test_id || null, file_id || null, is_homework ? 1 : 0);
|
||||
INSERT INTO assignments (class_id, title, subject_slug, mode, count, topic_id, deadline, created_by, test_id, file_id, is_homework, textbook_id, textbook_paragraphs)
|
||||
VALUES (?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?)
|
||||
`).run(cls.id, stripTags(title.trim()), subject_slug, mode, Number(count), topic_id || null, deadline || null, req.user.id, test_id || null, file_id || null, is_homework ? 1 : 0, textbook_id, textbook_paragraphs || null);
|
||||
ids.push(r.lastInsertRowid);
|
||||
|
||||
const members = db.prepare('SELECT user_id FROM class_members WHERE class_id = ?').all(cls.id);
|
||||
|
||||
Reference in New Issue
Block a user