Files
LMS/web/src/main/resources/static/js/admin-group.js

282 lines
11 KiB
JavaScript
Raw Blame History

This file contains ambiguous Unicode characters

This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.

function $id(id) { return document.getElementById(id); }
function fmtSeconds(v) {
var m = Math.floor(v / 60);
var s = v % 60;
return m + ':' + (s < 10 ? '0' : '') + s;
}
/* ---------- Оценка + комментарии ---------- */
function openGradeEditor(btn) {
var all = [];
try { all = JSON.parse(btn.getAttribute('data-comments') || '[]'); } catch (e) { all = []; }
var idx = parseInt(btn.getAttribute('data-ci') || '0', 10);
var list = Array.isArray(all[idx]) ? all[idx] : [];
$id('grade-student').value = btn.getAttribute('data-student') || '';
$id('grade-lab').value = btn.getAttribute('data-lab') || '';
$id('grade-who').value = btn.getAttribute('data-student-name') || '';
var title = $id('grade-title');
if (title) title.textContent = (btn.getAttribute('data-lab-name') || '') + ' — ' + (btn.getAttribute('data-student-name') || '');
$id('grade-score').value = btn.getAttribute('data-score') || '';
var box = $id('cs-rows');
box.innerHTML = '';
list.forEach(function (c) { csRow(c.text, c.tone); });
if (!list.length) csRow('', 'neutral');
var d = $id('modal-grade');
if (d && d.showModal) d.showModal();
}
function csRow(text, tone) {
var box = $id('cs-rows');
var row = document.createElement('div');
row.className = 'cs-row';
var inp = document.createElement('input');
inp.className = 'input cs-text';
inp.placeholder = 'Комментарий';
inp.value = text || '';
var sel = document.createElement('select');
sel.className = 'input cs-tone';
[['positive', '+ позитивный'], ['negative', '— негативный'], ['neutral', '○ нейтральный']].forEach(function (o) {
var op = document.createElement('option');
op.value = o[0];
op.textContent = o[1];
sel.appendChild(op);
});
sel.value = tone || 'neutral';
var rm = document.createElement('button');
rm.type = 'button';
rm.className = 'btn btn-secondary btn-sm';
rm.textContent = 'удалить';
rm.onclick = function () { row.remove(); };
row.appendChild(inp);
row.appendChild(sel);
row.appendChild(rm);
box.appendChild(row);
attachCommentAutocomplete(inp);
}
function csSerialize() {
var out = [];
document.querySelectorAll('#cs-rows .cs-row').forEach(function (r) {
var t = r.querySelector('.cs-text').value.trim();
if (t) out.push({ text: t, tone: r.querySelector('.cs-tone').value });
});
$id('grade-comments').value = JSON.stringify(out);
}
/* ---------- Автодополнение комментариев ----------
* При фокусе/вводе запрашивает ранее введённые комментарии (подстрока, без регистра),
* отсортированные по частоте; выбор подставляет текст и «тон». */
var AC_TIMER = null;
function attachCommentAutocomplete(input) {
var list = document.createElement('div');
list.className = 'ac-list';
list.hidden = true;
input.parentNode.appendChild(list);
var active = -1;
function hide() {
list.hidden = true;
active = -1;
}
function render(items) {
if (document.activeElement !== input) return;
if (!Array.isArray(items) || !items.length) {
hide();
return;
}
list.innerHTML = '';
items.forEach(function (item) {
var row = document.createElement('div');
row.className = 'ac-item';
row.textContent = item.text;
row.addEventListener('mousedown', function (event) {
event.preventDefault();
input.value = item.text;
var tone = input.parentNode.querySelector('.cs-tone');
if (tone && item.tone) tone.value = item.tone;
hide();
});
list.appendChild(row);
});
list.hidden = false;
active = -1;
}
function load() {
clearTimeout(AC_TIMER);
AC_TIMER = setTimeout(function () {
fetch('/admin/comment-templates?q=' + encodeURIComponent(input.value.trim()))
.then(function (r) { return r.ok ? r.json() : []; })
.then(render)
.catch(hide);
}, 150);
}
input.addEventListener('focus', load);
input.addEventListener('input', load);
input.addEventListener('blur', function () { setTimeout(hide, 150); });
input.addEventListener('keydown', function (event) {
if (list.hidden) return;
var items = list.querySelectorAll('.ac-item');
if (!items.length) return;
if (event.key === 'ArrowDown' || event.key === 'ArrowUp') {
event.preventDefault();
active = (active + (event.key === 'ArrowDown' ? 1 : -1) + items.length) % items.length;
items.forEach(function (el, i) { el.classList.toggle('active', i === active); });
} else if (event.key === 'Enter' && active >= 0) {
event.preventDefault();
items[active].dispatchEvent(new MouseEvent('mousedown', { bubbles: true }));
} else if (event.key === 'Escape') {
hide();
}
});
}
/* ---------- Индивидуальное задание (защита) ---------- */
function defenseStatusText(status, code, seconds) {
if (status === 'waiting') {
return code
? 'Задание назначено, код выдан — ждём входа студента.'
: 'Задание назначено, код ещё не выдан.';
}
if (status === 'started') return 'Студент выполняет работу, осталось ' + fmtSeconds(seconds) + '.';
if (status === 'ready') return 'Студент готов к сдаче. Можно начать заново для дополнительного задания.';
return '';
}
/** Открывает диалог задания; кнопка может не нести data-* (тогда это «назначить новое»). */
function openDefense(btn) {
var form = $id('defense-form');
if (!form) return;
form.reset();
var student = btn.getAttribute('data-student') || '';
var lab = btn.getAttribute('data-lab') || '';
if (student) $id('defense-student-select').value = student;
if (lab) $id('defense-lab-select').value = lab;
$id('defense-md').value = btn.getAttribute('data-md') || '';
var session = btn.getAttribute('data-session') || '';
var status = btn.getAttribute('data-status') || '';
var code = btn.getAttribute('data-code') || '';
var seconds = parseInt(btn.getAttribute('data-seconds') || '0', 10);
var actions = $id('defense-actions');
var statusEl = $id('defense-status');
if (session) {
$id('defense-issue-form').action = '/admin/defenses/' + session + '/issue-code';
$id('defense-reset-form').action = '/admin/defenses/' + session + '/reset';
$id('defense-action-lab').value = lab;
$id('defense-reset-lab').value = lab;
$id('defense-reset-form').hidden = status !== 'ready';
if (statusEl) statusEl.textContent = defenseStatusText(status, code, seconds);
actions.hidden = false;
} else {
if (statusEl) statusEl.textContent = '';
actions.hidden = true;
}
var d = $id('modal-defense');
if (d && d.showModal) d.showModal();
}
/* ---------- Назначение варианта (вручную или случайно из уровня) ---------- */
function openVariant(btn) {
var student = btn.getAttribute('data-student') || '';
var lab = btn.getAttribute('data-lab') || '';
var who = btn.getAttribute('data-student-name') || '';
var labName = btn.getAttribute('data-lab-name') || '';
var currentVariant = btn.getAttribute('data-variant') || '';
$id('variant-who').textContent = who + ' — ' + labName;
$id('variant-random-student').value = student;
$id('variant-random-lab').value = lab;
$id('variant-pin-student').value = student;
$id('variant-pin-lab').value = lab;
var levelSel = $id('variant-level');
levelSel.innerHTML = '';
fetch('/admin/labs/' + encodeURIComponent(lab) + '/schema')
.then(function (r) { return r.ok ? r.json() : { levels: [] }; })
.then(function (data) {
(data.levels || []).forEach(function (lvl, i) {
var opt = document.createElement('option');
opt.value = lvl.id;
opt.textContent = lvl.title || ('Уровень ' + (i + 1));
opt.__variants = lvl.variants || [];
levelSel.appendChild(opt);
});
levelSel.onchange = function () { fillVariantSelect(currentVariant); };
fillVariantSelect(currentVariant);
});
var d = $id('modal-variant');
if (d && d.showModal) d.showModal();
}
function fillVariantSelect(currentVariant) {
var levelSel = $id('variant-level');
var variantSel = $id('variant-variant');
variantSel.innerHTML = '';
var opt = levelSel.selectedOptions[0];
var variants = (opt && opt.__variants) || [];
variants.forEach(function (v) {
var o = document.createElement('option');
o.value = v.id;
o.textContent = 'Вариант ' + v.number;
if (currentVariant && v.id === currentVariant) o.selected = true;
variantSel.appendChild(o);
});
$id('variant-random-level').value = levelSel.value || '';
$id('variant-pin-id').value = variantSel.value || '';
variantSel.onchange = function () { $id('variant-pin-id').value = variantSel.value || ''; };
}
/* ---------- Таймеры защиты в ячейках ---------- */
function tickDefenseCells() {
document.querySelectorAll('.defense-cell-timer').forEach(function (el) {
var left = parseInt(el.getAttribute('data-seconds') || '0', 10);
var tick = setInterval(function () {
left -= 1;
if (left <= 0) { clearInterval(tick); el.textContent = '0:00'; return; }
el.textContent = fmtSeconds(left);
}, 1000);
el.textContent = fmtSeconds(left);
});
}
/* ---------- Матрица: фокус на лабораторной ---------- */
function focusLab() {
var s = $id('lab-focus');
if (!s) return;
var labId = s.value;
document.querySelectorAll('th.lab-col[data-lab], td.cell-lab[data-lab], .filter-row th[data-lab]').forEach(function (el) {
el.style.display = (!labId || el.getAttribute('data-lab') === labId) ? '' : 'none';
});
}
document.addEventListener('submit', function (ev) {
if (ev.target && ev.target.id === 'grade-form') csSerialize();
}, true);
window.addEventListener('DOMContentLoaded', function () {
focusLab();
tickDefenseCells();
});