const API = location.origin; let token = sessionStorage.getItem('authToken'); let currentUser = null; function hdr() { return { 'X-Auth-Token': token }; } function hdrJson() { return { 'Content-Type': 'application/json', 'X-Auth-Token': token }; } function esc(s) { const d = document.createElement('div'); d.textContent = s; return d.innerHTML; } // --- Auth check --- async function checkAuth() { if (!token) { location.href = 'login.html'; return false; } try { const res = await fetch(`${API}/api/auth/me`, { headers: hdr() }); if (!res.ok) { cleanupSession(); return false; } currentUser = await res.json(); return true; } catch { cleanupSession(); return false; } } function cleanupSession() { sessionStorage.removeItem('authToken'); token = null; currentUser = null; location.href = 'login.html'; } function isAdmin() { return currentUser && currentUser.role === 'admin'; } async function requireAdminPage() { if (!(await checkAuth())) return false; if (!isAdmin()) { location.href = 'journal.html'; return false; } return true; } async function doLogout() { try { await fetch(`${API}/api/auth/logout`, { method: 'POST', headers: hdr() }); } catch {} cleanupSession(); } // --- Sidebar --- function buildSidebar(active) { const el = document.getElementById('sidebar'); if (!el) return; const base = [ { page: 'dashboard', label: 'Дашборд' }, { page: 'journal', label: 'Журнал' }, { page: 'trash', label: 'Корзина' }, { page: 'files', label: 'Файлы' }, { page: 'links', label: 'Ссылки' }, { page: 'students', label: 'Ученики' }, { page: 'groups', label: 'Группы' }, ]; const adminNav = [ { page: 'branches', label: 'Филиалы' }, { page: 'users', label: 'Пользователи' }, { page: 'audit', label: 'Аудит' }, { page: 'worker', label: 'Воркер ИИ' }, { page: 'settings', label: 'Настройки' }, ]; const nav = [...base, ...(isAdmin() ? adminNav : [])]; const userName = currentUser?.name || currentUser?.username || ''; const roleLabel = isAdmin() ? 'Администратор' : 'Тутор'; el.innerHTML = `
${esc(userName)}
${roleLabel}
На главную Выйти
`; } // --- Common UI --- function showToast(msg) { const t = document.getElementById('toast'); if (!t) return; t.textContent = msg; t.classList.add('show'); setTimeout(() => t.classList.remove('show'), 3000); } function showImg(src) { const img = document.getElementById('imgModalSrc'); const m = document.getElementById('imgModal'); if (img && m) { img.src = src; m.classList.add('open'); } } async function fillAutocomplete() { const res = await fetch(`${API}/api/students`, { headers: hdr() }); const students = await res.json(); const names = students.map(s => s.name); const dlEdit = document.getElementById('studentEditList'); if (dlEdit) { dlEdit.innerHTML = ''; names.forEach(n => { const o = document.createElement('option'); o.value = n; dlEdit.appendChild(o); }); } const dlAdd = document.getElementById('studentAddList'); if (dlAdd) { dlAdd.innerHTML = ''; names.forEach(n => { const o = document.createElement('option'); o.value = n; dlAdd.appendChild(o); }); } } function copyStudentLink(name) { const url = `${location.origin}/?nameInput=${encodeURIComponent(name)}`; const done = () => showToast('Ссылка скопирована: ' + url); if (navigator.clipboard && window.isSecureContext) { navigator.clipboard.writeText(url).then(done).catch(() => fallbackCopy(url, done)); } else { fallbackCopy(url, done); } } function fallbackCopy(text, done) { const ta = document.createElement('textarea'); ta.value = text; ta.style.position = 'fixed'; ta.style.opacity = '0'; document.body.appendChild(ta); ta.select(); try { document.execCommand('copy'); done(); } catch { prompt('Скопируйте вручную:', text); } document.body.removeChild(ta); } function studentUrlContent(name) { const url = `${location.origin}/?nameInput=${encodeURIComponent(name)}`; return `[InternetShortcut]\r\nURL=${url}\r\n`; } function safeWindowsName(name) { return String(name).replace(/[\\/:*?"<>|]/g, '_').replace(/\s+/g, '_'); } function downloadBlob(blob, filename) { const url = URL.createObjectURL(blob); const a = document.createElement('a'); a.href = url; a.download = filename; a.click(); URL.revokeObjectURL(url); } async function exportStudentUrlZip(students, zipName) { if (typeof JSZip === 'undefined') { alert('JSZip не загружен'); return; } const zip = new JSZip(); students.forEach(s => { zip.file(safeWindowsName(s.name) + '.url', studentUrlContent(s.name)); }); const blob = await zip.generateAsync({ type: 'blob' }); downloadBlob(blob, zipName); } function copyText(text) { const done = () => showToast('Скопировано: ' + text); if (navigator.clipboard && window.isSecureContext) { navigator.clipboard.writeText(text).then(done).catch(() => fallbackCopy(text, done)); } else { fallbackCopy(text, done); } } function renderPager(el, page, totalPages, onPage) { if (!el) return; if (totalPages <= 1) { el.innerHTML = ''; return; } let html = ``; for (let i = 1; i <= totalPages; i++) { if (totalPages > 9 && i > 2 && i < totalPages - 1 && Math.abs(i - page) > 2) { if (i === 3) html += ``; continue; } html += ``; } html += ``; el.innerHTML = html; el._onPage = onPage; el._page = page; if (!el._bound) { el._bound = true; el.addEventListener('click', e => { const btn = e.target.closest('button'); if (!btn || btn.disabled) return; const p = btn.dataset.page ? parseInt(btn.dataset.page, 10) : el._page + parseInt(btn.dataset.nav, 10); if (el._onPage) el._onPage(p); }); } } document.addEventListener('click', e => { const imgEl = e.target.closest('[data-img]'); if (imgEl) { e.preventDefault(); showImg(imgEl.dataset.img); return; } const out = e.target.closest('[data-action="logout"]'); if (out) { e.preventDefault(); doLogout(); } });