92030b462c
Replace ~3500L admin.js monolith with thin orchestrator (~700L) + 14 IIFE-wrapped per-section modules under /js/admin/sections/. Section modules expose AdminSections.<name>.init/reload (lazy init via switchTab/router) and re-expose onclick handlers via window.X for backward compat. Shared helpers (MODES/DIFFS, fmtDate, pctClass, renderMath, qTypeBadge, pagination) live in /js/admin/_shared.js exposed on window.AdminCtx. switchTab now dispatches to AdminSections via ROUTE_TO_SECTION map; non-extracted system tabs (topics/audit/errors/health/classroom/avatars) remain inline in admin.js. user-panel overlay markup untouched — Phase 6 will remove it.
478 lines
22 KiB
JavaScript
478 lines
22 KiB
JavaScript
'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 = '<div class="spinner"></div>';
|
|
try {
|
|
allAssignments = await LS.teacherAssignments();
|
|
renderAssignments();
|
|
} catch (e) {
|
|
document.getElementById('a-body').innerHTML = `<div class="error">Ошибка: ${esc(e.message)}</div>`;
|
|
}
|
|
}
|
|
|
|
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 = [
|
|
`<span class="a-sum-chip s-all">Всего: ${all.length}</span>`,
|
|
nActive ? `<span class="a-sum-chip s-active">Активных: ${nActive}</span>` : '',
|
|
nOverdue ? `<span class="a-sum-chip s-overdue">Просрочено: ${nOverdue}</span>` : '',
|
|
nDone ? `<span class="a-sum-chip s-done">Завершено: ${nDone}</span>` : '',
|
|
].join('');
|
|
|
|
document.getElementById('a-count').textContent = `${list.length} заданий`;
|
|
const container = document.getElementById('a-body');
|
|
|
|
if (!list.length) {
|
|
container.innerHTML = '<div class="empty">Заданий нет</div>';
|
|
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,
|
|
`<span class="mode-badge mode-${a.mode}">${MODES[a.mode]||a.mode}</span>`,
|
|
a.count + ' вопр.',
|
|
dl ? `до ${dl}` : null,
|
|
isUrgent ? `<span class="a-tag-urgent"><i data-lucide="zap" style="width:10px;height:10px;vertical-align:-1px"></i> срочно</span>` : null,
|
|
cls === 'overdue' ? `<span class="a-tag-over">просрочено</span>` : null,
|
|
].filter(Boolean);
|
|
|
|
const barColor = pct >= 75 ? '#06D6A0' : pct >= 40 ? '#F59E0B' : '#F15BB5';
|
|
const pctLabel = pct !== null ? `${pct}%` : '—';
|
|
|
|
return `<div class="a-row ${rowCls}${isUrgent ? ' a-urgent' : ''}" style="--ac:${sColor}">
|
|
<div class="a-icon" style="background:${sColor}18;color:${sColor}"><i data-lucide="${SUBJ_ICONS_A[a.subject_slug]||'file-text'}" style="width:18px;height:18px"></i></div>
|
|
<div class="a-main">
|
|
<div class="a-title">${esc(a.title)}</div>
|
|
<div class="a-meta">${metaParts.join(' · ')}</div>
|
|
</div>
|
|
<div class="a-prog">
|
|
<div class="a-prog-nums">
|
|
<span>${a.completed_count} / ${a.total_members} сдали</span>
|
|
<span class="a-prog-pct ${pctClass(pct)}">${pctLabel}</span>
|
|
</div>
|
|
<div class="a-prog-bar">
|
|
<div class="a-prog-fill" style="width:${pct||0}%;background:${barColor}"></div>
|
|
</div>
|
|
</div>
|
|
<div class="a-actions">
|
|
<button class="btn-edit-q" onclick="openAModal(${a.id})">Изменить</button>
|
|
<button class="btn-del-q" onclick="deleteAsgn(${a.id})"><i data-lucide="x" style="width:14px;height:14px"></i></button>
|
|
</div>
|
|
</div>`;
|
|
}).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 = '<option value="">Загрузка…</option>';
|
|
try {
|
|
_afLoadedTests = await LS.getTests();
|
|
testSel.innerHTML = _afLoadedTests.length
|
|
? '<option value="">— выберите тест —</option>' + _afLoadedTests.map(t => `<option value="${t.id}">${esc(t.title)} (${t.question_count} вопр.)</option>`).join('')
|
|
: '<option value="">Нет тестов</option>';
|
|
} catch {
|
|
testSel.innerHTML = '<option value="">Ошибка загрузки</option>';
|
|
_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 = '<div style="padding:8px 12px;font-size:13px;color:#9ca3af">Загрузка…</div>';
|
|
drop.style.display = '';
|
|
try {
|
|
_acAllStudents = await LS.getStudentsList();
|
|
openAcStudentDrop();
|
|
} catch(e) {
|
|
_acAllStudents = [];
|
|
drop.innerHTML = `<div style="padding:8px 12px;font-size:13px;color:#ef4444">Ошибка загрузки: ${e.message}</div>`;
|
|
}
|
|
}
|
|
|
|
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 = '<div style="padding:8px 12px;font-size:13px;color:#9ca3af">Нет учеников</div>';
|
|
drop.style.display = '';
|
|
return;
|
|
}
|
|
drop.innerHTML = filtered.slice(0, 50).map(s =>
|
|
`<div style="padding:8px 12px;cursor:pointer;border-bottom:1px solid #f3f4f6;font-size:13px" data-id="${s.id}" data-name="${esc(s.name)}" data-email="${esc(s.email)}" onmousedown="selectAcStudent(+this.dataset.id,this.dataset.name,this.dataset.email)" onmouseover="this.style.background='#f9fafb'" onmouseout="this.style.background=''">${esc(s.name)} <span style="color:#9ca3af">${esc(s.email)}</span></div>`
|
|
).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 = '<div style="padding:10px;color:var(--text-3);font-size:.82rem;text-align:center">Загрузка…</div>'; 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 = '<div style="padding:10px;color:var(--text-3);font-size:.82rem;text-align:center">Нет файлов</div>'; return; }
|
|
el.innerHTML = items.map(f => `
|
|
<div onclick="selectAcFile(${f.id},'${esc(f.title||'Файл')}','${f.subject_slug||''}')"
|
|
style="padding:9px 12px;cursor:pointer;border-bottom:1px solid rgba(15,23,42,0.07);display:flex;align-items:center;gap:8px;${_acFileId===f.id?'background:rgba(155,93,229,0.08);':''} transition:background .15s">
|
|
<div style="flex:1">
|
|
<div style="font-size:.84rem;font-weight:600">${esc(f.title||'Файл')}</div>
|
|
<div style="font-size:.74rem;color:var(--text-3)">${SUBJ[f.subject_slug]||f.subject_slug||''}</div>
|
|
</div>
|
|
${_acFileId===f.id ? '<span style="color:var(--violet)"><i data-lucide="check" style="width:15px;height:15px"></i></span>' : ''}
|
|
</div>`).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 = '<option value="">Загрузка…</option>';
|
|
testSel.innerHTML = '<option value="">Загрузка…</option>';
|
|
|
|
const [classesP, testsP] = await Promise.allSettled([LS.getClasses(), LS.getTests()]);
|
|
|
|
if (classesP.status === 'fulfilled') {
|
|
const classes = classesP.value;
|
|
clsSel.innerHTML = classes.length
|
|
? '<option value="">— выберите класс —</option>' + classes.map(c => `<option value="${c.id}">${esc(c.name)} (${c.member_count} уч.)</option>`).join('')
|
|
: '<option value="">Нет классов — создайте класс</option>';
|
|
} else {
|
|
clsSel.innerHTML = `<option value="">Ошибка загрузки классов</option>`;
|
|
}
|
|
|
|
if (testsP.status === 'fulfilled') {
|
|
const tests = testsP.value;
|
|
testSel.innerHTML = tests.length
|
|
? '<option value="">— выберите тест —</option>' + tests.map(t => `<option value="${t.id}">${esc(t.title)} (${t.question_count} вопр.)</option>`).join('')
|
|
: '<option value="">Нет тестов — создайте тест</option>';
|
|
} else {
|
|
testSel.innerHTML = `<option value="">Ошибка загрузки тестов</option>`;
|
|
}
|
|
|
|
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,
|
|
};
|
|
})();
|