import { useEffect, useState } from 'react'; import { readUrlState, writeUrlState } from '../../admin/js/url-state.js'; import { trackUxEvent } from '../../telemetry.js'; import { buildScheduleOverviewModel } from '../../schedule-overview.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 { ScheduleOverview } from '../shared/ui/ScheduleOverview.jsx'; import { addDays, formatWeekLabel, parseIsoDate, startOfWeek, toIsoDate, weekEndIso } from '../shared/dates.js'; import { AbsencePanel } from './AbsencePanel.jsx'; import { PreferencesTab } from './PreferencesTab.jsx'; import { RequestDialog } from './RequestDialog.jsx'; import { RequestsTab } from './RequestsTab.jsx'; import { TeacherWeekGrid } from './TeacherWeekGrid.jsx'; import { cancelChangeRequest, createChangeRequest, fetchChangeRequests, fetchTeacherSchedule, pendingCount, WORKSPACE_TABS } from './teacher-service.js'; const THEME_STORAGE_KEY = 'theme'; const OVERVIEW_DAYS = 14; const TAB_TITLES = Object.freeze({ preferences: 'Мои пожелания', requests: 'Заявки на изменение', schedule: 'Расписание' }); export function TeacherApp() { const sessionState = useAuthorizedSession(['TEACHER']); useEffect(() => { document.documentElement.dataset.theme = localStorage.getItem(THEME_STORAGE_KEY) || 'light'; }, []); if (sessionState.phase === SESSION_PHASES.loading) { return (
); } if (sessionState.phase !== SESSION_PHASES.ready) { return (
); } return ; } function TeacherWorkspace({ teacherId }) { const [initialUrlState] = useState(() => readUrlState({ teacherTab: 'schedule', teacherDate: '' })); const [activeTab, setActiveTab] = useState(() => ( WORKSPACE_TABS.includes(initialUrlState.teacherTab) ? initialUrlState.teacherTab : 'schedule' )); const [weekStart, setWeekStart] = useState(() => startOfWeek( initialUrlState.teacherDate ? parseIsoDate(initialUrlState.teacherDate) : new Date() )); const [scheduleState, setScheduleState] = useState({ kind: 'loading' }); const [overviewState, setOverviewState] = useState({ kind: 'loading' }); const [requestsState, setRequestsState] = useState({ kind: 'loading', items: [] }); const [dialogLesson, setDialogLesson] = useState(null); const weekStartIso = toIsoDate(weekStart); useEffect(() => { writeUrlState({ teacherDate: weekStartIso }); }, [weekStartIso]); useEffect(() => { let active = true; (async () => { setRequestsState({ kind: 'loading', items: [] }); try { const items = await fetchChangeRequests(); if (!active) return; setRequestsState({ kind: 'ready', items }); } catch { if (!active) return; setRequestsState({ kind: 'error', items: [] }); } })(); return () => { active = false; }; }, []); useEffect(() => { if (!teacherId) { setScheduleState({ kind: 'error', message: 'Не удалось определить преподавателя' }); return undefined; } let active = true; (async () => { setScheduleState({ kind: 'loading' }); try { const lessons = await fetchTeacherSchedule({ endDate: weekEndIso(weekStart), startDate: weekStartIso, teacherId }); if (!active) return; setScheduleState({ kind: 'ready', lessons }); trackUxEvent('teacher_schedule_loaded', { lessons: lessons.length, startDate: weekStartIso }); } catch { if (!active) return; setScheduleState({ kind: 'error' }); } })(); return () => { active = false; }; }, [teacherId, weekStart, weekStartIso]); useEffect(() => { if (!teacherId) { setOverviewState({ kind: 'error', message: 'Не удалось определить преподавателя' }); return undefined; } let active = true; (async () => { setOverviewState({ kind: 'loading' }); const now = new Date(); try { const lessons = await fetchTeacherSchedule({ endDate: toIsoDate(addDays(now, OVERVIEW_DAYS)), startDate: toIsoDate(now), teacherId }); if (!active) return; setOverviewState({ kind: 'ready', lessons, now }); const overview = buildScheduleOverviewModel(lessons, now); trackUxEvent('teacher_schedule_overview_loaded', { todayCount: overview.todayLessons.length, hasNext: Boolean(overview.nextLesson) }); } catch { if (!active) return; setOverviewState({ kind: 'error', message: 'Не удалось загрузить ближайшие занятия' }); } })(); return () => { active = false; }; }, [teacherId]); function switchTab(name) { setActiveTab(name); writeUrlState({ teacherTab: name }); trackUxEvent('teacher_workspace_opened', { section: name }); } function moveWeek(delta) { setWeekStart(current => addDays(current, delta * 7)); } function goToCurrentWeek() { setWeekStart(startOfWeek(new Date())); } function handleDateChange(event) { if (event.target.value) { setWeekStart(startOfWeek(parseIsoDate(event.target.value))); } } 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(); } async function refreshRequests() { try { const items = await fetchChangeRequests(); setRequestsState({ kind: 'ready', items }); } catch { setRequestsState({ kind: 'error', items: [] }); } } async function handleCancelRequest(id) { try { await cancelChangeRequest(id); await refreshRequests(); } catch { setRequestsState({ kind: 'error', items: [] }); } } async function handleSubmitRequest(payload) { try { await createChangeRequest(payload); setDialogLesson(null); await refreshRequests(); switchTab('requests'); } catch { return false; } return true; } return (

Кабинет преподавателя

{formatWeekLabel(weekStart)}
Выйти
{dialogLesson && ( setDialogLesson(null)} onSubmitted={handleSubmitRequest} /> )}
); } function scheduleStatus(scheduleState) { if (scheduleState.kind === 'error') { return scheduleState.message || 'Не удалось загрузить расписание'; } if (scheduleState.kind === 'ready') { const count = scheduleState.lessons.length; return count > 0 ? `Найдено занятий: ${count}` : 'На этой неделе занятий нет'; } return 'Загрузка расписания...'; }