diff --git a/frontend/admin/css/components.css b/frontend/admin/css/components.css index 4084a73..7c24c52 100755 --- a/frontend/admin/css/components.css +++ b/frontend/admin/css/components.css @@ -753,306 +753,4 @@ tbody tr:hover { display: flex; align-items: center; gap: 8px; -} - -/* ===== Modal ===== */ -.modal-overlay { - display: none; - position: fixed; - top: 0; - left: 0; - right: 0; - /*bottom: 0;*/ - background: rgba(0, 0, 0, 0.6); - backdrop-filter: blur(4px); - z-index: 1000; - align-items: center; - justify-content: center; - opacity: 0; - transition: opacity var(--transition); -} - -.modal-overlay.open { - display: flex; - opacity: 1; -} - -.modal-content { - background: var(--bg-primary); - border: 1px solid var(--bg-card-border); - border-radius: var(--radius-md); - padding: 2rem; - width: 100%; - top: 0; - max-width: 100%; - margin: 0 auto; - position: relative; - transform: scale(0.95); - transition: transform 0.3s cubic-bezier(0.175, 0.885, 0.32, 1.275); - box-shadow: 0 20px 50px rgba(0, 0, 0, 0.5); -} - -.modal-overlay.open .modal-content { - transform: scale(1); -} - -.modal-close { - position: absolute; - top: 1rem; - right: 1rem; - background: none; - border: none; - font-size: 1.5rem; - color: var(--text-secondary); - cursor: pointer; - transition: color var(--transition); -} - -.modal-close:hover { - color: var(--error); -} - -.btn-add-lesson { - padding: 0.35rem 0.7rem; - background: rgba(16, 185, 129, 0.1); - border: 1px solid rgba(16, 185, 129, 0.2); - border-radius: var(--radius-sm); - color: var(--success); - font-family: inherit; - font-size: 0.8rem; - cursor: pointer; - transition: background var(--transition), transform var(--transition); - position: relative; - overflow: hidden; -} - -.btn-add-lesson:hover { - background: rgba(16, 185, 129, 0.2); - transform: scale(1.05); -} - -/* Кнопки-переключатели для недели */ -.btn-checkbox { - display: inline-block; - cursor: pointer; -} - -.btn-checkbox input { - position: absolute; - opacity: 0; - width: 0; - height: 0; -} - -.checkbox-btn { - display: inline-block; - padding: 0.5rem 1rem; - background: var(--bg-secondary); - border: 1px solid var(--bg-card-border); - border-radius: var(--radius-sm); - color: var(--text-primary); - transition: all var(--transition); - user-select: none; -} - -.btn-checkbox input:checked+.checkbox-btn { - background: var(--success, #10b981); - /* используем success или зелёный */ - border-color: var(--success, #10b981); - color: white; -} - -/* ===== View Lessons Modal ===== */ -.view-lessons-modal { - width: 50% !important; /* Половина экрана */ - max-width: 50% !important; - background: var(--bg-primary); - border: 1px solid var(--bg-card-border); - border-radius: var(--radius-md); - padding: 2rem; - position: relative; - box-shadow: 0 20px 50px rgba(0, 0, 0, 0.5); - margin: 0; - transform: none; -} - -.modal-header { - display: flex; - justify-content: space-between; - align-items: center; - margin-bottom: 2rem; - padding-right: 2rem; -} - -.modal-header h2 { - margin: 0; - font-size: 1.3rem; - color: var(--text-primary); -} - -/* Контейнер для занятий */ -.lessons-container { - max-height: 70vh; - overflow-y: auto; - padding-right: 0.5rem; -} - -/* Карточка занятия */ -.lesson-card { - background: var(--bg-card); - border: 1px solid var(--bg-card-border); - border-radius: var(--radius-sm); - padding: 1.2rem; - margin-bottom: 1rem; - transition: all 0.2s ease; -} - -.lesson-card:hover { - transform: translateY(-2px); - box-shadow: 0 8px 20px rgba(0, 0, 0, 0.15); - border-color: var(--accent); -} - -.lesson-card-header { - display: flex; - justify-content: space-between; - align-items: center; - margin-bottom: 0.8rem; - padding-bottom: 0.5rem; - border-bottom: 1px dashed var(--bg-card-border); -} - -.lesson-group { - font-weight: 700; - color: var(--accent); - font-size: 1rem; - background: rgba(99, 102, 241, 0.1); - padding: 0.3rem 0.8rem; - border-radius: 20px; -} - -.lesson-time { - color: var(--text-secondary); - font-size: 0.9rem; - display: flex; - align-items: center; - gap: 0.3rem; -} - -.lesson-time::before { - content: "🕒"; - font-size: 0.9rem; - opacity: 0.7; -} - -.lesson-card-body { - display: flex; - flex-direction: column; - gap: 0.5rem; -} - -.lesson-subject { - font-weight: 600; - color: var(--text-primary); - font-size: 1.1rem; - display: flex; - align-items: center; - gap: 0.5rem; -} - -.lesson-subject::before { - content: "📚"; - font-size: 1rem; - opacity: 0.7; -} - -.lesson-details { - display: flex; - flex-wrap: wrap; - gap: 0.8rem; - margin-top: 0.5rem; -} - -.lesson-detail-item { - background: var(--bg-input); - padding: 0.3rem 0.8rem; - border-radius: 15px; - font-size: 0.85rem; - color: var(--text-secondary); - border: 1px solid var(--bg-card-border); -} - -/* День недели как разделитель */ -.lesson-day-divider { - margin: 1.5rem 0 1rem 0; - font-weight: 700; - color: var(--accent); - font-size: 1.1rem; - text-transform: uppercase; - letter-spacing: 0.05em; - border-bottom: 2px solid var(--accent-glow); - padding-bottom: 0.3rem; -} - -.lesson-day-divider:first-of-type { - margin-top: 0; -} - -/* Загрузка и пустые состояния */ -.loading-lessons, .no-lessons { - text-align: center; - color: var(--text-secondary); - padding: 3rem; - font-size: 1rem; - background: var(--bg-card); - border-radius: var(--radius-sm); -} - -/* Светлая тема */ -[data-theme="light"] .lesson-card { - background: white; - border-color: rgba(0, 0, 0, 0.1); -} - -[data-theme="light"] .lesson-group { - background: rgba(99, 102, 241, 0.05); -} - -/* Адаптивность */ -@media (max-width: 1200px) { - .view-lessons-modal { - width: 70% !important; - max-width: 70% !important; - } -} - -@media (max-width: 768px) { - .view-lessons-modal { - width: 90% !important; - max-width: 90% !important; - } - - .lesson-card-header { - flex-direction: column; - align-items: flex-start; - gap: 0.5rem; - } -} - -.btn-view-lessons { - padding: 0.35rem 0.7rem; - background: rgba(99, 102, 241, 0.1); - border: 1px solid rgba(99, 102, 241, 0.2); - border-radius: var(--radius-sm); - color: var(--accent); - font-family: inherit; - font-size: 0.8rem; - cursor: pointer; - transition: all var(--transition); - white-space: nowrap; -} - -.btn-view-lessons:hover { - background: rgba(99, 102, 241, 0.2); - transform: translateY(-1px); } \ No newline at end of file diff --git a/frontend/admin/css/modals.css b/frontend/admin/css/modals.css new file mode 100644 index 0000000..e4be11b --- /dev/null +++ b/frontend/admin/css/modals.css @@ -0,0 +1,418 @@ +/* ===== Modal (общие стили) ===== */ +.modal-overlay { + display: none; + position: fixed; + top: 0; + left: 0; + right: 0; + /* bottom: 0; */ + background: rgba(0, 0, 0, 0.6); + backdrop-filter: blur(4px); + + z-index: 1000; + + align-items: center; + justify-content: center; + + opacity: 0; + transition: opacity var(--transition); +} + +.modal-overlay.open { + display: flex; + opacity: 1; +} + +.modal-content { + background: var(--bg-primary); + border: 1px solid var(--bg-card-border); + border-radius: var(--radius-md); + padding: 2rem; + + width: 100%; + top: 0; + max-width: 100%; + margin: 0 auto; + + position: relative; + transform: scale(0.95); + transition: transform 0.3s cubic-bezier(0.175, 0.885, 0.32, 1.275); + box-shadow: 0 20px 50px rgba(0, 0, 0, 0.5); +} + +.modal-overlay.open .modal-content { + transform: scale(1); +} + +.modal-close { + position: absolute; + top: 1rem; + right: 1rem; + background: none; + border: none; + font-size: 1.5rem; + color: var(--text-secondary); + cursor: pointer; + transition: color var(--transition); +} + +.modal-close:hover { + color: var(--error); +} + +/* ===== Кнопки ===== */ +.btn-add-lesson { + padding: 0.35rem 0.7rem; + background: rgba(16, 185, 129, 0.1); + border: 1px solid rgba(16, 185, 129, 0.2); + border-radius: var(--radius-sm); + color: var(--success); + font-family: inherit; + font-size: 0.8rem; + cursor: pointer; + transition: background var(--transition), transform var(--transition); + position: relative; + overflow: hidden; +} + +.btn-add-lesson:hover { + background: rgba(16, 185, 129, 0.2); + transform: scale(1.05); +} + +.btn-view-lessons { + padding: 0.35rem 0.7rem; + background: rgba(99, 102, 241, 0.1); + border: 1px solid rgba(99, 102, 241, 0.2); + border-radius: var(--radius-sm); + color: var(--accent); + font-family: inherit; + font-size: 0.8rem; + cursor: pointer; + transition: all var(--transition); + white-space: nowrap; +} + +.btn-view-lessons:hover { + background: rgba(99, 102, 241, 0.2); + transform: translateY(-1px); +} + +/* ===== Кнопки-переключатели (неделя) ===== */ +.btn-checkbox { + display: inline-block; + cursor: pointer; +} + +.btn-checkbox input { + position: absolute; + opacity: 0; + width: 0; + height: 0; +} + +.checkbox-btn { + display: inline-block; + padding: 0.5rem 1rem; + background: var(--bg-secondary); + border: 1px solid var(--bg-card-border); + border-radius: var(--radius-sm); + color: var(--text-primary); + transition: all var(--transition); + user-select: none; +} + +.btn-checkbox input:checked + .checkbox-btn { + background: var(--success, #10b981); + border-color: var(--success, #10b981); + color: #fff; +} + +/* =========================================================== + ===== 2-е модальное окно (View Lessons) — ОСНОВНЫЕ ПРАВКИ ===== + Требования: + - слева + - ~30% ширины + - сверху начинается СРАЗУ под 1-й модалкой + - высота = весь остаток до низа экрана + - визуально "ниже" 1-й модалки (и по z-index тоже ниже) + =========================================================== */ + +#modal-view-lessons.modal-overlay { + background: transparent !important; + backdrop-filter: none !important; + pointer-events: none; + z-index: 999; /* ниже чем 1-е (1000) */ +} + +/* В открытом состоянии: прижать влево и опустить вниз на высоту "шапки" */ +#modal-view-lessons.modal-overlay.open { + justify-content: flex-start; + align-items: flex-start; + + padding-left: 1rem; + padding-right: 1rem; + + /* ключевое: высота 1-й модалки приходит из JS через --add-lesson-height */ + padding-top: var(--add-lesson-height, 0px); +} + +/* Панель 2-й модалки */ +#modal-view-lessons .view-lessons-modal { + width: 30vw !important; + max-width: 30vw !important; + min-width: 320px; + + pointer-events: auto; + + background: var(--bg-primary); + border: 1px solid var(--bg-card-border); + border-radius: var(--radius-md); + padding: 2rem; + + position: relative; + box-shadow: 0 20px 50px rgba(0, 0, 0, 0.5); + + margin: 0; + + /* отключаем "пружинку" от .modal-content */ + transform: none; + + /* ключевое: занимает остаток по высоте */ + height: calc(100vh - var(--add-lesson-height, 0px)); + max-height: calc(100vh - var(--add-lesson-height, 0px)); + + /* чтобы скролл был внутри, а не у всей модалки */ + display: flex; + flex-direction: column; + overflow: hidden; +} + +/* Header во 2-й модалке */ +#modal-veiw-lessons .modal-header { + display: flex; + justify-content: space-between; + align-items: center; + margin-bottom: 1rem; + padding-right: 2rem; + flex: 0 0 auto; +} + +#modal-view-lessons .modal-header h2 { + margin: 0; + font-size: 1.3rem; + color: var(--text-primary); +} + +/* Контейнер занятий: растягивается и скроллится */ +#modal-view-lessons .lessons-container { + flex: 1 1 auto; + overflow-y: auto; + + /* перебиваем старое ограничение */ + max-height: none; + + padding-right: 0.5rem; +} + +/* ===== Карточки занятий ===== */ +.lesson-card { + background: var(--bg-card); + border: 1px solid var(--bg-card-border); + border-radius: var(--radius-sm); + padding: 1.2rem; + margin-bottom: 1rem; + transition: all 0.2s ease; +} + +.lesson-card:hover { + transform: translateY(-2px); + box-shadow: 0 8px 20px rgba(0, 0, 0, 0.15); + border-color: var(--accent); +} + +.lesson-card-header { + display: flex; + justify-content: space-between; + align-items: center; + margin-bottom: 0.8rem; + padding-bottom: 0.5rem; + border-bottom: 1px dashed var(--bg-card-border); +} + +.lesson-group { + font-weight: 700; + color: var(--accent); + font-size: 1rem; + background: rgba(99, 102, 241, 0.1); + padding: 0.3rem 0.8rem; + border-radius: 20px; +} + +.lesson-time { + color: var(--text-secondary); + font-size: 0.9rem; + display: flex; + align-items: center; + gap: 0.3rem; +} + +.lesson-time::before { + content: "🕒"; + font-size: 0.9rem; + opacity: 0.7; +} + +.lesson-card-body { + display: flex; + flex-direction: column; + gap: 0.5rem; +} + +.lesson-subject { + font-weight: 600; + color: var(--text-primary); + font-size: 1.1rem; + display: flex; + align-items: center; + gap: 0.5rem; +} + +.lesson-subject::before { + content: "📚"; + font-size: 1rem; + opacity: 0.7; +} + +.lesson-details { + display: flex; + flex-wrap: wrap; + gap: 0.8rem; + margin-top: 0.5rem; +} + +.lesson-detail-item { + background: var(--bg-input); + padding: 0.3rem 0.8rem; + border-radius: 15px; + font-size: 0.85rem; + color: var(--text-secondary); + border: 1px solid var(--bg-card-border); +} + +/* День недели как разделитель */ +.lesson-day-divider { + margin: 1.5rem 0 1rem 0; + font-weight: 700; + color: var(--accent); + font-size: 1.1rem; + text-transform: uppercase; + letter-spacing: 0.05em; + border-bottom: 2px solid var(--accent-glow); + padding-bottom: 0.3rem; +} + +.lesson-day-divider:first-of-type { + margin-top: 0; +} + +/* Загрузка/пусто */ +.loading-lessons, +.no-lessons { + text-align: center; + color: var(--text-secondary); + padding: 3rem; + font-size: 1rem; + background: var(--bg-card); + border-radius: var(--radius-sm); +} + +/* Светлая тема */ +[data-theme="light"] .lesson-card { + background: #fff; + border-color: rgba(0, 0, 0, 0.1); +} + +[data-theme="light"] .lesson-group { + background: rgba(99, 102, 241, 0.05); +} + +/* ===== Адаптивность ===== */ +@media (max-width: 1200px) { + #modal-view-lessons .view-lessons-modal { + width: 40vw !important; + max-width: 40vw !important; + } +} + +@media (max-width: 768px) { + /* На мобилке делаем поведение более "обычным" */ + #modal-view-lessons.modal-overlay.open { + padding-top: 1rem; + justify-content: center; + align-items: flex-start; + } + + #modal-view-lessons .view-lessons-modal { + width: 90vw !important; + max-width: 90vw !important; + min-width: 0; + + /* чтобы занимало почти весь экран */ + height: calc(100vh - 2rem); + max-height: calc(100vh - 2rem); + } + + .lesson-card-header { + flex-direction: column; + align-items: flex-start; + gap: 0.5rem; + } +} + +/* ===== Скролл во 2-й модалке ===== */ +#modal-view-lessons .lessons-container { + scrollbar-width: thin; /* Firefox */ + scrollbar-color: rgba(99, 102, 241, 0.55) rgba(255, 255, 255, 0.06); /* thumb track */ +} + +/* WebKit (Chrome/Edge/Safari) */ +#modal-view-lessons .lessons-container::-webkit-scrollbar { + width: 10px; +} + +#modal-view-lessons .lessons-container::-webkit-scrollbar-track { + background: rgba(255, 255, 255, 0.06); + border-radius: 10px; +} + +#modal-view-lessons .lessons-container::-webkit-scrollbar-thumb { + background: rgba(99, 102, 241, 0.55); /* под accent */ + border-radius: 10px; + border: 2px solid rgba(0, 0, 0, 0); /* чтобы выглядел “тоньше” */ + background-clip: padding-box; +} + +#modal-view-lessons .lessons-container::-webkit-scrollbar-thumb:hover { + background: rgba(99, 102, 241, 0.75); +} + +/* Общий блюр/затемнение за модалками */ +#modal-backdrop{ + position: fixed; + inset: 0; + background: rgba(0,0,0,0.55); + backdrop-filter: blur(6px); + -webkit-backdrop-filter: blur(6px); + + opacity: 0; + pointer-events: none; + transition: opacity var(--transition); + z-index: 998; /* ниже модалок: 999 и 1000 */ +} + +#modal-backdrop.open{ + opacity: 1; + pointer-events: auto; +} + diff --git a/frontend/admin/index.html b/frontend/admin/index.html index 860acd3..8c47146 100755 --- a/frontend/admin/index.html +++ b/frontend/admin/index.html @@ -13,6 +13,7 @@ + diff --git a/frontend/admin/js/views/users.js b/frontend/admin/js/views/users.js index ef01e89..5a02dcf 100755 --- a/frontend/admin/js/views/users.js +++ b/frontend/admin/js/views/users.js @@ -7,11 +7,13 @@ const ROLE_BADGE = { ADMIN: 'badge-admin', TEACHER: 'badge-teacher', STUDENT: 'b export async function initUsers() { const usersTbody = document.getElementById('users-tbody'); const createForm = document.getElementById('create-form'); + const modalBackdrop = document.getElementById('modal-backdrop'); - // Элементы модального окна добавления занятия + // ===== 1-е модальное окно: Добавить занятие ===== const modalAddLesson = document.getElementById('modal-add-lesson'); const modalAddLessonClose = document.getElementById('modal-add-lesson-close'); const addLessonForm = document.getElementById('add-lesson-form'); + const lessonGroupSelect = document.getElementById('lesson-group'); const lessonDisciplineSelect = document.getElementById('lesson-discipline'); const lessonClassroomSelect = document.getElementById('lesson-classroom'); @@ -22,15 +24,21 @@ export async function initUsers() { const lessonDaySelect = document.getElementById('lesson-day'); const weekUpper = document.getElementById('week-upper'); const weekLower = document.getElementById('week-lower'); - // NEW: получаем элемент выбора времени const lessonTimeSelect = document.getElementById('lesson-time'); - // Переменные для хранения загруженных данных + // ===== 2-е модальное окно: Просмотр занятий ===== + const modalViewLessons = document.getElementById('modal-view-lessons'); + const modalViewLessonsClose = document.getElementById('modal-view-lessons-close'); + const lessonsContainer = document.getElementById('lessons-container'); + const modalTeacherName = document.getElementById('modal-teacher-name'); + + let currentLessonsTeacherId = null; + let currentLessonsTeacherName = ''; + // ===== Данные ===== let groups = []; let subjects = []; let classrooms = []; - // NEW: массивы с временными слотами const weekdaysTimes = [ "8:00-9:30", "9:40-11:10", @@ -48,7 +56,39 @@ export async function initUsers() { "13:20-14:50" ]; - // Загрузка групп с сервера + // ========================================================= + // СИНХРОНИЗАЦИЯ ВЫСОТЫ 1-й МОДАЛКИ -> CSS переменная + // ========================================================= + const addLessonContent = document.querySelector('#modal-add-lesson .modal-content'); + + function setAddLessonHeightVar(px) { + const h = Math.max(0, Math.ceil(px || 0)); + document.documentElement.style.setProperty('--add-lesson-height', `${h}px`); + } + + function syncAddLessonHeight() { + if (!addLessonContent) return; + + if (!modalAddLesson?.classList.contains('open')) { + // если первая модалка закрыта — "шапки" нет + setAddLessonHeightVar(0); + return; + } + + setAddLessonHeightVar(addLessonContent.getBoundingClientRect().height); + } + + // Авто-обновление при любом изменении размеров первой модалки + if (addLessonContent && 'ResizeObserver' in window) { + const ro = new ResizeObserver(() => syncAddLessonHeight()); + ro.observe(addLessonContent); + } + + window.addEventListener('resize', () => syncAddLessonHeight()); + + // ========================================================= + // Загрузка справочников + // ========================================================= async function loadGroups() { try { groups = await api.get('/api/groups'); @@ -58,7 +98,6 @@ export async function initUsers() { } } - // Загрузка дисциплин async function loadSubjects() { try { subjects = await api.get('/api/subjects'); @@ -69,39 +108,37 @@ export async function initUsers() { } async function loadClassrooms() { - try { - classrooms = await api.get('/api/classrooms'); - renderClassroomsOptions(); - } catch (e) { - console.error('Ошибка загрузки аудиторий:', e); - } + try { + classrooms = await api.get('/api/classrooms'); + renderClassroomsOptions(); + } catch (e) { + console.error('Ошибка загрузки аудиторий:', e); } + } - // Заполнение select группами function renderGroupOptions() { if (!groups || groups.length === 0) { - lessonClassroomSelect.innerHTML = ''; + lessonGroupSelect.innerHTML = ''; return; } - lessonGroupSelect.innerHTML = '' + + lessonGroupSelect.innerHTML = + '' + groups.map(g => { - let optionText = escapeHtml(g.name); - if(g.groupSize) { - optionText += ` (численность: ${g.groupSize} чел.)`; - } - return ``; + let optionText = escapeHtml(g.name); + if (g.groupSize) optionText += ` (численность: ${g.groupSize} чел.)`; + return ``; }).join(''); } - // Заполнение select дисциплинами function renderSubjectOptions() { - lessonDisciplineSelect.innerHTML = '' + + lessonDisciplineSelect.innerHTML = + '' + subjects.map(s => ``).join(''); } function renderClassroomsOptions() { - if (!classrooms || classrooms.length ===0) { + if (!classrooms || classrooms.length === 0) { lessonClassroomSelect.innerHTML = ''; return; } @@ -110,32 +147,28 @@ export async function initUsers() { const selectedGroup = groups?.find(g => g.id == selectedGroupId); const groupSize = selectedGroup?.groupSize || 0; - lessonClassroomSelect.innerHTML = '' + + lessonClassroomSelect.innerHTML = + '' + classrooms.map(c => { - let optionText = escapeHtml(c.name); - // Добавление текста с инфой о вместимости чел. - if(c.capacity) { - optionText += ` (вместимость: ${c.capacity} чел.)`; - } + let optionText = escapeHtml(c.name); - // Если аудитория занята, то рисуем крестик допом - if (c.isAvailable === false) { - optionText += ` ❌ Занята` - // Если свободна, но меньше численности группы, отображаем воскл. знак - } else if (selectedGroupId && groupSize > 0 && c.capacity && groupSize > c.capacity) { - optionText += ` ⚠️ Недостаточно места`; - } + if (c.capacity) optionText += ` (вместимость: ${c.capacity} чел.)`; + if (c.isAvailable === false) { + optionText += ` ❌ Занята`; + } else if (selectedGroupId && groupSize > 0 && c.capacity && groupSize > c.capacity) { + optionText += ` ⚠️ Недостаточно места`; + } - return ``; + return ``; }).join(''); } - lessonGroupSelect.addEventListener('change', function() { + lessonGroupSelect.addEventListener('change', function () { renderClassroomsOptions(); + requestAnimationFrame(() => syncAddLessonHeight()); }); - // NEW: функция обновления списка времени в зависимости от дня function updateTimeOptions(dayValue) { let times = []; if (dayValue === "Суббота") { @@ -148,17 +181,23 @@ export async function initUsers() { return; } - lessonTimeSelect.innerHTML = '' + + lessonTimeSelect.innerHTML = + '' + times.map(t => ``).join(''); lessonTimeSelect.disabled = false; } + // ========================================================= + // Пользователи + // ========================================================= async function loadUsers() { try { const users = await api.get('/api/users'); renderUsers(users); } catch (e) { - usersTbody.innerHTML = 'Ошибка загрузки: ' + escapeHtml(e.message) + ''; + usersTbody.innerHTML = + 'Ошибка загрузки: ' + + escapeHtml(e.message) + ''; } } @@ -167,45 +206,72 @@ export async function initUsers() { usersTbody.innerHTML = 'Нет пользователей'; return; } + usersTbody.innerHTML = users.map(u => ` - - ${u.id} - ${escapeHtml(u.username)} - ${ROLE_LABELS[u.role] || escapeHtml(u.role)} - - - - - - - - `).join(''); + + ${u.id} + ${escapeHtml(u.username)} + ${ROLE_LABELS[u.role] || escapeHtml(u.role)} + + + + + + + + `).join(''); } - // Сброс формы модального окна + function updateBackdrop() { + if(!modalBackdrop) return; + const anyOpen = + modalAddLesson?.classList.contains('open') || + modalViewLessons?.classList.contains('open'); + + modalBackdrop.classList.toggle('open', anyOpen); + } + // Клик мимо модалок закроет их, если не надо, то закомментить этот код + modalBackdrop?.addEventListener('click', () => { + if (modalAddLesson?.classList.contains('open')) { + modalAddLesson.classList.remove('open'); + resetLessonForm(); + syncAddLessonHeight(); + } + if (modalViewLessons?.classList.contains('open')) { + closeViewLessonsModal(); + } + }); + + // ========================================================= + // 1-я модалка: добавление занятия + // ========================================================= function resetLessonForm() { addLessonForm.reset(); lessonUserId.value = ''; + if (weekUpper) weekUpper.checked = false; if (weekLower) weekLower.checked = false; - // NEW: сбрасываем селект времени + if (lessonOfflineFormat) lessonOfflineFormat.checked = true; if (lessonOnlineFormat) lessonOnlineFormat.checked = false; + lessonTimeSelect.innerHTML = ''; lessonTimeSelect.disabled = true; + hideAlert('add-lesson-alert'); } - // Открытие модалки с установкой userId function openAddLessonModal(userId) { lessonUserId.value = userId; - // NEW: сбрасываем выбранный день и время + lessonDaySelect.value = ''; updateTimeOptions(''); + modalAddLesson.classList.add('open'); + updateBackdrop(); + requestAnimationFrame(() => syncAddLessonHeight()); } - // Обработчик отправки формы добавления занятия addLessonForm.addEventListener('submit', async (e) => { e.preventDefault(); hideAlert('add-lesson-alert'); @@ -216,48 +282,26 @@ export async function initUsers() { const classroomId = lessonClassroomSelect.value; const lessonType = lessonTypeSelect.value; const dayOfWeek = lessonDaySelect.value; - const timeSlot = lessonTimeSelect.value; // NEW: получаем выбранное время + const timeSlot = lessonTimeSelect.value; const lessonFormat = document.querySelector('input[name="lessonFormat"]:checked')?.value; - // Проверка обязательных полей - if (!groupId) { - showAlert('add-lesson-alert', 'Выберите группу', 'error'); - return; - } - if (!subjectId) { - showAlert('add-lesson-alert', 'Выберите дисциплину', 'error'); - return; - } - if (!classroomId) { - showAlert('add-lesson-alert', 'Выберите аудиторию', 'error') - return; - } - if (!dayOfWeek) { - showAlert('add-lesson-alert', 'Выберите день недели', 'error'); - return; - } - // NEW: проверка времени - if (!timeSlot) { - showAlert('add-lesson-alert', 'Выберите время', 'error'); - return; - } + if (!groupId) { showAlert('add-lesson-alert', 'Выберите группу', 'error'); requestAnimationFrame(() => syncAddLessonHeight()); return; } + if (!subjectId) { showAlert('add-lesson-alert', 'Выберите дисциплину', 'error'); requestAnimationFrame(() => syncAddLessonHeight()); return; } + if (!classroomId) { showAlert('add-lesson-alert', 'Выберите аудиторию', 'error'); requestAnimationFrame(() => syncAddLessonHeight()); return; } + if (!dayOfWeek) { showAlert('add-lesson-alert', 'Выберите день недели', 'error'); requestAnimationFrame(() => syncAddLessonHeight()); return; } + if (!timeSlot) { showAlert('add-lesson-alert', 'Выберите время', 'error'); requestAnimationFrame(() => syncAddLessonHeight()); return; } - // Определяем выбранный тип недели const weekUpperChecked = weekUpper?.checked || false; const weekLowerChecked = weekLower?.checked || false; + let weekType = null; - if (weekUpperChecked && weekLowerChecked) { - weekType = 'Обе'; - } else if (weekUpperChecked) { - weekType = 'Верхняя'; - } else if (weekLowerChecked) { - weekType = 'Нижняя'; - } + if (weekUpperChecked && weekLowerChecked) weekType = 'Обе'; + else if (weekUpperChecked) weekType = 'Верхняя'; + else if (weekLowerChecked) weekType = 'Нижняя'; try { - // Отправляем данные на сервер - const response = await api.post('/api/users/lessons/create', { + await api.post('/api/users/lessons/create', { teacherId: parseInt(userId), groupId: parseInt(groupId), subjectId: parseInt(subjectId), @@ -266,10 +310,15 @@ export async function initUsers() { lessonFormat: lessonFormat, day: dayOfWeek, week: weekType, - time: timeSlot // передаём время + time: timeSlot }); + + if (modalViewLessons?.classList.contains('open') && currentLessonsTeacherId == userId) { + await loadTeacherLessons(currentLessonsTeacherId, currentLessonsTeacherName); + } + showAlert('add-lesson-alert', 'Занятие добавлено', 'success'); - // Очищаем только поля, но оставляем userId + lessonGroupSelect.value = ''; lessonDisciplineSelect.value = ''; lessonClassroomSelect.value = ''; @@ -277,106 +326,90 @@ export async function initUsers() { lessonDaySelect.value = ''; lessonTimeSelect.value = ''; lessonTimeSelect.disabled = true; + weekUpper.checked = false; weekLower.checked = false; document.querySelector('input[name="lessonFormat"][value="Очно"]').checked = true; - // Убираем сообщение через 3 секунды, чтобы можно было добавлять дальше + requestAnimationFrame(() => syncAddLessonHeight()); + setTimeout(() => { hideAlert('add-lesson-alert'); + syncAddLessonHeight(); }, 3000); - } catch (e) { - showAlert('add-lesson-alert', e.message || 'Ошибка добавления занятия', 'error'); + } catch (err) { + showAlert('add-lesson-alert', err.message || 'Ошибка добавления занятия', 'error'); + requestAnimationFrame(() => syncAddLessonHeight()); } }); + lessonDaySelect.addEventListener('change', function () { + updateTimeOptions(this.value); + requestAnimationFrame(() => syncAddLessonHeight()); + }); + + if (modalAddLessonClose) { + modalAddLessonClose.addEventListener('click', () => { + modalAddLesson.classList.remove('open'); + resetLessonForm(); + syncAddLessonHeight(); + updateBackdrop(); + }); + } + + if (modalAddLesson) { + modalAddLesson.addEventListener('click', (e) => { + if (e.target === modalAddLesson) { + modalAddLesson.classList.remove('open'); + resetLessonForm(); + syncAddLessonHeight(); + updateBackdrop(); + } + }); + } + + // ========================================================= + // Создание пользователя + // ========================================================= createForm.addEventListener('submit', async (e) => { e.preventDefault(); hideAlert('create-alert'); + const username = document.getElementById('new-username').value.trim(); const password = document.getElementById('new-password').value; const role = document.getElementById('new-role').value; - if (!username || !password) { showAlert('create-alert', 'Заполните все поля', 'error'); return; } + + if (!username || !password) { + showAlert('create-alert', 'Заполните все поля', 'error'); + return; + } try { const data = await api.post('/api/users', { username, password, role }); showAlert('create-alert', `Пользователь "${escapeHtml(data.username)}" создан`, 'success'); createForm.reset(); loadUsers(); - } catch (e) { - showAlert('create-alert', e.message || 'Ошибка соединения', 'error'); + } catch (err) { + showAlert('create-alert', err.message || 'Ошибка соединения', 'error'); } }); - // Обработчик кликов по таблице - usersTbody.addEventListener('click', async (e) => { - const deleteBtn = e.target.closest('.btn-delete'); - if (deleteBtn) { - if (!confirm('Удалить пользователя?')) return; - try { - await api.delete('/api/users/' + deleteBtn.dataset.id); - loadUsers(); - } catch (e) { - alert(e.message || 'Ошибка удаления'); - } - return; - } - - const addLessonBtn = e.target.closest('.btn-add-lesson'); - if (addLessonBtn) { - e.preventDefault(); - if (modalAddLesson) { - openAddLessonModal(addLessonBtn.dataset.id); - } - } - }); - - // NEW: обработчик изменения дня недели для обновления списка времени - lessonDaySelect.addEventListener('change', function() { - updateTimeOptions(this.value); - }); - - // Закрытие модалки по крестику - if (modalAddLessonClose) { - modalAddLessonClose.addEventListener('click', () => { - modalAddLesson.classList.remove('open'); - resetLessonForm(); - }); - } - - // Закрытие по клику на overlay - if (modalAddLesson) { - modalAddLesson.addEventListener('click', (e) => { - if (e.target === modalAddLesson) { - modalAddLesson.classList.remove('open'); - resetLessonForm(); - } - }); - } - - // Загружаем все данные при инициализации + // ========================================================= + // Инициализация + // ========================================================= await Promise.all([loadUsers(), loadGroups(), loadSubjects(), loadClassrooms()]); - -// Элементы второго модального окна - const modalViewLessons = document.getElementById('modal-view-lessons'); - const modalViewLessonsClose = document.getElementById('modal-view-lessons-close'); - const lessonsContainer = document.getElementById('lessons-container'); - const modalTeacherName = document.getElementById('modal-teacher-name'); - -// Функция для загрузки и отображения занятий преподавателя + // ========================================================= + // 2-я модалка: просмотр занятий + // ========================================================= async function loadTeacherLessons(teacherId, teacherName) { try { lessonsContainer.innerHTML = '
Загрузка занятий...
'; - // Устанавливаем имя преподавателя в заголовок - if (teacherName) { - modalTeacherName.textContent = `Занятия преподавателя: ${teacherName}`; - } else { - modalTeacherName.textContent = 'Занятия преподавателя'; - } + modalTeacherName.textContent = teacherName + ? `Занятия преподавателя: ${teacherName}` + : 'Занятия преподавателя'; - // Загружаем занятия const lessons = await api.get(`/api/users/lessons/${teacherId}`); if (!lessons || lessons.length === 0) { @@ -384,34 +417,27 @@ export async function initUsers() { return; } - // Группируем занятия по дням недели const daysOrder = ['Понедельник', 'Вторник', 'Среда', 'Четверг', 'Пятница', 'Суббота']; const lessonsByDay = {}; lessons.forEach(lesson => { - if (!lessonsByDay[lesson.day]) { - lessonsByDay[lesson.day] = []; - } + if (!lessonsByDay[lesson.day]) lessonsByDay[lesson.day] = []; lessonsByDay[lesson.day].push(lesson); }); - // Сортируем занятия в каждом дне по времени Object.keys(lessonsByDay).forEach(day => { - lessonsByDay[day].sort((a, b) => { - // Простая сортировка по времени (можно улучшить) - return a.time.localeCompare(b.time); - }); + lessonsByDay[day].sort((a, b) => a.time.localeCompare(b.time)); }); - // Формируем HTML let html = ''; daysOrder.forEach(day => { - if (lessonsByDay[day]) { - html += `
${day}
`; + if (!lessonsByDay[day]) return; - lessonsByDay[day].forEach(lesson => { - html += ` + html += `
${day}
`; + + lessonsByDay[day].forEach(lesson => { + html += `
${escapeHtml(lesson.groupName)} @@ -428,36 +454,68 @@ export async function initUsers() {
`; - }); - } + }); }); lessonsContainer.innerHTML = html; - } catch (e) { lessonsContainer.innerHTML = `
Ошибка загрузки: ${escapeHtml(e.message)}
`; console.error('Ошибка загрузки занятий:', e); } } -// Функция открытия модального окна function openViewLessonsModal(teacherId, teacherName) { + currentLessonsTeacherId = teacherId; + currentLessonsTeacherName = teacherName || ''; + loadTeacherLessons(teacherId, teacherName); + + requestAnimationFrame(() => syncAddLessonHeight()); + modalViewLessons.classList.add('open'); - // Блокируем скролл страницы - document.body.style.overflow = 'hidden'; + updateBackdrop(); +// document.body.style.overflow = 'hidden'; } -// Функция закрытия модального окна function closeViewLessonsModal() { modalViewLessons.classList.remove('open'); - document.body.style.overflow = ''; + updateBackdrop(); +// document.body.style.overflow = ''; + + currentLessonsTeacherId = null; + currentLessonsTeacherName = ''; } -// Обработчик кликов по таблице (добавляем в существующий) -// Найдите в коде usersTbody.addEventListener('click', ...) и добавьте в него: + if (modalViewLessonsClose) { + modalViewLessonsClose.addEventListener('click', closeViewLessonsModal); + } -// ДОЛЖНО ПОЛУЧИТЬСЯ ТАК: + if (modalViewLessons) { + modalViewLessons.addEventListener('click', (e) => { + if (e.target === modalViewLessons) closeViewLessonsModal(); + }); + } + + document.addEventListener('keydown', (e) => { + if (e.key !== 'Escape') return; + + if (modalAddLesson?.classList.contains('open')) { + modalAddLesson.classList.remove('open'); + resetLessonForm(); + syncAddLessonHeight(); + updateBackdrop(); + return; + } + + if (modalViewLessons?.classList.contains('open')) { + closeViewLessonsModal(); + return; + } + }); + + // ========================================================= + // ЕДИНЫЙ обработчик кликов по таблице (ВАЖНО: без дубля) + // ========================================================= usersTbody.addEventListener('click', async (e) => { const deleteBtn = e.target.closest('.btn-delete'); if (deleteBtn) { @@ -465,8 +523,8 @@ export async function initUsers() { try { await api.delete('/api/users/' + deleteBtn.dataset.id); loadUsers(); - } catch (e) { - alert(e.message || 'Ошибка удаления'); + } catch (err) { + alert(err.message || 'Ошибка удаления'); } return; } @@ -474,40 +532,23 @@ export async function initUsers() { const addLessonBtn = e.target.closest('.btn-add-lesson'); if (addLessonBtn) { e.preventDefault(); - if (modalAddLesson) { - openAddLessonModal(addLessonBtn.dataset.id); - } + + const teacherId = addLessonBtn.dataset.id; + const teacherName = addLessonBtn.dataset.name; + + openAddLessonModal(teacherId); + openViewLessonsModal(teacherId, teacherName); + return; } - // НОВЫЙ ОБРАБОТЧИК для кнопки просмотра занятий const viewLessonsBtn = e.target.closest('.btn-view-lessons'); if (viewLessonsBtn) { e.preventDefault(); const teacherId = viewLessonsBtn.dataset.id; const teacherName = viewLessonsBtn.dataset.name; openViewLessonsModal(teacherId, teacherName); - } - }); - -// Обработчики закрытия модального окна - if (modalViewLessonsClose) { - modalViewLessonsClose.addEventListener('click', closeViewLessonsModal); - } - -// Закрытие по клику на overlay - if (modalViewLessons) { - modalViewLessons.addEventListener('click', (e) => { - if (e.target === modalViewLessons) { - closeViewLessonsModal(); - } - }); - } - -// Закрытие по Escape - document.addEventListener('keydown', (e) => { - if (e.key === 'Escape' && modalViewLessons?.classList.contains('open')) { - closeViewLessonsModal(); + return; } }); } diff --git a/frontend/admin/views/users.html b/frontend/admin/views/users.html index 3eb70b2..84e6815 100755 --- a/frontend/admin/views/users.html +++ b/frontend/admin/views/users.html @@ -168,4 +168,5 @@ - \ No newline at end of file + + \ No newline at end of file