feat(admin): Phase 6 sub-commit 2 — remove .user-panel overlay
Now that the deep pages (sub-commit 1) work, retire the legacy
.user-panel inline overlay entirely.
* admin.html: removed <div class="user-panel" id="user-panel"> block
inside #tab-users, removed dead .user-panel* CSS (kept .btn-close
for any external use).
* users.js: removed openUserPanel / closeUserPanel / reloadUserPanel
and their closure state (activeTr, activeUserRole). User row onclick
switched from openUserPanel(...) → AdminRouter.navigate('#users/N').
clearUserHistory / toggleBanUser / confirmDeleteUser / openEditUserModal
/ openUserPermsModal / doSet/doReset* all refactored to use the
getActiveUid() helper (reads window.activeUid, set by user-detail.init)
+ reloadDetailAndList() helper (refreshes deep page + list together).
* sessions.js: row click + eye-button switched from toggleDrawer(id)
→ gotoSession(id) → AdminRouter.navigate('#sessions/N'). Removed
toggleDrawer + renderDrawer functions (~60L) and openDrawerId state.
Inline drawer markup removed from the row template.
Verified node --check on all touched JS. ast-index confirms zero
remaining usages of openUserPanel / closeUserPanel / reloadUserPanel /
toggleDrawer across the repo.
This completes Phase 6 and the admin-redesign feature.
Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
This commit is contained in:
@@ -5,7 +5,11 @@
|
||||
let inited = false;
|
||||
|
||||
let allSessions = [];
|
||||
let openDrawerId = null;
|
||||
// Phase 6: clicking a session row navigates to the deep page (#sessions/:id)
|
||||
// instead of toggling an inline drawer. The drawer rendering is gone.
|
||||
function gotoSession(id) {
|
||||
if (window.AdminRouter) AdminRouter.navigate('#sessions/' + id);
|
||||
}
|
||||
|
||||
/* SVG icons (Lucide-style) — kept local to mirror users.js without coupling */
|
||||
const SESS_ICONS = {
|
||||
@@ -39,7 +43,6 @@
|
||||
async function load() {
|
||||
const subject = document.getElementById('t-subject').value;
|
||||
document.getElementById('t-body').innerHTML = '<div class="spinner"></div>';
|
||||
openDrawerId = null;
|
||||
ensureRowActionsStyles();
|
||||
try {
|
||||
allSessions = await LS.adminGetSessions({ subject: subject || undefined });
|
||||
@@ -90,7 +93,7 @@
|
||||
const ring = s.percent !== null
|
||||
? sessPctRing(s.percent)
|
||||
: `<div style="width:48px;height:48px;display:flex;align-items:center;justify-content:center;font-family:'Unbounded',sans-serif;font-size:0.85rem;font-weight:800;color:var(--text-3)">—</div>`;
|
||||
return `<div class="sess-tl-item" id="trow-${s.id}" onclick="toggleDrawer(${s.id})">
|
||||
return `<div class="sess-tl-item" id="trow-${s.id}" onclick="gotoSession(${s.id})">
|
||||
${ring}
|
||||
<div class="sess-tl-user">
|
||||
<div class="sess-tl-name">${esc(s.user_name)}</div>
|
||||
@@ -100,88 +103,15 @@
|
||||
<div class="sess-tl-time">${fmtTime(s.duration_sec)}</div>
|
||||
<div class="row-actions" onclick="event.stopPropagation()">
|
||||
<button type="button" class="row-action-btn" title="Открыть детали"
|
||||
onclick="event.stopPropagation();toggleDrawer(${s.id})">${SESS_ICONS.eye}</button>
|
||||
onclick="event.stopPropagation();gotoSession(${s.id})">${SESS_ICONS.eye}</button>
|
||||
<button type="button" class="row-action-btn danger" title="Удалить сессию"
|
||||
onclick="event.stopPropagation();quickDeleteSession(${s.id},this)">${SESS_ICONS.trash}</button>
|
||||
</div>
|
||||
</div>
|
||||
<div class="sess-tl-drawer" id="tdrawer-${s.id}">
|
||||
<div class="sess-drawer" id="drawer-${s.id}">
|
||||
<div class="sess-drawer-inner" id="drawer-inner-${s.id}"><div class="spinner"></div></div>
|
||||
</div>
|
||||
</div>`;
|
||||
}).join('')}</div>`
|
||||
).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 = `<div class="error">Ошибка: ${esc(e.message)}</div>`; }
|
||||
}
|
||||
|
||||
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='<svg class="ic" viewBox="0 0 24 24"><circle cx="12" cy="12" r="8"/></svg>';
|
||||
if (isCor) { cls='correct-opt'; icon='<i data-lucide="check" style="width:13px;height:13px"></i>'; }
|
||||
else if (isCho && !isCor) { cls='chosen-wrong'; icon='<i data-lucide="x" style="width:13px;height:13px"></i>'; }
|
||||
return `<div class="qb-opt ${cls}"><span class="qb-opt-icon">${icon}</span>${esc(o.text)}</div>`;
|
||||
}).join('');
|
||||
const expl = q.explanation ? `<div class="qb-expl"><strong>Пояснение:</strong> ${esc(q.explanation)}</div>` : '';
|
||||
return `<div class="qb-item ${status}">
|
||||
<div class="qb-header"><span class="qb-qnum">Вопрос ${i+1}</span><span class="qb-badge ${status}">${badgeTxt}</span><span class="qb-time">${q.time_spent_sec?q.time_spent_sec+' сек':''}</span></div>
|
||||
<div class="qb-text">${esc(q.text)}</div>
|
||||
<div class="qb-opts">${opts}</div>${expl}
|
||||
</div>`;
|
||||
}).join('');
|
||||
el.innerHTML = `
|
||||
<div class="drawer-header">
|
||||
<div>
|
||||
<div style="font-family:'Unbounded',sans-serif;font-weight:800;font-size:0.95rem">${esc(d.user_name)}</div>
|
||||
<div class="drawer-meta">${esc(d.user_email)} · ${d.subject_name||'?'} · ${MODES[d.mode]||d.mode} · ${fmtDate(d.started_at)}</div>
|
||||
</div>
|
||||
<div class="drawer-score ${pc}">${pct !== null ? pct+'%' : '—'}</div>
|
||||
<div style="display:flex;gap:20px;margin-left:auto;text-align:center">
|
||||
<div><div style="font-family:'Unbounded',sans-serif;color:var(--green);font-weight:700">${correct}</div><div style="font-size:0.72rem;color:var(--text-3)">Верно</div></div>
|
||||
<div><div style="font-family:'Unbounded',sans-serif;color:var(--pink);font-weight:700">${wrong}</div><div style="font-size:0.72rem;color:var(--text-3)">Неверно</div></div>
|
||||
<div><div style="font-family:'Unbounded',sans-serif;color:var(--text-3);font-weight:700">${skipped}</div><div style="font-size:0.72rem;color:var(--text-3)">Пропущено</div></div>
|
||||
<div><div style="font-family:'Unbounded',sans-serif;color:var(--text-2);font-weight:700">${fmtTime(d.duration_sec)}</div><div style="font-size:0.72rem;color:var(--text-3)">Время</div></div>
|
||||
</div>
|
||||
</div>
|
||||
<div class="qb-list">${qHtml||'<div class="empty">Вопросы не найдены</div>'}</div>`;
|
||||
renderMath(el);
|
||||
if (window.lucide) lucide.createIcons();
|
||||
}
|
||||
|
||||
async function quickDeleteSession(id, btn) {
|
||||
if (!await LS.confirm(
|
||||
'Удалить эту сессию? Все ответы и связанные данные будут удалены.\nЭто действие нельзя отменить.',
|
||||
@@ -202,7 +132,7 @@
|
||||
// Expose handlers
|
||||
window.loadSessions = load;
|
||||
window.renderSessions = renderSessions;
|
||||
window.toggleDrawer = toggleDrawer;
|
||||
window.gotoSession = gotoSession;
|
||||
window.quickDeleteSession = quickDeleteSession;
|
||||
|
||||
window.AdminSections = window.AdminSections || {};
|
||||
|
||||
Reference in New Issue
Block a user