feat(admin): Phase 6 sub-commit 1 — add deep-page sections (overlay still works)
Add user-detail.js (~370L) and session-detail.js (~180L) section modules that render full pages for #users/:id and #sessions/:id, plus admin.js dispatch and HTML tab-panes. The legacy .user-panel overlay is intentionally still in place — sub-commit 2 will remove it once the deep pages are verified. * admin.js: DEEP_ROUTES map + activateDeepPane(); activate(route, params) signature; initial dispatch respects hash params (so F5 on #users/123 goes straight to the deep page). * admin.html: new tab-panes #tab-user-detail / #tab-session-detail and two script tags. Old #user-panel overlay untouched. * user-detail.js: header (avatar/role/email/meta) + sub-tabs (Обзор/Сессии/Классы/Audit) with URL-synced sub-tab routing (#users/N/sessions etc). Overview: 4 stat cards + per-subject SVG bar chart. Sessions: clickable rows that navigate to #sessions/N. Classes: placeholder empty-state (no per-user classes endpoint). Audit: client-side filter of /admin/audit-log by uid match. Header action buttons (Изменить/Права/История/Бан/Удалить) call existing overlay handlers; window.activeUid is set before opening any modal. * session-detail.js: full header (user/subject/score/stats) + per- question correctness layout reusing the drawer renderer. Delete button uses LS.adminDeleteSession then navigates to #sessions. Clicking the user name opens the user deep page. * users.js: quickOpenUserSessions now navigates to #users/<uid>/sessions instead of the bare #sessions list. Verified node --check on all new/modified JS. baseline npm test still shows pre-existing 3 auth failures unrelated to this change. Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
This commit is contained in:
@@ -69,6 +69,32 @@
|
||||
sublog: 'sublog',
|
||||
};
|
||||
|
||||
/* Phase 6: deep entity pages. When a route has a first param (#users/123),
|
||||
* dispatch to the matching detail section instead of the list section.
|
||||
* Detail sections render into hidden tab-panes (#tab-user-detail / #tab-session-detail)
|
||||
* which are activated by activateDeepPane() below. The "parent" nav item
|
||||
* (Пользователи / Тесты) stays highlighted so users know where they are. */
|
||||
const DEEP_ROUTES = {
|
||||
users: { section: 'user-detail', paneId: 'tab-user-detail', parentTab: 'users' },
|
||||
sessions: { section: 'session-detail', paneId: 'tab-session-detail', parentTab: 'sessions' },
|
||||
};
|
||||
|
||||
function activateDeepPane(deepInfo, params) {
|
||||
// Activate the parent nav item visually (so user knows the section),
|
||||
// but show the deep-page pane instead of the list pane.
|
||||
document.querySelectorAll('.tab-pane').forEach(p => p.classList.remove('active'));
|
||||
document.querySelectorAll('.admin-nav-item').forEach(b => b.classList.remove('active'));
|
||||
const parentBtn = document.querySelector('.admin-nav-item[data-tab="' + deepInfo.parentTab + '"]');
|
||||
if (parentBtn) parentBtn.classList.add('active');
|
||||
const pane = document.getElementById(deepInfo.paneId);
|
||||
if (pane) pane.classList.add('active');
|
||||
const sec = AdminSections[deepInfo.section];
|
||||
if (sec && typeof sec.init === 'function') {
|
||||
// params: [id, subTab?]
|
||||
sec.init(params[0], params[1]);
|
||||
}
|
||||
}
|
||||
|
||||
function switchTab(btn, opts) {
|
||||
if (btn.classList.contains('locked')) {
|
||||
LS.toast('Этот раздел доступен только администраторам', 'warn');
|
||||
@@ -670,8 +696,17 @@
|
||||
(function initAdminRouter() {
|
||||
if (!window.AdminRouter) return;
|
||||
|
||||
function activate(route) {
|
||||
function activate(route, params) {
|
||||
const name = route || 'overview';
|
||||
params = Array.isArray(params) ? params : [];
|
||||
|
||||
// Phase 6: deep page dispatch when route has a first param.
|
||||
const deep = DEEP_ROUTES[name];
|
||||
if (deep && params.length > 0 && AdminSections[deep.section]) {
|
||||
activateDeepPane(deep, params);
|
||||
return;
|
||||
}
|
||||
|
||||
const btn = document.querySelector('.admin-nav-item[data-tab="' + name + '"]');
|
||||
if (!btn) {
|
||||
console.warn('AdminRouter: unknown route', name);
|
||||
@@ -690,13 +725,13 @@
|
||||
switchTab(btn, { fromRouter: true });
|
||||
}
|
||||
|
||||
AdminRouter.on('change', (r) => activate(r.route));
|
||||
AdminRouter.on('change', (r) => activate(r.route, r.params));
|
||||
|
||||
// Initial dispatch: respect existing hash, else default to #overview.
|
||||
const initial = AdminRouter.current();
|
||||
if (!initial.route) {
|
||||
AdminRouter.navigate('#overview', { replace: true, silent: true });
|
||||
} else if (initial.route !== 'overview') {
|
||||
activate(initial.route);
|
||||
} else if (initial.route !== 'overview' || initial.params.length > 0) {
|
||||
activate(initial.route, initial.params);
|
||||
}
|
||||
})();
|
||||
|
||||
Reference in New Issue
Block a user