214 lines
8.5 KiB
JavaScript
Executable File
214 lines
8.5 KiB
JavaScript
Executable File
// 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 { 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();
|
||
});
|
||
|
||
import { initUsers } from './views/users.js';
|
||
import { initGroups } from './views/groups.js';
|
||
import { initEduForms } from './views/edu-forms.js';
|
||
import { initEquipments } from './views/equipments.js';
|
||
import { initClassrooms } from './views/classrooms.js';
|
||
import { initSubjects } from './views/subjects.js';
|
||
import {initSchedule} from "./views/schedule.js";
|
||
import {initDatabase} from "./views/database.js";
|
||
import {initDepartmentsData} from "./views/departments-data.js";
|
||
import {initAuditoriumWorkload} from "./views/auditorium-workload.js";
|
||
import {initProfiles} from "./views/profiles.js";
|
||
import {initAcademicCalendar} from "./views/academic-calendar.js";
|
||
import {initScheduleView} from "./views/schedule-view.js";
|
||
import {initDepartmentWorkspace} from "./views/department-workspace.js";
|
||
|
||
// Configuration
|
||
const ROUTES = {
|
||
users: { title: 'Управление пользователями', file: '/admin/views/users.html', init: initUsers },
|
||
groups: { title: 'Управление группами', file: '/admin/views/groups.html', init: initGroups },
|
||
'edu-forms': { title: 'Формы обучения', file: '/admin/views/edu-forms.html', init: initEduForms },
|
||
equipments: { title: 'Оборудование', file: '/admin/views/equipments.html', init: initEquipments },
|
||
classrooms: { title: 'Аудитории', file: '/admin/views/classrooms.html', init: initClassrooms },
|
||
subjects: { title: 'Дисциплины и преподаватели', file: '/admin/views/subjects.html', init: initSubjects },
|
||
'department-workspace': { title: 'Кабинет кафедры', file: '/admin/views/department-workspace.html', init: initDepartmentWorkspace },
|
||
'schedule-view': { title: 'Просмотр расписаний', file: '/admin/views/schedule-view.html', init: initScheduleView },
|
||
schedule: { title: 'Расписание занятий', file: '/admin/views/schedule.html', init: initSchedule },
|
||
'academic-calendar': { title: 'Календарный учебный график', file: '/admin/views/academic-calendar.html', init: initAcademicCalendar },
|
||
'auditorium-workload': { title: 'Загруженность', file: '/admin/views/auditorium-workload.html', init: initAuditoriumWorkload },
|
||
database: { title: 'База данных', file: '/admin/views/database.html', init: initDatabase },
|
||
'departments-data': { title: 'Создание кафедры/специальности', file: '/admin/views/departments-data.html', init: initDepartmentsData },
|
||
profiles: { title: 'Профили обучения', file: '/admin/views/profiles.html', init: initProfiles },
|
||
};
|
||
|
||
const ROLE_NAVIGATION = {
|
||
ADMIN: {
|
||
defaultTab: 'users',
|
||
tabs: Object.keys(ROUTES)
|
||
},
|
||
EDUCATION_OFFICE: {
|
||
defaultTab: 'schedule-view',
|
||
tabs: ['schedule-view', 'schedule', 'academic-calendar', 'auditorium-workload', 'classrooms', 'equipments']
|
||
},
|
||
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 roleNavigation = ROLE_NAVIGATION[currentRole] || ROLE_NAVIGATION.SCHEDULE_VIEWER;
|
||
const allowedTabs = new Set(roleNavigation.tabs);
|
||
|
||
// Setup Global Effects
|
||
applyRippleEffect();
|
||
closeAllDropdownsOnOutsideClick();
|
||
configureRoleInterface();
|
||
|
||
// 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 = '/';
|
||
});
|
||
|
||
// 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 = '<div class="loading-row">Загрузка...</div>';
|
||
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) {
|
||
ROUTES[tab].init();
|
||
}
|
||
|
||
currentTab = tab;
|
||
} catch (e) {
|
||
appContent.innerHTML = `<div class="form-alert error">Ошибка загрузки вкладки: ${e.message}</div>`;
|
||
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;
|
||
}
|
||
|
||
const hashTab = window.location.hash.replace('#', '');
|
||
switchTab(allowedTabs.has(hashTab) ? hashTab : roleNavigation.defaultTab);
|