ролевая моделю доступа + загруженность по кафедрам и преподавателям + просморт расписания

This commit is contained in:
Zuev
2026-05-19 23:10:56 +03:00
parent f010ffc467
commit 91729cff7d
93 changed files with 4264 additions and 699 deletions

View File

@@ -3,12 +3,14 @@ if (!['localhost', '127.0.0.1'].includes(window.location.hostname)) {
import('./otel.js').catch(e => console.warn('OTel init skipped:', e.message));
}
import { isAuthenticatedAsAdmin } from './api.js';
import { isAuthenticatedAsAny } from './api.js';
import { applyRippleEffect, closeAllDropdownsOnOutsideClick } from './utils.js';
import { startDropdownAutoObserver, initAllCustomDropdowns } from './dropdown.js';
// Auth check
if (!isAuthenticatedAsAdmin()) {
const AUTHORIZED_ROLES = ['ADMIN', 'EDUCATION_OFFICE', 'DEPARTMENT', 'SCHEDULE_VIEWER'];
const currentRole = localStorage.getItem('role');
if (!isAuthenticatedAsAny(AUTHORIZED_ROLES)) {
window.location.href = '/';
}
@@ -30,21 +32,44 @@ 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: 'views/users.html', init: initUsers },
groups: { title: 'Управление группами', file: 'views/groups.html', init: initGroups },
'edu-forms': { title: 'Формы обучения', file: 'views/edu-forms.html', init: initEduForms },
equipments: { title: 'Оборудование', file: 'views/equipments.html', init: initEquipments },
classrooms: { title: 'Аудитории', file: 'views/classrooms.html', init: initClassrooms },
subjects: { title: 'Дисциплины и преподаватели', file: 'views/subjects.html', init: initSubjects },
schedule: { title: 'Расписание занятий', file: 'views/schedule.html', init: initSchedule },
'academic-calendar': { title: 'Календарный учебный график', file: 'views/academic-calendar.html', init: initAcademicCalendar },
'auditorium-workload': { title: 'Загруженность аудиторий', file: 'views/auditorium-workload.html', init: initAuditoriumWorkload },
database: { title: 'База данных', file: 'views/database.html', init: initDatabase },
'departments-data': { title: 'Создание кафедры/специальности', file: 'views/departments-data.html', init: initDepartmentsData },
profiles: { title: 'Профили обучения', file: 'views/profiles.html', init: initProfiles },
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;
@@ -59,15 +84,13 @@ const menuToggle = document.getElementById('menu-toggle');
const sidebarCloseBtn = document.getElementById('sidebar-close-btn');
const btnLogout = document.getElementById('btn-logout');
const main = document.querySelector('.main');
// Initial auth check
if (!isAuthenticatedAsAdmin()) {
window.location.href = '/';
}
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') {
@@ -136,7 +159,7 @@ navItems.forEach(item => {
});
async function switchTab(tab) {
if (currentTab === tab || !ROUTES[tab]) return;
if (currentTab === tab || !ROUTES[tab] || !allowedTabs.has(tab)) return;
// UI Update
navItems.forEach(n => n.classList.remove('active'));
@@ -147,7 +170,7 @@ async function switchTab(tab) {
try {
appContent.innerHTML = '<div class="loading-row">Загрузка...</div>';
const response = await fetch(ROUTES[tab].file);
if (!response.ok) throw new Error('Failed to load view');
if (!response.ok) throw new Error('Не удалось загрузить вкладку');
const html = await response.text();
appContent.innerHTML = html;
@@ -168,5 +191,24 @@ async function switchTab(tab) {
sidebarOverlay.classList.remove('open');
}
// Load default tab
switchTab('users');
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);