'use strict'; /* ────────────────────────────────────────────────────────────────── Exam Preparation Module — common helpers Loaded by every exam-prep*.html page before its view-specific JS. Responsibilities: - Parse examKey from URL path: /exam-prep/[/...] - Determine current view (dashboard | variants | practice | topics | mock) - Render the tabs bar with the active tab highlighted - Expose helpers on window.EP ────────────────────────────────────────────────────────────────── */ (function () { const VIEWS = [ { id: 'dashboard', label: 'Дашборд', icon: 'gauge', path: '' }, { id: 'variants', label: 'Варианты', icon: 'layout-grid', path: '/variants' }, { id: 'practice', label: 'Тренажёр', icon: 'dumbbell', path: '/practice' }, { id: 'topics', label: 'Темы', icon: 'tag', path: '/topics' }, { id: 'mock', label: 'Пробник', icon: 'timer', path: '/mock' }, ]; /* Parse examKey and view from `/exam-prep/[/[/...]]` */ function parseUrl() { const parts = location.pathname.replace(/\/+$/, '').split('/').filter(Boolean); // parts[0] === 'exam-prep'; parts[1] === examKey; parts[2] === optional view const examKey = parts[1] || 'math9'; const view = (parts[2] && VIEWS.find(v => v.id === parts[2])) ? parts[2] : 'dashboard'; return { examKey, view }; } function renderTabs(containerSel, { examKey, view }) { const el = document.querySelector(containerSel); if (!el) return; el.innerHTML = VIEWS.map(v => { const href = `/exam-prep/${examKey}${v.path}`; const active = v.id === view ? ' active' : ''; return ` ${v.label} `; }).join(''); if (window.lucide && typeof lucide.createIcons === 'function') lucide.createIcons(); } /* Bootstrap shared for every exam-prep page. - Reads {examKey, view} - Initializes LS auth/page chrome - Renders the tabs bar (if a #ep-tabs slot exists) - Loads track info and writes it into #ep-title / #ep-sub if present - Returns the {track, counts, progress} payload to the caller (Promise) */ async function boot(opts = {}) { const { examKey, view } = parseUrl(); if (typeof LS !== 'undefined') { LS.initPage?.(); LS.showBoardIfAllowed?.(); LS.hideDisabledFeatures?.(); } renderTabs(opts.tabsSelector || '#ep-tabs', { examKey, view }); let info = null; try { info = await LS.api(`/api/exam-prep/${examKey}/info`); } catch (e) { if (e && e.status === 403) { window.location.replace('/403'); return { examKey, view, info: null }; } console.warn('[exam-prep] info failed', e); } if (info?.track) { const titleEl = document.getElementById('ep-title'); const subEl = document.getElementById('ep-sub'); if (titleEl) titleEl.textContent = info.track.title; if (subEl) { const c = info.counts || {}; subEl.textContent = `${info.track.variants_count} вариантов · ${c.total ?? '—'} задач · ` + `${info.track.duration_min} мин`; } } window.EP = window.EP || {}; window.EP.examKey = examKey; window.EP.view = view; window.EP.info = info; return { examKey, view, info }; } window.EP = { boot, parseUrl, renderTabs, VIEWS, }; })();