From 82d323547fc1c6943eb5147fef39fd4042d4fd41 Mon Sep 17 00:00:00 2001 From: Maxim Dolgolyov Date: Fri, 19 Jun 2026 16:06:41 +0300 Subject: [PATCH] =?UTF-8?q?feat(prep):=20=D1=82=D1=83=D0=BC=D0=B1=D0=BB?= =?UTF-8?q?=D0=B5=D1=80=20=C2=AB=D0=B3=D0=BE=D1=82=D0=BE=D0=B2=D0=B8=D1=82?= =?UTF-8?q?=D1=81=D1=8F=20=D0=BA=20=D0=A6=D0=A2=C2=BB=20=D0=BD=D0=B0=20?= =?UTF-8?q?=D1=81=D1=82=D1=80=D0=B0=D0=BD=D0=B8=D1=86=D0=B5=20=D0=BF=D0=B5?= =?UTF-8?q?=D1=80=D1=81=D0=BE=D0=BD=D0=B0=D0=BB=D1=8C=D0=BD=D1=8B=D1=85=20?= =?UTF-8?q?=D1=83=D1=87=D0=B5=D0=BD=D0=B8=D0=BA=D0=BE=D0=B2?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit my-students.html: колонка «ЦТ» с тумблером у каждого ученика (teacher_students вне классов). Бэкенд уже поддерживал (canManageStudent включает teacher_students); статус грузится per-student через LS.prepStudentTracks, переключение — prepSetStudent/prepUnsetStudent. togglePrep на window (скрипт-модуль). Иконки — SVG. Co-Authored-By: Claude Opus 4.8 (1M context) --- frontend/my-students.html | 47 +++++++++++++++++++++++++++++++++++++-- 1 file changed, 45 insertions(+), 2 deletions(-) diff --git a/frontend/my-students.html b/frontend/my-students.html index 70eb6dd..0af8cb7 100644 --- a/frontend/my-students.html +++ b/frontend/my-students.html @@ -66,11 +66,17 @@ overflow:hidden; } .ms-row { - display:grid; grid-template-columns: 36px 1.5fr 2fr 1fr auto auto; + display:grid; grid-template-columns: 36px 1.5fr 2fr 1fr auto auto auto; gap:14px; padding:14px 20px; align-items:center; border-bottom:1px solid var(--border); transition:background .12s; } + .prep-toggle { display:inline-flex; align-items:center; gap:5px; padding:4px 9px; border-radius:999px; + border:1px solid var(--border); background:var(--surface); cursor:pointer; font-size:.74rem; + font-weight:600; color:var(--text-3); transition:all .15s; white-space:nowrap; } + .prep-toggle svg { width:13px; height:13px; } + .prep-toggle:hover { border-color:var(--violet); color:var(--violet); } + .prep-toggle.on { background:rgba(155,93,229,.12); border-color:var(--violet); color:var(--violet); } .ms-row:last-child { border-bottom:none; } .ms-row:hover { background:rgba(155,93,229,.04); } .ms-row.head { @@ -110,7 +116,7 @@ @media (max-width: 700px) { .ms-row { grid-template-columns: 36px 1fr auto; gap:10px; } - .ms-row > :nth-child(3), .ms-row > :nth-child(4) { display:none; } + .ms-row > :nth-child(3), .ms-row > :nth-child(4), .ms-row > :nth-child(5), .ms-row > :nth-child(6) { display:none; } .ms-row.head { display:none; } } @@ -193,11 +199,46 @@ let students = []; + /* Флаг «готовится к ЦТ» (трек ct-math открывает карточки + курс + пробники). */ + const PREP_TRACK = 'ct-math'; + let _prep = {}; // { studentId: true|false } + + function prepCellHtml(id) { + const on = _prep[id] === true; + return ``; + } + + async function loadPrepAll() { + if (!students.length) return; + const results = await Promise.all(students.map(s => + LS.prepStudentTracks(s.id) + .then(r => ({ id: s.id, on: (r.tracks || []).includes(PREP_TRACK) })) + .catch(() => ({ id: s.id, on: false })))); + _prep = {}; + results.forEach(r => { _prep[r.id] = r.on; }); + render(); + } + + window.togglePrep = async function (id) { + const on = _prep[id] === true; + try { + if (on) await LS.prepUnsetStudent(id, PREP_TRACK); + else await LS.prepSetStudent(id, PREP_TRACK); + _prep[id] = !on; + render(); + if (LS.toast) LS.toast(on ? 'Снято' : 'Готовится к ЦТ', 'success'); + } catch (e) { if (LS.toast) LS.toast(e.message || 'Ошибка', 'error'); else alert(e.message || 'Ошибка'); } + }; + async function loadStudents() { try { const r = await LS.api('/api/teacher-students'); students = r.students || []; render(); + loadPrepAll(); } catch (e) { document.getElementById('ms-list-container').innerHTML = `
Ошибка: ${esc(e.message)}
`; } @@ -222,6 +263,7 @@
Email
Заданий
Добавлен
+
ЦТ
${students.map(s => ` @@ -234,6 +276,7 @@
${esc(s.email)}
${s.assignment_count || 0}
${fmtDate(s.added_at)}
+
${prepCellHtml(s.id)}