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:
Maxim Dolgolyov
2026-05-16 14:05:19 +03:00
parent 31a51956b6
commit e8018d85c1
10 changed files with 23974 additions and 4 deletions
@@ -0,0 +1,42 @@
-- Feature flag for textbooks module
INSERT OR IGNORE INTO app_settings (key, value) VALUES ('feature_textbooks_enabled', '1');
-- Catalog of textbooks (admin-editable; html_path is relative to /frontend/textbooks/)
CREATE TABLE textbooks (
id INTEGER PRIMARY KEY AUTOINCREMENT,
slug TEXT UNIQUE NOT NULL,
subject TEXT NOT NULL, -- 'chemistry', 'physics', 'math', 'biology', ...
grade INTEGER NOT NULL, -- 9, 10, 11, ...
title TEXT NOT NULL,
author TEXT NOT NULL DEFAULT '',
description TEXT NOT NULL DEFAULT '',
html_path TEXT NOT NULL, -- relative filename: 'chemistry_9.html'
para_count INTEGER NOT NULL DEFAULT 0, -- total paragraphs (for progress %)
color TEXT NOT NULL DEFAULT 'violet',-- visual theme: 'amber','blue','violet','green',...
sort_order INTEGER NOT NULL DEFAULT 0,
is_active INTEGER NOT NULL DEFAULT 1,
created_at TEXT NOT NULL DEFAULT (datetime('now'))
);
-- Per-user reading progress (one row per (user, textbook))
CREATE TABLE textbook_progress (
user_id INTEGER NOT NULL REFERENCES users(id) ON DELETE CASCADE,
textbook_id INTEGER NOT NULL REFERENCES textbooks(id) ON DELETE CASCADE,
paragraphs_read TEXT NOT NULL DEFAULT '[]', -- JSON array of para keys like ["p1","p3","p7"]
last_para TEXT, -- last opened paragraph key (e.g. 'p15')
last_at TEXT NOT NULL DEFAULT (datetime('now')),
PRIMARY KEY (user_id, textbook_id)
);
-- Assignment extension: link to textbook + paragraph range string ("1-5" or "1,3,5")
ALTER TABLE assignments ADD COLUMN textbook_id INTEGER REFERENCES textbooks(id) ON DELETE SET NULL;
ALTER TABLE assignments ADD COLUMN textbook_paragraphs TEXT;
-- Seed: chemistry 9 + physics 9 (the two files we just copied)
INSERT INTO textbooks (slug, subject, grade, title, author, description, html_path, para_count, color, sort_order) VALUES
('chemistry-9', 'chemistry', 9, 'Химия — 9 класс', 'Шиманович Е. Я.',
'Полный курс химии за 9 класс. §1–60: строение атома, химическая связь, классы соединений, ОВР, металлы и их соединения, электролиз.',
'chemistry_9.html', 60, 'amber', 1),
('physics-9', 'physics', 9, 'Физика — 9 класс', 'Исаченкова Л. А.',
'Полный курс физики за 9 класс: §1–38. Механика, кинематика, динамика, статика, законы сохранения, импульс, работа и энергия.',
'physics_9.html', 38, 'blue', 2);