diff --git a/12-20 b/12-20 new file mode 100644 index 0000000..e69de29 diff --git a/public/app.js b/public/app.js index 2b9dd9b..1bfdc47 100644 --- a/public/app.js +++ b/public/app.js @@ -76,11 +76,15 @@ function getFilterParams() { const student = document.getElementById('gfStudent').value.trim(); const subject = document.getElementById('gfSubject').value.trim(); const teacher = document.getElementById('gfTeacher').value.trim(); + const classVal = document.getElementById('gfClass').value.trim(); + const parallel = document.getElementById('gfParallel').value.trim(); const from = document.getElementById('gfDateFrom').value; const to = document.getElementById('gfDateTo').value; if (student) params.student = student; if (subject) params.subject = subject; if (teacher) params.teacher = teacher; + if (classVal) params.class = classVal; + if (parallel) params.parallel = parallel; if (from) params.date_from = from.slice(5); if (to) params.date_to = to.slice(5); return params; @@ -111,6 +115,7 @@ async function loadAll() { renderClassesTable(stats); renderDailyTable(daily); loadPivot(); + loadControl(); } async function loadFilters() { @@ -126,6 +131,7 @@ async function loadFilters() { fill('dlStudents', data.students); fill('dlSubjects', data.subjects); fill('dlTeachers', data.teachers); + fill('dlClasses', data.classes); } catch (e) { console.error(e); } @@ -305,6 +311,8 @@ document.getElementById('gfReset').addEventListener('click', () => { document.getElementById('gfStudent').value = ''; document.getElementById('gfSubject').value = ''; document.getElementById('gfTeacher').value = ''; + document.getElementById('gfClass').value = ''; + document.getElementById('gfParallel').value = ''; document.getElementById('gfDateFrom').value = ''; document.getElementById('gfDateTo').value = ''; loadAll(); @@ -491,5 +499,166 @@ window.toggleGroup = function(groupId) { } }; +let controlMode = 'homework'; + +document.querySelectorAll('.ctrl-tab').forEach(btn => { + btn.addEventListener('click', () => { + document.querySelectorAll('.ctrl-tab').forEach(b => b.classList.remove('active')); + btn.classList.add('active'); + controlMode = btn.dataset.ctrl; + document.getElementById('ctrlFiltersHW').style.display = controlMode === 'homework' ? '' : 'none'; + document.getElementById('ctrlFiltersGrades').style.display = controlMode === 'grades' ? '' : 'none'; + document.getElementById('ctrlFiltersDvoiki').style.display = controlMode === 'dvoiki' ? '' : 'none'; + document.getElementById('ctrlTableWrap').style.display = controlMode === 'dvoiki' ? 'none' : ''; + document.getElementById('dvoikiCards').style.display = controlMode === 'dvoiki' ? '' : 'none'; + loadControl(); + }); +}); + +document.getElementById('ctrlNoTopic').addEventListener('change', loadControl); +document.getElementById('ctrlNoHomework').addEventListener('change', loadControl); +document.getElementById('ctrlMinGrades').addEventListener('change', loadControl); + +async function loadControl() { + const params = getFilterParams(); + + if (controlMode === 'dvoiki') { + const qs = buildQuery(params); + try { + const res = await fetch('/api/dvoiki?' + qs); + const data = await res.json(); + if (data.error) return; + + const allRows = data.rows || []; + const openCount = allRows.filter(r => !r.resolved).length; + const closedCount = allRows.filter(r => r.resolved).length; + + renderDvoikiPills(allRows.length, openCount, closedCount); + + let rows = allRows; + if (window._dvoikiFilter === 'open') rows = rows.filter(r => !r.resolved); + else if (window._dvoikiFilter === 'closed') rows = rows.filter(r => r.resolved); + + renderDvoikiCards(rows); + } catch (e) { + console.error(e); + } + return; + } + + if (controlMode === 'homework') { + if (document.getElementById('ctrlNoTopic').checked) params.no_topic = 'true'; + if (document.getElementById('ctrlNoHomework').checked) params.no_homework = 'true'; + } else { + params.low_grades = 'true'; + params.min_grades = document.getElementById('ctrlMinGrades').value || '3'; + } + + const qs = buildQuery(params); + + try { + const res = await fetch('/api/lessons?' + qs); + const data = await res.json(); + if (data.error) return; + + const thead = document.querySelector('#controlTable thead'); + thead.innerHTML = 'ДатаПредметУчительТемаДЗВсегоЧисл.Симв.Оценки'; + + const tbody = document.querySelector('#controlTable tbody'); + if (!data.rows || !data.rows.length) { + tbody.innerHTML = 'Нет данных'; + return; + } + + const minNumeric = data.min_grades || 3; + + tbody.innerHTML = data.rows.map(r => { + let topicCls = ''; + let hwCls = ''; + let rowCls = ''; + if (controlMode === 'homework') { + if (!r.topic) topicCls = 'missing'; + if (!r.homework) hwCls = 'missing'; + } else { + if (r.grades_numeric < minNumeric) rowCls = 'red-row'; + } + return ` + ${r.lesson_date} + ${r.subject} + ${r.teacher} + ${r.topic || '—'} + ${r.homework || '—'} + ${r.grades_total} + ${r.grades_numeric} + ${r.grades_symbol} + ${r.grades_list || ''} + `; + }).join(''); + } catch (e) { + console.error(e); + } +} + +let _dvoikiFilter = 'all'; + +function renderDvoikiPills(total, open, closed) { + window._dvoikiFilter = window._dvoikiFilter || 'all'; + const pills = [ + { key: 'all', label: 'Все', count: total, active: window._dvoikiFilter === 'all' }, + { key: 'open', label: '⚠️ Незакрыто', count: open, active: window._dvoikiFilter === 'open' }, + { key: 'closed', label: '✅ Закрыто', count: closed, active: window._dvoikiFilter === 'closed' } + ]; + document.getElementById('dvoikiPills').innerHTML = pills.map(p => + `` + ).join(''); + + document.querySelectorAll('.pill').forEach(btn => { + btn.addEventListener('click', () => { + window._dvoikiFilter = btn.dataset.filter; + loadControl(); + }); + }); +} + +function renderDvoikiCards(rows) { + const container = document.getElementById('dvoikiCards'); + + if (!rows.length) { + container.innerHTML = '
Нет цепочек
'; + return; + } + + const gradeClass = (g) => { + if (g === '2') return 's-2'; + if (g === '3') return 's-3'; + if (g === '4' || g === '5') return 's-45'; + if (g === '—') return 's-empty'; + return 's-sym'; + }; + + container.innerHTML = rows.map(r => { + const cardCls = r.resolved ? 'd-card resolved' : 'd-card unresolved'; + const statusIcon = r.resolved ? '✅' : '⚠️'; + const statusText = r.resolved ? `Закрыт (${r.reason})` : `Не закрыт — ${r.reason}`; + + const stepsHtml = r.chain.map(c => + `
+
${c.date}
+
${c.grade}
+
` + ).join(''); + + return `
+
+ ${statusIcon} ${statusText} + ${r.chain.length} шаг${r.chain.length > 1 ? (r.chain.length < 5 ? 'а' : 'ов') : ''} +
+
${r.student}
+
${r.subject} · ${r.teacher}
+
${stepsHtml}
+
`; + }).join(''); +} + loadFilters(); loadAll(); diff --git a/public/index.html b/public/index.html index c7364b5..772c75e 100644 --- a/public/index.html +++ b/public/index.html @@ -30,6 +30,8 @@ + + @@ -46,6 +48,7 @@ +
@@ -109,6 +112,32 @@
+ +
+

Контроль

+
+ + + +
+
+ + +
+ + +
+ + + +
ДатаПредметУчительТемаДЗВсегоЧисл.Симв.Оценки
+
+ +