import { Suspense, useEffect, useRef, useState } from 'react'; import { ADMIN_APP_ROLES, SETTINGS_ROLES } from '../../admin/js/role-capabilities.js'; import { applyRippleEffect, closeAllDropdownsOnOutsideClick } from '../../admin/js/utils.js'; import { initAllCustomDropdowns, startDropdownAutoObserver } from '../../admin/js/dropdown.js'; import { confirmAbandonDirtyChanges } from '../../admin/js/dirty-state.js'; import { trackUxEvent } from '../../telemetry.js'; import { logoutAndRedirect } from '../shared/auth-client.js'; import { SESSION_PHASES } from '../shared/session-policy.js'; import { useAuthorizedSession } from '../shared/use-authorized-session.js'; import { AsyncState } from '../shared/ui/AsyncState.jsx'; import { REACT_TABS } from './react-tabs.js'; import { ADMIN_ROUTES, allowedAdminTabs, badgeVisibility, defaultAdminTabForRole, fetchPendingRequestsCount, logoTitleForRole, navBadgeLabel, navSectionsForRole, requestCountLabel, tabFromHash } from './admin-service.js'; const THEME_STORAGE_KEY = 'theme'; const SIDEBAR_STORAGE_KEY = 'sidebar-collapsed'; const ICONS = Object.freeze({ dashboard: ( ), 'teacher-requests': ( ), 'department-workspace': ( ), schedule: ( ), 'schedule-versions': ( ), 'schedule-quality': ( ), 'academic-calendar': ( ), 'auditorium-workload': ( ), 'schedule-view': ( ), 'teacher-absences': ( ), classrooms: ( ), groups: ( ), 'university-structure': ( ), subjects: ( ), users: ( ) }); export function AdminApp() { const sessionState = useAuthorizedSession(ADMIN_APP_ROLES); if (sessionState.phase === SESSION_PHASES.loading) { return ( ); } if (sessionState.phase !== SESSION_PHASES.ready) { return ( ); } return ; } function AdminFrame({ children }) { return (

Админ-панель

{children}
); } export function AdminWorkspace({ role }) { const allowedTabs = allowedAdminTabs(role); const [activeTab, setActiveTab] = useState(() => ( typeof window === 'undefined' ? defaultAdminTabForRole(role) : tabFromHash(window.location.hash, role) )); const activeTabRef = useRef(activeTab); const [collapsed, setCollapsed] = useState(() => ( typeof window !== 'undefined' && window.innerWidth > 768 && localStorage.getItem(SIDEBAR_STORAGE_KEY) === 'true' )); const [settingsOpen, setSettingsOpen] = useState(false); const [pendingCount, setPendingCount] = useState(null); const settingsRef = useRef(null); useEffect(() => { applyRippleEffect(); closeAllDropdownsOnOutsideClick(); initAllCustomDropdowns(document.body); startDropdownAutoObserver(); }, []); useEffect(() => { document.documentElement.dataset.theme = localStorage.getItem(THEME_STORAGE_KEY) || 'light'; }, []); useEffect(() => { function handleHashChange() { void requestTabSwitch(tabFromHash(window.location.hash, role)); } window.addEventListener('hashchange', handleHashChange); return () => { window.removeEventListener('hashchange', handleHashChange); }; }); useEffect(() => { function handleClickOutside(event) { if (settingsRef.current && !settingsRef.current.contains(event.target)) { setSettingsOpen(false); } } document.addEventListener('click', handleClickOutside); return () => { document.removeEventListener('click', handleClickOutside); }; }, []); useEffect(() => { if (role !== 'ADMIN') return; let active = true; async function refresh(countOverride = null) { try { const count = countOverride === null ? await fetchPendingRequestsCount() : countOverride; if (active) setPendingCount(count); } catch { if (active) setPendingCount(null); console.warn('Не удалось загрузить счётчик заявок'); } } void refresh(); function handleRequestsChanged(event) { void refresh(Number.isFinite(event.detail?.count) ? event.detail.count : null); } document.addEventListener('teacherRequestsChanged', handleRequestsChanged); return () => { active = false; document.removeEventListener('teacherRequestsChanged', handleRequestsChanged); }; }, [role]); async function requestTabSwitch(tab) { if (tab === activeTabRef.current || !allowedTabs.has(tab)) return; if (activeTabRef.current && !(await confirmAbandonDirtyChanges())) return; activeTabRef.current = tab; setActiveTab(tab); trackUxEvent('раздел_открыт', { tab, role }); if (window.location.hash !== `#${tab}`) { window.location.hash = tab; } } function toggleSidebar() { setCollapsed(current => { const next = !current; localStorage.setItem(SIDEBAR_STORAGE_KEY, next); return next; }); } function toggleTheme() { const next = document.documentElement.dataset.theme === 'dark' ? 'light' : 'dark'; document.documentElement.dataset.theme = next; localStorage.setItem(THEME_STORAGE_KEY, next); } function handleLogout(event) { event.preventDefault(); void logoutAndRedirect(); } const sections = navSectionsForRole(role); const title = activeTab ? ADMIN_ROUTES[activeTab].title : 'Админ-панель'; const badge = pendingCount === null ? null : badgeVisibility(pendingCount); return ( <>

{title}

{badge && !badge.hidden && ( )}
{activeTab && }
); } function TabContent({ tab }) { const reactTab = REACT_TABS[tab]; const TabComponent = reactTab?.Component; if (!TabComponent) { return null; } return (
}> ); }