feat(lessons): тема занятия в отчёте о занятии
В модалку #lessonModal добавлено текстовое поле «Тема занятия» (#lessonTopic) между датой/временем и текстом отчёта. Поле необязательное, лимит 300 символов. Протянуто по всему срезу: - колонка lesson_reports.topic в db/init.sql, db/migration.sql и ensureLessonReportsTable() — миграция идемпотентная, ALTER IF NOT EXISTS - POST /api/lesson-reports принимает topic, PUT обновляет; в GET-списке колонка добавлена в явный SELECT (там не SELECT *, без правки не пришла бы) - тема в normalizeRestoreData, чтобы бэкап/restore её не теряли - тема уходит в ИИ-контекст воркера строкой «Тема занятия: ...» - вывод в списке отчётов (lessons.js + .lesson-topic в admin.css) В PUT пустая строка очищает тему, а отсутствие поля в теле запроса её не трогает: CASE WHEN $6::boolean THEN $3::text ELSE topic END, а не COALESCE — при COALESCE пустая тема затиралась бы старым значением и поле нельзя было бы очистить. Версии не трогаем: lesson_report_versions хранит только text. Тесты: +5 проверок (создание, список, редактирование, очистка, >300 символов), api.smoketest.js — 76 PASS / 0 FAIL.
This commit is contained in:
+11
-1
@@ -534,6 +534,11 @@ function ensureLessonModal() {
|
||||
<input type="time" id="lessonTime" class="settings-input">
|
||||
</div>
|
||||
</div>
|
||||
<div class="settings-field">
|
||||
<label>Тема занятия</label>
|
||||
<input type="text" id="lessonTopic" class="settings-input" maxlength="300"
|
||||
placeholder="Например: циклы for и while, задачи на списки">
|
||||
</div>
|
||||
<div class="settings-field">
|
||||
<label>Что прошли на занятии <span style="color:#ef4444">*</span></label>
|
||||
<textarea id="lessonText" class="settings-input" rows="6" maxlength="5000" required
|
||||
@@ -591,6 +596,7 @@ async function submitLessonModal(e) {
|
||||
const dateEl = modal.querySelector('#lessonDate');
|
||||
const timeEl = modal.querySelector('#lessonTime');
|
||||
const textEl = modal.querySelector('#lessonText');
|
||||
const topicEl = modal.querySelector('#lessonTopic');
|
||||
const submitBtn = modal.querySelector('#lessonSubmit');
|
||||
const aiCheckEl = modal.querySelector('#lessonAiCheck');
|
||||
const groupId = groupSel.value;
|
||||
@@ -606,7 +612,7 @@ async function submitLessonModal(e) {
|
||||
const res = await fetch(`${API}/api/lesson-reports${id ? '/' + id : ''}`, {
|
||||
method: id ? 'PUT' : 'POST',
|
||||
headers: hdrJson(),
|
||||
body: JSON.stringify({ group_id: Number(groupId), lesson_date: date, lesson_time: timeEl.value || null, text, ai_check: aiCheck }),
|
||||
body: JSON.stringify({ group_id: Number(groupId), lesson_date: date, lesson_time: timeEl.value || null, topic: topicEl.value.trim(), text, ai_check: aiCheck }),
|
||||
});
|
||||
const data = await res.json().catch(() => ({}));
|
||||
if (!res.ok) {
|
||||
@@ -675,6 +681,9 @@ function fillLessonModalFromReport(modal, r) {
|
||||
textEl.value = r.text || '';
|
||||
textEl.dispatchEvent(new Event('input'));
|
||||
}
|
||||
if (!modal.querySelector('#lessonTopic').value) {
|
||||
modal.querySelector('#lessonTopic').value = r.topic || '';
|
||||
}
|
||||
const aiCheck = modal.querySelector('#lessonAiCheck');
|
||||
if (aiCheck) aiCheck.checked = false;
|
||||
updateLessonAiRow(modal, r);
|
||||
@@ -713,6 +722,7 @@ function resetLessonModalFields(modal) {
|
||||
showLessonDuplicate(modal, null);
|
||||
modal.querySelector('#lessonDate').value = lessonTodayIso();
|
||||
modal.querySelector('#lessonTime').value = lessonNowTime();
|
||||
modal.querySelector('#lessonTopic').value = '';
|
||||
const textEl = modal.querySelector('#lessonText');
|
||||
textEl.value = '';
|
||||
textEl.dispatchEvent(new Event('input'));
|
||||
|
||||
Reference in New Issue
Block a user