'use strict';
/* admin → sessions section: sessions timeline + drawer detail */
(function () {
'use strict';
let inited = false;
let allSessions = [];
let openDrawerId = null;
/* SVG icons (Lucide-style) — kept local to mirror users.js without coupling */
const SESS_ICONS = {
eye: '',
trash: '',
};
/* Inject .row-actions / .row-action-btn styles only if users.js hasn't (sessions can render first). */
function ensureRowActionsStyles() {
if (document.getElementById('row-actions-style')) return;
const s = document.createElement('style');
s.id = 'row-actions-style';
s.textContent = `
.row-actions { opacity: 0; transition: opacity .15s ease; display: inline-flex; gap: 4px; vertical-align: middle; }
tr:hover .row-actions, .sess-tl-item:hover .row-actions { opacity: 1; }
tr.selected .row-actions, .sess-tl-item.open .row-actions { opacity: 1; }
.row-action-btn { width: 28px; height: 28px; border-radius: 6px; border: 1px solid var(--border); background: transparent; cursor: pointer; display: inline-flex; align-items: center; justify-content: center; color: var(--text-3); transition: background .12s ease, border-color .12s ease, color .12s ease; padding: 0; }
.row-action-btn:hover { background: rgba(155,93,229,.08); border-color: var(--violet); color: var(--violet); }
.row-action-btn:focus-visible { outline: 2px solid var(--violet); outline-offset: 1px; }
.row-action-btn.danger:hover { background: rgba(239,68,68,.08); border-color: var(--red, #EF4444); color: var(--red, #EF4444); }
.row-action-btn svg { width: 14px; height: 14px; pointer-events: none; }
.row-action-btn:disabled { opacity: .5; cursor: wait; }
.row-actions-cell { text-align: right; white-space: nowrap; padding-right: 12px; }
@media (max-width: 768px) {
.row-actions { display: none; }
}
`;
document.head.appendChild(s);
}
async function load() {
const subject = document.getElementById('t-subject').value;
document.getElementById('t-body').innerHTML = '
';
openDrawerId = null;
ensureRowActionsStyles();
try {
allSessions = await LS.adminGetSessions({ subject: subject || undefined });
renderSessions();
} catch (e) {
document.getElementById('t-body').innerHTML = `Ошибка: ${esc(e.message)}
`;
}
}
function sessPctRing(pct) {
const { pctClass } = AdminCtx;
const pc = pctClass(pct);
const colorMap = {'pct-hi':'var(--green)','pct-mid':'var(--amber)','pct-lo':'var(--pink)'};
const color = colorMap[pc] || 'var(--text-3)';
const circ = 106.8;
const dash = (pct / 100 * circ).toFixed(1);
return ``;
}
function renderSessions() {
const { MODES, fmtDate, fmtTime } = AdminCtx;
const modeF = document.getElementById('t-mode').value;
const searchF = document.getElementById('t-search').value.toLowerCase();
const filtered = allSessions.filter(s => {
if (modeF && s.mode !== modeF) return false;
if (searchF && !s.user_name.toLowerCase().includes(searchF) && !s.user_email.toLowerCase().includes(searchF)) return false;
return true;
});
document.getElementById('t-count').textContent = `${filtered.length} тестов`;
if (!filtered.length) {
document.getElementById('t-body').innerHTML = 'Нет тестов
';
return;
}
const groups = {};
filtered.forEach(s => {
const key = fmtDate(s.started_at);
(groups[key] = groups[key] || []).push(s);
});
document.getElementById('t-body').innerHTML = Object.entries(groups).map(([date, sessions]) =>
`${date}
${sessions.map(s => {
const ring = s.percent !== null
? sessPctRing(s.percent)
: `
—
`;
return `
${ring}
${esc(s.user_name)}
${esc(s.subject_name||'?')} · ${MODES[s.mode]||s.mode}
${s.score??'—'} / ${s.total}
${fmtTime(s.duration_sec)}
`;
}).join('')}
`
).join('');
}
async function toggleDrawer(id) {
const drawerEl = document.getElementById('tdrawer-' + id);
const drawer = document.getElementById('drawer-' + id);
const trow = document.getElementById('trow-' + id);
if (openDrawerId && openDrawerId !== id) {
document.getElementById('tdrawer-' + openDrawerId)?.classList.remove('open');
document.getElementById('drawer-' + openDrawerId)?.classList.remove('open');
document.getElementById('trow-' + openDrawerId)?.classList.remove('open');
}
if (openDrawerId === id) {
drawerEl.classList.remove('open'); drawer.classList.remove('open'); trow.classList.remove('open');
openDrawerId = null; return;
}
openDrawerId = id; trow.classList.add('open');
drawerEl.classList.add('open');
requestAnimationFrame(() => drawer.classList.add('open'));
const inner = document.getElementById('drawer-inner-' + id);
if (inner.dataset.loaded) return;
inner.dataset.loaded = '1';
try {
const d = await LS.adminGetSessionDetail(id);
renderDrawer(inner, d);
} catch (e) { inner.innerHTML = `Ошибка: ${esc(e.message)}
`; }
}
function renderDrawer(el, d) {
const { MODES, pctClass, fmtDate, fmtTime, renderMath } = AdminCtx;
const pct = d.score !== null && d.total ? Math.round((d.score/d.total)*100) : null;
const pc = pctClass(pct);
const correct = d.questions.filter(q => q.is_correct).length;
const wrong = d.questions.filter(q => !q.is_correct && q.chosen_option_id).length;
const skipped = d.questions.filter(q => !q.chosen_option_id).length;
const qHtml = d.questions.map((q,i) => {
const status = !q.chosen_option_id ? 'skipped' : q.is_correct ? 'correct' : 'wrong';
const badgeTxt = { correct:'Верно', wrong:'Неверно', skipped:'Пропущено' }[status];
const opts = q.options.map(o => {
const isCor = o.is_correct, isCho = o.id === q.chosen_option_id;
let cls='', icon='';
if (isCor) { cls='correct-opt'; icon=''; }
else if (isCho && !isCor) { cls='chosen-wrong'; icon=''; }
return `${icon}${esc(o.text)}
`;
}).join('');
const expl = q.explanation ? `Пояснение: ${esc(q.explanation)}
` : '';
return `
${esc(q.text)}
${opts}
${expl}
`;
}).join('');
el.innerHTML = `
${qHtml||'
Вопросы не найдены
'}
`;
renderMath(el);
if (window.lucide) lucide.createIcons();
}
async function quickDeleteSession(id, btn) {
if (!await LS.confirm(
'Удалить эту сессию? Все ответы и связанные данные будут удалены.\nЭто действие нельзя отменить.',
{ title: 'Удалить сессию', confirmText: 'Удалить' }
)) return;
btn.disabled = true;
try {
await LS.adminDeleteSession(id);
LS.toast('Сессия удалена', 'success');
// Refresh from server — keeps grouped layout consistent.
await load();
} catch (e) {
LS.toast('Ошибка: ' + e.message, 'error');
btn.disabled = false;
}
}
// Expose handlers
window.loadSessions = load;
window.renderSessions = renderSessions;
window.toggleDrawer = toggleDrawer;
window.quickDeleteSession = quickDeleteSession;
window.AdminSections = window.AdminSections || {};
window.AdminSections.sessions = {
init: async () => { if (inited) return; inited = true; await load(); },
reload: load,
};
})();