Files
magistr/frontend/admin/js/main.js

215 lines
8.5 KiB
JavaScript
Executable File
Raw Blame History

This file contains ambiguous Unicode characters
This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.
// 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 } 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', () => {
localStorage.removeItem('token');
localStorage.removeItem('role');
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);