'use strict'; /* admin → assignments section (классные/индивидуальные задания) */ (function () { 'use strict'; let inited = false; let allAssignments = []; let editingAId = null; const SUBJ_NAMES = { bio:'Биология', chem:'Химия', math:'Математика', phys:'Физика' }; const SUBJ_COLORS_A = { bio:'#9B5DE5', chem:'#06D6A0', math:'#06B6D4', phys:'#F59E0B' }; const SUBJ_ICONS_A = { bio:'dna', chem:'flask-conical', math:'calculator', phys:'zap' }; let _aFilter = 'all'; // create-modal state let _afSrc = 'random'; let _afLoadedTests = []; let _acSrc = 'random'; let _acTarget = 'class'; let _acFileId = null, _acAllFiles = null; let _acStudentId = null, _acAllStudents = null; async function load() { document.getElementById('a-body').innerHTML = '
'; try { allAssignments = await LS.teacherAssignments(); renderAssignments(); } catch (e) { document.getElementById('a-body').innerHTML = `
Ошибка: ${esc(e.message)}
`; } } function setAFilter(f) { _aFilter = f; document.querySelectorAll('.a-f-chip').forEach(c => c.classList.toggle('active', c.textContent.trim() === {all:'Все',active:'Активные',overdue:'Просрочены',done:'Завершены'}[f]) ); renderAssignments(); } function aClassify(a) { const pct = a.total_members ? Math.round(a.completed_count / a.total_members * 100) : null; if (pct === 100) return 'done'; if (a.deadline && new Date(a.deadline) < new Date()) return 'overdue'; return 'active'; } function renderAssignments() { const { MODES, pctClass } = AdminCtx; const subjF = document.getElementById('a-subject').value; const searchF = document.getElementById('a-search').value.toLowerCase(); const sortF = document.getElementById('a-sort')?.value || 'date'; let list = allAssignments.filter(a => { if (subjF && a.subject_slug !== subjF) return false; if (searchF && !a.title.toLowerCase().includes(searchF)) return false; if (_aFilter === 'active' && aClassify(a) !== 'active') return false; if (_aFilter === 'overdue' && aClassify(a) !== 'overdue') return false; if (_aFilter === 'done' && aClassify(a) !== 'done') return false; return true; }); list = [...list].sort((a, b) => { if (sortF === 'deadline') { const da = a.deadline ? new Date(a.deadline) : new Date(9e15); const db = b.deadline ? new Date(b.deadline) : new Date(9e15); return da - db; } if (sortF === 'progress_asc') { const pa = a.total_members ? a.completed_count / a.total_members : 0; const pb = b.total_members ? b.completed_count / b.total_members : 0; return pa - pb; } if (sortF === 'progress_desc') { const pa = a.total_members ? a.completed_count / a.total_members : 0; const pb = b.total_members ? b.completed_count / b.total_members : 0; return pb - pa; } return 0; }); const all = allAssignments; const nActive = all.filter(a => aClassify(a) === 'active').length; const nOverdue = all.filter(a => aClassify(a) === 'overdue').length; const nDone = all.filter(a => aClassify(a) === 'done').length; document.getElementById('a-summary').innerHTML = [ `Всего: ${all.length}`, nActive ? `Активных: ${nActive}` : '', nOverdue ? `Просрочено: ${nOverdue}` : '', nDone ? `Завершено: ${nDone}` : '', ].join(''); document.getElementById('a-count').textContent = `${list.length} заданий`; const container = document.getElementById('a-body'); if (!list.length) { container.innerHTML = '
Заданий нет
'; return; } const now = new Date(); container.innerHTML = list.map(a => { const pct = a.total_members ? Math.round(a.completed_count / a.total_members * 100) : null; const cls = aClassify(a); const rowCls = cls === 'overdue' ? 'a-overdue' : cls === 'done' ? 'a-done' : ''; const sColor = SUBJ_COLORS_A[a.subject_slug] || '#9B5DE5'; const dlMs = a.deadline ? new Date(a.deadline) - now : Infinity; const isUrgent = cls === 'active' && dlMs > 0 && dlMs < 24 * 3600 * 1000; const dl = a.deadline ? new Date(a.deadline).toLocaleDateString('ru', {day:'numeric', month:'short'}) : null; const targetStr = a.target_user_id ? esc(a.target_user_name || 'Ученик') : esc(a.class_name || '—'); const metaParts = [ targetStr, SUBJ_NAMES[a.subject_slug] || a.subject_slug, `${MODES[a.mode]||a.mode}`, a.count + ' вопр.', dl ? `до ${dl}` : null, isUrgent ? ` срочно` : null, cls === 'overdue' ? `просрочено` : null, ].filter(Boolean); const barColor = pct >= 75 ? '#06D6A0' : pct >= 40 ? '#F59E0B' : '#F15BB5'; const pctLabel = pct !== null ? `${pct}%` : '—'; return `
${esc(a.title)}
${metaParts.join(' · ')}
${a.completed_count} / ${a.total_members} сдали ${pctLabel}
`; }).join(''); if (window.lucide) lucide.createIcons(); } function setAfSrc(src) { _afSrc = src; document.querySelectorAll('[data-afsrc]').forEach(b => b.classList.toggle('active', b.dataset.afsrc === src)); document.getElementById('af-random-fields').style.display = src === 'random' ? '' : 'none'; document.getElementById('af-test-fields').style.display = src === 'test' ? '' : 'none'; } async function openAModal(id) { const a = allAssignments.find(x => x.id === id); if (!a) return; editingAId = id; document.getElementById('a-modal-title').textContent = `Редактировать: ${a.title}`; document.getElementById('af-title').value = a.title; document.getElementById('af-deadline').value = a.deadline ? a.deadline.split('T')[0] : ''; document.getElementById('af-error').textContent = ''; const testSel = document.getElementById('af-test'); testSel.innerHTML = ''; try { _afLoadedTests = await LS.getTests(); testSel.innerHTML = _afLoadedTests.length ? '' + _afLoadedTests.map(t => ``).join('') : ''; } catch { testSel.innerHTML = ''; _afLoadedTests = []; } if (a.test_id) { setAfSrc('test'); testSel.value = a.test_id; document.getElementById('af-mode-test').value = a.mode; } else { setAfSrc('random'); document.getElementById('af-subject').value = a.subject_slug; document.getElementById('af-mode').value = a.mode; document.getElementById('af-count').value = a.count; } document.getElementById('a-modal').classList.add('open'); setTimeout(() => document.getElementById('af-title').focus(), 80); } function closeAModal() { document.getElementById('a-modal').classList.remove('open'); editingAId = null; } async function saveAssignment() { const title = document.getElementById('af-title').value.trim(); const deadline = document.getElementById('af-deadline').value || null; const errEl = document.getElementById('af-error'); errEl.textContent = ''; if (!title) { errEl.textContent = 'Введите название'; return; } let payload = { title, deadline }; if (_afSrc === 'test') { const test_id = document.getElementById('af-test').value; const mode = document.getElementById('af-mode-test').value; if (!test_id) { errEl.textContent = 'Выберите тест'; return; } const testObj = _afLoadedTests.find(t => t.id === Number(test_id)); if (testObj && testObj.question_count === 0) { errEl.textContent = 'В выбранном тесте нет вопросов'; return; } payload = { ...payload, test_id: Number(test_id), mode }; } else { const subject_slug = document.getElementById('af-subject').value; const mode = document.getElementById('af-mode').value; const count = Number(document.getElementById('af-count').value); if (!subject_slug) { errEl.textContent = 'Выберите предмет'; return; } if (!count || count < 1) { errEl.textContent = 'Введите количество вопросов'; return; } payload = { ...payload, subject_slug, mode, count, test_id: null }; } const btn = document.getElementById('af-save'); btn.disabled = true; btn.textContent = 'Сохранение…'; try { await LS.updateAssignment(editingAId, payload); const idx = allAssignments.findIndex(x => x.id === editingAId); if (idx !== -1) Object.assign(allAssignments[idx], payload); closeAModal(); renderAssignments(); } catch (e) { errEl.textContent = 'Ошибка: ' + e.message; } finally { btn.disabled = false; btn.textContent = 'Сохранить'; } } /* ─── Create assignment modal ─── */ function setAcTarget(t) { _acTarget = t; document.querySelectorAll('[data-actgt]').forEach(b => b.classList.toggle('active', b.dataset.actgt === t)); document.getElementById('acf-class-field').style.display = t === 'class' ? '' : 'none'; document.getElementById('acf-user-field').style.display = t === 'user' ? '' : 'none'; if (t === 'user' && !_acAllStudents) loadAcStudents(); } async function loadAcStudents() { const drop = document.getElementById('acf-student-drop'); drop.innerHTML = '
Загрузка…
'; drop.style.display = ''; try { _acAllStudents = await LS.getStudentsList(); openAcStudentDrop(); } catch(e) { _acAllStudents = []; drop.innerHTML = `
Ошибка загрузки: ${e.message}
`; } } function filterAcStudents(q) { openAcStudentDrop(q); } function openAcStudentDrop(q) { const drop = document.getElementById('acf-student-drop'); if (_acAllStudents === null) { loadAcStudents(); return; } const list = _acAllStudents; const term = (q !== undefined ? q : document.getElementById('acf-student-search').value).toLowerCase().trim(); const filtered = term ? list.filter(s => s.name.toLowerCase().includes(term) || s.email.toLowerCase().includes(term)) : list; if (!filtered.length) { drop.innerHTML = '
Нет учеников
'; drop.style.display = ''; return; } drop.innerHTML = filtered.slice(0, 50).map(s => `
${esc(s.name)} ${esc(s.email)}
` ).join(''); drop.style.display = ''; } function closeAcStudentDrop() { document.getElementById('acf-student-drop').style.display = 'none'; } function selectAcStudent(id, name, email) { _acStudentId = id; document.getElementById('acf-student-search').value = name; document.getElementById('acf-student-selected').textContent = `${name} (${email})`; document.getElementById('acf-student-selected').style.display = ''; closeAcStudentDrop(); } function setAcSrc(src) { _acSrc = src; document.querySelectorAll('[data-src]').forEach(b => b.classList.toggle('active', b.dataset.src === src)); document.getElementById('acf-random-fields').style.display = src === 'random' ? '' : 'none'; document.getElementById('acf-test-fields').style.display = src === 'test' ? '' : 'none'; document.getElementById('acf-file-fields').style.display = src === 'file' ? '' : 'none'; if (src === 'file' && !_acAllFiles) loadAcFiles(); } async function loadAcFiles() { try { _acAllFiles = await LS.getFiles(); renderAcFiles(''); } catch { _acAllFiles = []; } } function renderAcFiles(q) { const el = document.getElementById('acf-file-list'); if (!_acAllFiles) { el.innerHTML = '
Загрузка…
'; return; } const lq = q.toLowerCase(); const items = q ? _acAllFiles.filter(f => (f.title||'').toLowerCase().includes(lq)) : _acAllFiles; const SUBJ = { bio:'Биология', chem:'Химия', math:'Математика', phys:'Физика' }; if (!items.length) { el.innerHTML = '
Нет файлов
'; return; } el.innerHTML = items.map(f => `
${esc(f.title||'Файл')}
${SUBJ[f.subject_slug]||f.subject_slug||''}
${_acFileId===f.id ? '' : ''}
`).join(''); if (window.lucide) lucide.createIcons(); } function filterAcFiles(q) { renderAcFiles(q); } function selectAcFile(id, title, subject_slug) { _acFileId = id; renderAcFiles(document.getElementById('acf-file-search').value); const sel = document.getElementById('acf-file-selected'); sel.textContent = 'Выбран: ' + title; sel.style.display = ''; } async function openCreateAModal() { _acSrc = 'random'; _acTarget = 'class'; _acFileId = null; _acStudentId = null; _acAllStudents = null; setAcSrc('random'); setAcTarget('class'); loadAcStudents(); document.getElementById('acf-title').value = ''; document.getElementById('acf-subject').value = ''; document.getElementById('acf-mode').value = 'exam'; document.getElementById('acf-mode-test').value = 'exam'; document.getElementById('acf-count').value = '25'; document.getElementById('acf-deadline').value = ''; document.getElementById('acf-student-search').value = ''; document.getElementById('acf-student-selected').style.display = 'none'; _acStudentId = null; document.getElementById('acf-error').textContent = ''; document.getElementById('acf-file-search').value = ''; document.getElementById('acf-file-selected').style.display = 'none'; const [clsSel, testSel] = [document.getElementById('acf-class'), document.getElementById('acf-test')]; clsSel.innerHTML = ''; testSel.innerHTML = ''; const [classesP, testsP] = await Promise.allSettled([LS.getClasses(), LS.getTests()]); if (classesP.status === 'fulfilled') { const classes = classesP.value; clsSel.innerHTML = classes.length ? '' + classes.map(c => ``).join('') : ''; } else { clsSel.innerHTML = ``; } if (testsP.status === 'fulfilled') { const tests = testsP.value; testSel.innerHTML = tests.length ? '' + tests.map(t => ``).join('') : ''; } else { testSel.innerHTML = ``; } document.getElementById('ac-modal').classList.add('open'); setTimeout(() => document.getElementById('acf-title').focus(), 80); } function closeCreateAModal() { document.getElementById('ac-modal').classList.remove('open'); } async function saveNewAssignment() { const title = document.getElementById('acf-title').value.trim(); const deadline = document.getElementById('acf-deadline').value || null; const errEl = document.getElementById('acf-error'); errEl.textContent = ''; if (!title) { errEl.textContent = 'Введите название'; return; } let payload = { title, deadline }; if (_acSrc === 'file') { if (!_acFileId) { errEl.textContent = 'Выберите файл из библиотеки'; return; } const f = _acAllFiles.find(x => x.id === _acFileId); payload = { ...payload, file_id: _acFileId, subject_slug: f?.subject_slug || 'bio', mode: 'exam', count: 1 }; } else if (_acSrc === 'test') { const test_id = document.getElementById('acf-test').value; const mode = document.getElementById('acf-mode-test').value; if (!test_id) { errEl.textContent = 'Выберите тест'; return; } const selOpt = document.querySelector(`#acf-test option[value="${test_id}"]`); if (selOpt && selOpt.textContent.includes('(0 вопр.)')) { errEl.textContent = 'В выбранном тесте нет вопросов. Добавьте вопросы во вкладке «Тесты».'; return; } payload = { ...payload, test_id: Number(test_id), mode }; } else { const subject_slug = document.getElementById('acf-subject').value; const mode = document.getElementById('acf-mode').value; const count = Number(document.getElementById('acf-count').value); if (!subject_slug) { errEl.textContent = 'Выберите предмет'; return; } if (!count || count < 1) { errEl.textContent = 'Укажите количество вопросов'; return; } payload = { ...payload, subject_slug, mode, count }; } const btn = document.getElementById('acf-save'); btn.disabled = true; btn.textContent = 'Создание…'; try { if (_acTarget === 'user') { if (!_acStudentId) { errEl.textContent = 'Выберите ученика из списка'; btn.disabled=false; btn.textContent='Создать'; return; } await LS.createDirectAssignment({ ...payload, student_id: _acStudentId }); } else { const class_id = document.getElementById('acf-class').value; if (!class_id) { errEl.textContent = 'Выберите класс'; btn.disabled=false; btn.textContent='Создать'; return; } await LS.createAssignment(class_id, payload); } closeCreateAModal(); await load(); } catch (e) { errEl.textContent = 'Ошибка: ' + e.message; } finally { btn.disabled = false; btn.textContent = 'Создать'; } } async function deleteAsgn(id) { const a = allAssignments.find(x => x.id === id); if (!await LS.confirm(`Удалить задание «${a?.title}»?\nВсе связанные сессии будут удалены.`, { title: 'Удалить задание', confirmText: 'Удалить' })) return; try { await LS.deleteAssignment(id); allAssignments = allAssignments.filter(x => x.id !== id); renderAssignments(); } catch (e) { LS.toast('Ошибка: ' + e.message, 'error'); } } // Expose handlers used by HTML/inline onclicks window.loadAssignments = load; window.renderAssignments = renderAssignments; window.setAFilter = setAFilter; window.setAfSrc = setAfSrc; window.openAModal = openAModal; window.closeAModal = closeAModal; window.saveAssignment = saveAssignment; window.setAcTarget = setAcTarget; window.filterAcStudents = filterAcStudents; window.openAcStudentDrop = openAcStudentDrop; window.closeAcStudentDrop = closeAcStudentDrop; window.selectAcStudent = selectAcStudent; window.setAcSrc = setAcSrc; window.filterAcFiles = filterAcFiles; window.selectAcFile = selectAcFile; window.openCreateAModal = openCreateAModal; window.closeCreateAModal = closeCreateAModal; window.saveNewAssignment = saveNewAssignment; window.deleteAsgn = deleteAsgn; window.AdminSections = window.AdminSections || {}; window.AdminSections.assignments = { init: async () => { if (inited) return; inited = true; await load(); }, reload: load, }; })();