// OTel: загружаем только на продакшене (не на localhost) if (!['localhost', '127.0.0.1'].includes(window.location.hostname)) { import('./otel.js').catch(e => console.warn('OTel init skipped:', e.message)); } import { api, isAuthenticatedAsAny, logout } from './api.js'; import { applyRippleEffect, closeAllDropdownsOnOutsideClick } from './utils.js'; import { startDropdownAutoObserver, initAllCustomDropdowns } from './dropdown.js'; const AUTHORIZED_ROLES = ['ADMIN', 'EDUCATION_OFFICE', 'DEPARTMENT', 'SCHEDULE_VIEWER']; const currentRole = localStorage.getItem('role'); if (!isAuthenticatedAsAny(AUTHORIZED_ROLES)) { window.location.href = '/'; } // Global initialization for Custom Selects document.addEventListener('DOMContentLoaded', () => { initAllCustomDropdowns(document.body); startDropdownAutoObserver(); }); // Configuration const ROUTES = { dashboard: { title: 'Дашборд', file: '/admin/views/dashboard.html', init: async () => { const module = await import('./views/dashboard.js'); await module.initDashboard(); } }, users: { title: 'Управление пользователями', file: '/admin/views/users.html', init: async () => { const module = await import('./views/users.js'); await module.initUsers(); } }, 'teacher-requests': { title: 'Заявки', file: '/admin/views/teacher-requests.html', init: async () => { const module = await import('./views/teacher-requests.js'); await module.initTeacherRequests(); } }, groups: { title: 'Управление группами', file: '/admin/views/groups.html', init: async () => { const module = await import('./views/groups.js'); await module.initGroups(); } }, classrooms: { title: 'Аудиторный фонд', file: '/admin/views/classrooms.html', init: async () => { const module = await import('./views/classrooms.js'); await module.initClassrooms(); } }, subjects: { title: 'Дисциплины', file: '/admin/views/subjects.html', init: async () => { const module = await import('./views/subjects.js'); await module.initSubjects(); } }, 'university-structure': { title: 'Структура вуза', file: '/admin/views/university-structure.html', init: async () => { const module = await import('./views/university-structure.js'); await module.initUniversityStructure(); } }, 'department-workspace': { title: 'Кабинет кафедры', file: '/admin/views/department-workspace.html', init: async () => { const module = await import('./views/department-workspace.js'); await module.initDepartmentWorkspace(); } }, 'schedule-view': { title: 'Просмотр расписаний', file: '/admin/views/schedule-view.html', init: async () => { const module = await import('./views/schedule-view.js'); await module.initScheduleView(); } }, schedule: { title: 'Расписание занятий', file: '/admin/views/schedule.html', init: async () => { const module = await import('./views/schedule.js'); await module.initSchedule(); } }, 'academic-calendar': { title: 'Календарный учебный график', file: '/admin/views/academic-calendar.html', init: async () => { const module = await import('./views/academic-calendar.js'); await module.initAcademicCalendar(); } }, 'auditorium-workload': { title: 'Загруженность', file: '/admin/views/auditorium-workload.html', init: async () => { const module = await import('./views/auditorium-workload.js'); await module.initAuditoriumWorkload(); } }, }; const ROLE_NAVIGATION = { ADMIN: { defaultTab: 'dashboard', tabs: ['dashboard', 'teacher-requests', 'schedule', 'academic-calendar', 'auditorium-workload', 'schedule-view', 'classrooms', 'groups', 'university-structure', 'subjects', 'users'] }, EDUCATION_OFFICE: { defaultTab: 'dashboard', tabs: ['dashboard', 'schedule', 'academic-calendar', 'auditorium-workload', 'schedule-view', 'classrooms'] }, DEPARTMENT: { defaultTab: 'department-workspace', tabs: ['department-workspace', 'schedule-view'] }, SCHEDULE_VIEWER: { defaultTab: 'schedule-view', tabs: ['schedule-view'] } }; let currentTab = null; // DOM Elements const appContent = document.getElementById('app-content'); const pageTitle = document.getElementById('page-title'); const navItems = document.querySelectorAll('.nav-item[data-tab]'); const sidebar = document.querySelector('.sidebar'); const sidebarOverlay = document.getElementById('sidebar-overlay'); const menuToggle = document.getElementById('menu-toggle'); const sidebarCloseBtn = document.getElementById('sidebar-close-btn'); const btnLogout = document.getElementById('btn-logout'); const main = document.querySelector('.main'); const teacherRequestsNavBadge = document.getElementById('teacher-requests-nav-badge'); const teacherRequestsTitleBadge = document.getElementById('teacher-requests-title-badge'); const roleNavigation = ROLE_NAVIGATION[currentRole] || ROLE_NAVIGATION.SCHEDULE_VIEWER; const allowedTabs = new Set(roleNavigation.tabs); // Setup Global Effects applyRippleEffect(); closeAllDropdownsOnOutsideClick(); configureRoleInterface(); refreshTeacherRequestBadge(); // Init sidebar state from localStorage on load if (window.innerWidth > 768 && localStorage.getItem('sidebar-collapsed') === 'true') { sidebar.classList.add('collapsed'); main.classList.add('sidebar-collapsed'); } // Menu Toggle (Hamburger) menuToggle.addEventListener('click', () => { if (window.innerWidth <= 768) { sidebar.classList.toggle('open'); sidebarOverlay.classList.toggle('open'); } else { sidebar.classList.remove('collapsed'); main.classList.remove('sidebar-collapsed'); localStorage.setItem('sidebar-collapsed', 'false'); } }); // Sidebar Close (X button) sidebarCloseBtn?.addEventListener('click', () => { if (window.innerWidth <= 768) { sidebar.classList.remove('open'); sidebarOverlay.classList.remove('open'); } else { sidebar.classList.toggle('collapsed'); main.classList.toggle('sidebar-collapsed'); localStorage.setItem('sidebar-collapsed', sidebar.classList.contains('collapsed')); } }); sidebarOverlay.addEventListener('click', () => { sidebar.classList.remove('open'); sidebarOverlay.classList.remove('open'); }); // Settings Dropdown const settingsDropdown = document.getElementById('settings-dropdown'); const btnSettings = document.getElementById('btn-settings'); btnSettings.addEventListener('click', (e) => { e.stopPropagation(); settingsDropdown.classList.toggle('open'); }); document.addEventListener('click', (e) => { if (!settingsDropdown.contains(e.target)) { settingsDropdown.classList.remove('open'); } }); // Logout btnLogout.addEventListener('click', async () => { await logout(); window.location.href = '/'; }); teacherRequestsTitleBadge?.addEventListener('click', () => { switchTab('teacher-requests'); }); // Navigation navItems.forEach(item => { item.addEventListener('click', (e) => { e.preventDefault(); const tab = item.dataset.tab; switchTab(tab); }); }); async function switchTab(tab) { if (currentTab === tab || !ROUTES[tab] || !allowedTabs.has(tab)) return; // UI Update navItems.forEach(n => n.classList.remove('active')); document.querySelector(`.nav-item[data-tab="${tab}"]`)?.classList.add('active'); pageTitle.textContent = ROUTES[tab].title; // Load template try { appContent.innerHTML = '
Загрузка...
'; const response = await fetch(ROUTES[tab].file); if (!response.ok) throw new Error('Не удалось загрузить вкладку'); const html = await response.text(); appContent.innerHTML = html; // Initialize logic for the tab if (ROUTES[tab].init) { await ROUTES[tab].init(); } currentTab = tab; if (window.location.hash !== `#${tab}`) { window.location.hash = tab; } } catch (e) { appContent.innerHTML = `
Ошибка загрузки вкладки: ${e.message}
`; console.error(e); } // Close mobile menu if open sidebar.classList.remove('open'); sidebarOverlay.classList.remove('open'); } function configureRoleInterface() { navItems.forEach(item => { item.hidden = !allowedTabs.has(item.dataset.tab); }); const settingsLink = document.querySelector('.settings-menu a[href="/admin/settings/"]'); if (settingsLink) { settingsLink.hidden = !['ADMIN', 'EDUCATION_OFFICE'].includes(currentRole); } const firstAllowedTitle = { ADMIN: 'Magistr', EDUCATION_OFFICE: 'Учебный отдел', DEPARTMENT: 'Кафедра', SCHEDULE_VIEWER: 'Расписания' }[currentRole] || 'Magistr'; document.querySelector('.logo span').textContent = firstAllowedTitle; } async function refreshTeacherRequestBadge(countOverride = null) { if (currentRole !== 'ADMIN') return; try { const count = countOverride === null ? (await api.get('/api/teacher-requests?status=PENDING') || []).length : countOverride; updateTeacherRequestBadge(teacherRequestsNavBadge, count, count > 99 ? '99+' : String(count)); updateTeacherRequestBadge(teacherRequestsTitleBadge, count, requestCountLabel(count)); } catch (error) { if (teacherRequestsNavBadge) teacherRequestsNavBadge.hidden = true; if (teacherRequestsTitleBadge) teacherRequestsTitleBadge.hidden = true; console.warn('Не удалось загрузить счётчик заявок:', error.message); } } function updateTeacherRequestBadge(element, count, label) { if (!element) return; const normalizedCount = Number(count) || 0; element.textContent = label; element.hidden = normalizedCount <= 0; element.setAttribute('aria-hidden', normalizedCount <= 0 ? 'true' : 'false'); element.setAttribute('aria-label', normalizedCount > 0 ? `Заявок в очереди: ${normalizedCount}` : 'Заявок в очереди нет'); } function requestCountLabel(count) { const mod10 = count % 10; const mod100 = count % 100; if (mod10 === 1 && mod100 !== 11) return `${count} заявка`; if (mod10 >= 2 && mod10 <= 4 && (mod100 < 12 || mod100 > 14)) return `${count} заявки`; return `${count} заявок`; } document.addEventListener('teacherRequestsChanged', (event) => { refreshTeacherRequestBadge(Number.isFinite(event.detail?.count) ? event.detail.count : null); }); const hashTab = window.location.hash.replace('#', ''); switchTab(allowedTabs.has(hashTab) ? hashTab : roleNavigation.defaultTab); window.addEventListener('hashchange', () => { const hashTab = window.location.hash.replace('#', ''); if (allowedTabs.has(hashTab) && hashTab !== currentTab) { switchTab(hashTab); } });