/
Cooolprogrammers
/
web-nodejs-labs
Обзор
Документация
Войти
/
Cooolprogrammers
/
web-nodejs-labs
Код
Запросы
0
Задачи
Пакеты
0
Релизы
0
Аналитика
Безопасность
main
public/lab10/task2/index.html
1 655 строк
56 KB
darkvoid6@mail.ru
ЫЫЫЫЫЫЫЫЫЫЫЫЫЫЫЫЫЫЫЫЫЫЫЫЫЫЫЫЫЫЫЫЫЫЫЫЫЫЫЫЫЫЫЫЫЫЫЫЫЫЫЫЫЫЫЫЫЫЫЫЫЫЫЫЫЫЫЫЫЫЫЫЫЫЫЫЫЫЫЫЫЫЫЫЫЫЫЫЫЫ
18 дек 2025, 07:59
18 дек 2025, 07:59
f2c4f06
Код
Авторство
О чём код?
<!DOCTYPE html> <html lang="ru"> <head> <meta charset="UTF-8"> <meta name="viewport" content="width=device-width, initial-scale=1.0"> <title>Задание 2: Система расписания</title> <link rel="stylesheet" href="css/styles.css"> <link rel="stylesheet" href="https://cdnjs.cloudflare.com/ajax/libs/font-awesome/6.0.0/css/all.min.css"> <style> /* Основные стили */ :root { --primary-color: #33ff44; --primary-dark: #156e1a; --secondary-color: #8df39b; --success-color: #28a745; --info-color: #528d5a; --warning-color: #ffc107; --danger-color: #dc3545; --light-color: #ffffff; --dark-color: #2aff0e; --border-radius: 8px; --box-shadow: 0 4px 6px rgba(0, 0, 0, 0.1); --transition: all 0.3s ease; } * { margin: 0; padding: 0; box-sizing: border-box; } body { font-family: 'Segoe UI', Tahoma, Geneva, Verdana, sans-serif; background: linear-gradient(135deg, #000000 0%, #000000 100%); color: #333; line-height: 1.6; min-height: 100vh; } .container { max-width: 1400px; margin: 0 auto; padding: 20px; } /* Заголовок */ header { background: rgba(2, 51, 0, 0.521); border-radius: var(--border-radius); padding: 20px 30px; margin-bottom: 25px; box-shadow: var(--box-shadow); display: flex; justify-content: space-between; align-items: center; flex-wrap: wrap; gap: 20px; animation: fadeIn 0.5s ease; } header h1 { color: var(--primary-color); display: flex; align-items: center; gap: 10px; font-size: 1.8rem; } nav { display: flex; gap: 15px; align-items: center; } .nav-link { text-decoration: none; color: var(--primary-color); padding: 8px 16px; border: 2px solid var(--primary-color); border-radius: var(--border-radius); display: flex; align-items: center; gap: 8px; transition: var(--transition); font-weight: 500; } .nav-link:hover { background: var(--primary-color); color: white; transform: translateY(-2px); } /* Основной контент */ .main-content { display: grid; grid-template-columns: 1fr; gap: 25px; } /* Панель фильтров */ .filters-panel, .management-panel, .results-section, .stats-panel { background: rgb(15, 53, 0); border-radius: var(--border-radius); padding: 25px; box-shadow: var(--box-shadow); animation: slideUp 0.5s ease; } .filters-panel h2, .management-panel h2, .results-section h2, .stats-panel h2 { color: var(--primary-color); margin-bottom: 20px; display: flex; align-items: center; gap: 10px; font-size: 1.4rem; } /* Сетка фильтров */ .filters-grid { display: grid; grid-template-columns: repeat(auto-fit, minmax(250px, 1fr)); gap: 20px; margin-bottom: 25px; } .filter-group { display: flex; flex-direction: column; gap: 8px; } .filter-group label { font-weight: 600; color: var(--dark-color); display: flex; align-items: center; gap: 8px; } .filter-group select, .filter-group input { padding: 10px 12px; border: 2px solid #00ff22; border-radius: var(--border-radius); font-size: 1rem; transition: var(--transition); color: var(--dark-color); background: rgb(0, 17, 0); } .filter-group select:focus, .filter-group input:focus { outline: none; border-color: var(--primary-color); box-shadow: 0 0 0 3px rgba(74, 111, 165, 0.1); } .filter-actions { display: flex; gap: 15px; justify-content: flex-end; } /* Кнопки */ .btn { padding: 10px 20px; border: none; border-radius: var(--border-radius); font-size: 1rem; font-weight: 600; cursor: pointer; display: inline-flex; align-items: center; justify-content: center; gap: 8px; transition: var(--transition); text-decoration: none; } .btn:hover { transform: translateY(-2px); box-shadow: 0 6px 12px rgba(0, 0, 0, 0.15); } .btn:active { transform: translateY(0); } .btn-primary { background: var(--primary-color); color: white; } .btn-primary:hover { background: var(--primary-dark); } .btn-secondary { background: var(--secondary-color); color: white; } .btn-secondary:hover { background: #5a6268; } .btn-success { background: var(--success-color); color: white; } .btn-success:hover { background: #218838; } .btn-info { background: var(--info-color); color: white; } .btn-info:hover { background: #138496; } /* Панель управления */ .management-actions { display: flex; gap: 15px; flex-wrap: wrap; } /* Таблица расписания */ .loading { text-align: center; padding: 40px; color: var(--secondary-color); display: flex; flex-direction: column; align-items: center; gap: 15px; } .fa-spinner { font-size: 2rem; color: var(--primary-color); } .schedule-table { overflow-x: auto; } .table { width: 100%; border-collapse: collapse; margin-top: 10px; animation: fadeIn 0.8s ease; } .table th { background: var(--primary-color); color: white; padding: 15px; text-align: left; font-weight: 600; position: sticky; top: 0; } .table td { padding: 12px 15px; border-bottom: 1px solid #e0e0e0; } .table tbody tr { transition: var(--transition); animation: fadeInRow 0.3s ease forwards; opacity: 0; } .table tbody tr:hover { background: #f8f9fa; transform: translateX(5px); } @keyframes fadeInRow { from { opacity: 0; transform: translateY(10px); } to { opacity: 1; transform: translateY(0); } } /* Стили для ячеек таблицы */ .day-cell { display: flex; flex-direction: column; gap: 4px; } .day-name { font-weight: 600; color: var(--primary-color); } .week-type { background: #e3f2fd; color: #1565c0; padding: 2px 8px; border-radius: 12px; font-size: 0.8rem; display: inline-block; } .time-cell { display: flex; flex-direction: column; gap: 4px; } .lesson-number { font-weight: 600; color: var(--dark-color); } .group-cell, .subject-cell { display: flex; flex-direction: column; gap: 4px; } .group-cell small, .subject-cell small { color: var(--secondary-color); font-size: 0.85rem; } .teacher-cell { font-weight: 500; } .classroom-cell { display: flex; flex-direction: column; gap: 2px; } .building { font-size: 0.85rem; color: var(--secondary-color); } .room { font-weight: 600; color: var(--dark-color); } .lesson-type { padding: 4px 12px; border-radius: 20px; font-size: 0.85rem; font-weight: 500; display: inline-block; } .lesson-type.лекция { background: #000000; color: #2ac231; } .lesson-type.практика { background: #000000; color: #1565c0; } .lesson-type.лабораторная { background: #000000; color: #7b1fa2; } /* Кнопки действий в таблице */ .action-buttons { display: flex; gap: 8px; } .btn-icon { width: 36px; height: 36px; border: none; border-radius: 50%; display: flex; align-items: center; justify-content: center; cursor: pointer; transition: var(--transition); font-size: 1rem; } .btn-edit { background: #e3f2fd; color: var(--primary-color); } .btn-edit:hover { background: var(--primary-color); color: white; } .btn-delete { background: #ffebee; color: var(--danger-color); } .btn-delete:hover { background: var(--danger-color); color: white; } /* Нет результатов */ .no-results { text-align: center; padding: 50px 20px; color: var(--secondary-color); display: flex; flex-direction: column; align-items: center; gap: 15px; } .no-results i { font-size: 3rem; opacity: 0.5; } /* Статистика */ .stats-grid { display: grid; grid-template-columns: repeat(auto-fit, minmax(200px, 1fr)); gap: 20px; } .stat-card { background: linear-gradient(135deg, #667eea 0%, #764ba2 100%); color: white; padding: 20px; border-radius: var(--border-radius); display: flex; align-items: center; gap: 20px; transition: var(--transition); animation: slideUp 0.5s ease; } .stat-card:hover { transform: translateY(-5px); box-shadow: 0 10px 20px rgba(0, 0, 0, 0.2); } .stat-card:nth-child(2) { background: linear-gradient(135deg, #f093fb 0%, #f5576c 100%); } .stat-card:nth-child(3) { background: linear-gradient(135deg, #4facfe 0%, #00f2fe 100%); } .stat-card:nth-child(4) { background: linear-gradient(135deg, #43e97b 0%, #38f9d7 100%); } .stat-icon { font-size: 2.5rem; opacity: 0.9; } .stat-info h3 { font-size: 2rem; margin-bottom: 5px; } .stat-info p { opacity: 0.9; font-size: 0.9rem; } /* Модальное окно */ .modal { display: none; position: fixed; top: 0; left: 0; width: 100%; height: 100%; background: rgba(0, 0, 0, 0.5); z-index: 1000; align-items: center; justify-content: center; animation: fadeIn 0.3s ease; } .modal-content { background: white; border-radius: var(--border-radius); width: 90%; max-width: 800px; max-height: 90vh; overflow-y: auto; box-shadow: 0 20px 40px rgba(0, 0, 0, 0.2); animation: slideUp 0.3s ease; } .modal-header { padding: 20px 30px; border-bottom: 1px solid #e0e0e0; display: flex; justify-content: space-between; align-items: center; background: var(--primary-color); color: white; border-radius: var(--border-radius) var(--border-radius) 0 0; } .modal-header h3 { display: flex; align-items: center; gap: 10px; } .close { background: none; border: none; color: white; font-size: 1.5rem; cursor: pointer; transition: var(--transition); width: 30px; height: 30px; display: flex; align-items: center; justify-content: center; border-radius: 50%; } .close:hover { background: rgba(255, 255, 255, 0.2); } .modal-body { padding: 30px; } /* Форма */ .form-grid { display: grid; grid-template-columns: repeat(auto-fit, minmax(250px, 1fr)); gap: 20px; margin-bottom: 25px; } .form-group { display: flex; flex-direction: column; gap: 8px; } .form-group.full-width { grid-column: 1 / -1; } .form-group label { font-weight: 600; color: var(--dark-color); display: flex; align-items: center; gap: 8px; } .form-group select, .form-group input { padding: 10px 12px; border: 2px solid #e0e0e0; border-radius: var(--border-radius); font-size: 1rem; transition: var(--transition); background: white; } .form-group select:focus, .form-group input:focus { outline: none; border-color: var(--primary-color); box-shadow: 0 0 0 3px rgba(74, 111, 165, 0.1); } .week-type { display: flex; gap: 20px; flex-wrap: wrap; } .week-type label { display: flex; align-items: center; gap: 8px; font-weight: normal; cursor: pointer; } .week-type input[type="checkbox"] { width: 18px; height: 18px; cursor: pointer; } .form-actions { display: flex; justify-content: flex-end; gap: 15px; padding-top: 20px; border-top: 1px solid #e0e0e0; } /* Индикатор загрузки */ .loading-indicator { display: none; position: fixed; top: 0; left: 0; width: 100%; height: 100%; background: rgba(255, 255, 255, 0.8); z-index: 2000; align-items: center; justify-content: center; } .loading-indicator.active { display: flex; flex-direction: column; gap: 15px; } .loading-indicator::after { content: ''; width: 50px; height: 50px; border: 5px solid #f3f3f3; border-top: 5px solid var(--primary-color); border-radius: 50%; animation: spin 1s linear infinite; } @keyframes spin { 0% { transform: rotate(0deg); } 100% { transform: rotate(360deg); } } /* Анимации */ @keyframes fadeIn { from { opacity: 0; } to { opacity: 1; } } @keyframes slideUp { from { opacity: 0; transform: translateY(20px); } to { opacity: 1; transform: translateY(0); } } /* Адаптивность */ @media (max-width: 768px) { .container { padding: 10px; } header { flex-direction: column; text-align: center; padding: 15px; } .filters-grid { grid-template-columns: 1fr; } .stats-grid { grid-template-columns: 1fr; } .management-actions { flex-direction: column; } .btn { width: 100%; justify-content: center; } .filter-actions, .form-actions { flex-direction: column; } .table { font-size: 0.9rem; } .table th, .table td { padding: 8px 10px; } .action-buttons { flex-direction: column; } .btn-icon { width: 32px; height: 32px; } } @media (max-width: 480px) { header h1 { font-size: 1.4rem; } .filters-panel, .management-panel, .results-section, .stats-panel { padding: 15px; } .stat-card { flex-direction: column; text-align: center; gap: 10px; } .modal-content { width: 95%; margin: 10px; } .modal-body { padding: 15px; } } </style> </head> <body> <div class="container"> <header> <h1><i class="fas fa-calendar-alt"></i> Система расписания занятий</h1> <nav> <a href="../index.html" class="nav-link"><i class="fas fa-arrow-left"></i> Назад</a> <button id="refreshBtn" class="btn btn-primary"><i class="fas fa-sync-alt"></i> Обновить</button> </nav> </header> <div class="main-content"> <!-- Фильтры --> <div class="filters-panel"> <h2><i class="fas fa-filter"></i> Фильтры расписания</h2> <div class="filters-grid"> <div class="filter-group"> <label for="courseFilter"><i class="fas fa-graduation-cap"></i> Курс:</label> <select id="courseFilter"> <option value="">Все курсы</option> <option value="1">1 курс</option> <option value="2">2 курс</option> <option value="3">3 курс</option> <option value="4">4 курс</option> </select> </div> <div class="filter-group"> <label for="groupFilter"><i class="fas fa-users"></i> Группа:</label> <select id="groupFilter"> <option value="">Все группы</option> </select> </div> <div class="filter-group"> <label for="teacherFilter"><i class="fas fa-chalkboard-teacher"></i> Преподаватель:</label> <select id="teacherFilter"> <option value="">Все преподаватели</option> </select> </div> <div class="filter-group"> <label for="dayFilter"><i class="fas fa-calendar-day"></i> День недели:</label> <select id="dayFilter"> <option value="">Все дни</option> <option value="1">Понедельник</option> <option value="2">Вторник</option> <option value="3">Среда</option> <option value="4">Четверг</option> <option value="5">Пятница</option> <option value="6">Суббота</option> </select> </div> <div class="filter-group"> <label for="weekFilter"><i class="fas fa-calendar-week"></i> Неделя:</label> <input type="number" id="weekFilter" min="1" max="52" placeholder="Номер недели"> </div> <div class="filter-group"> <label for="typeFilter"><i class="fas fa-book"></i> Тип занятия:</label> <select id="typeFilter"> <option value="">Все типы</option> </select> </div> </div> <div class="filter-actions"> <button id="applyFilters" class="btn btn-primary"> <i class="fas fa-search"></i> Применить фильтры </button> <button id="resetFilters" class="btn btn-secondary"> <i class="fas fa-undo"></i> Сбросить </button> </div> </div> <!-- Управление --> <div class="management-panel"> <h2><i class="fas fa-cog"></i> Управление расписанием</h2> <div class="management-actions"> <button id="addScheduleBtn" class="btn btn-success"> <i class="fas fa-plus"></i> Добавить занятие </button> <button id="viewAllBtn" class="btn btn-info"> <i class="fas fa-list"></i> Показать все </button> <button id="exportBtn" class="btn btn-secondary"> <i class="fas fa-download"></i> Экспорт </button> <button class="btn btn-warning" onclick="location.href='manage.html'"> <i class="fas fa-database"></i> Справочники </button> </div> </div> <!-- Результаты --> <div class="results-section"> <h2><i class="fas fa-list-alt"></i> Расписание занятий</h2> <div id="loading" class="loading"> <i class="fas fa-spinner fa-spin"></i> Загрузка данных... </div> <div id="scheduleTable" class="schedule-table" style="display: none;"> <!-- Расписание будет загружено через JavaScript --> </div> <div id="noResults" class="no-results" style="display: none;"> <i class="fas fa-calendar-times"></i> <p>Нет данных для отображения</p> </div> </div> <!-- Статистика --> <div class="stats-panel"> <h2><i class="fas fa-chart-bar"></i> Статистика</h2> <div class="stats-grid"> <div class="stat-card"> <div class="stat-icon"> <i class="fas fa-chalkboard"></i> </div> <div class="stat-info"> <h3 id="totalLessons">0</h3> <p>Всего занятий</p> </div> </div> <div class="stat-card"> <div class="stat-icon"> <i class="fas fa-users"></i> </div> <div class="stat-info"> <h3 id="totalGroups">0</h3> <p>Групп в расписании</p> </div> </div> <div class="stat-card"> <div class="stat-icon"> <i class="fas fa-user-tie"></i> </div> <div class="stat-info"> <h3 id="totalTeachers">0</h3> <p>Преподавателей</p> </div> </div> <div class="stat-card"> <div class="stat-icon"> <i class="fas fa-building"></i> </div> <div class="stat-info"> <h3 id="totalClassrooms">0</h3> <p>Аудиторий</p> </div> </div> </div> </div> </div> <!-- Модальное окно добавления/редактирования --> <div id="scheduleModal" class="modal"> <div class="modal-content"> <div class="modal-header"> <h3><i class="fas fa-calendar-plus"></i> Добавить занятие</h3> <button class="close">×</button> </div> <div class="modal-body"> <form id="scheduleForm"> <div class="form-grid"> <div class="form-group"> <label for="formGroup"><i class="fas fa-users"></i> Группа *</label> <select id="formGroup" name="group_id" required> <option value="">Выберите группу</option> </select> </div> <div class="form-group"> <label for="formSubject"><i class="fas fa-book"></i> Дисциплина *</label> <select id="formSubject" name="subject_id" required> <option value="">Выберите дисциплину</option> </select> </div> <div class="form-group"> <label for="formTeacher"><i class="fas fa-chalkboard-teacher"></i> Преподаватель</label> <select id="formTeacher" name="teacher_id"> <option value="">Выберите преподавателя</option> </select> </div> <div class="form-group"> <label for="formClassroom"><i class="fas fa-door-open"></i> Аудитория</label> <select id="formClassroom" name="classroom_id"> <option value="">Выберите аудиторию</option> </select> </div> <div class="form-group"> <label for="formType"><i class="fas fa-tasks"></i> Тип занятия *</label> <select id="formType" name="lesson_type_id" required> <option value="">Выберите тип</option> </select> </div> <div class="form-group"> <label for="formDay"><i class="fas fa-calendar-day"></i> День недели *</label> <select id="formDay" name="day_of_week" required> <option value="">Выберите день</option> <option value="1">Понедельник</option> <option value="2">Вторник</option> <option value="3">Среда</option> <option value="4">Четверг</option> <option value="5">Пятница</option> <option value="6">Суббота</option> </select> </div> <div class="form-group"> <label for="formLessonNumber"><i class="fas fa-sort-numeric-up"></i> Номер пары *</label> <select id="formLessonNumber" name="lesson_number" required> <option value="">Выберите номер</option> <option value="1">1 пара (8:30-10:00)</option> <option value="2">2 пара (10:15-11:45)</option> <option value="3">3 пара (12:00-13:30)</option> <option value="4">4 пара (14:00-15:30)</option> <option value="5">5 пара (15:45-17:15)</option> <option value="6">6 пара (17:30-19:00)</option> </select> </div> <div class="form-group"> <label for="formWeek"><i class="fas fa-calendar-week"></i> Номер недели</label> <input type="number" id="formWeek" name="week_number" min="1" max="52"> </div> <div class="form-group"> <label for="formDate"><i class="fas fa-calendar"></i> Дата</label> <input type="date" id="formDate" name="date"> </div> <div class="form-group full-width"> <label><i class="fas fa-calendar-check"></i> Тип недели</label> <div class="week-type"> <label> <input type="checkbox" name="is_even_week" value="true"> Чётная неделя </label> <label> <input type="checkbox" name="is_odd_week" value="true" checked> Нечётная неделя </label> </div> </div> </div> <div class="form-actions"> <button type="submit" class="btn btn-success"> <i class="fas fa-save"></i> Сохранить </button> <button type="button" class="btn btn-secondary" id="cancelBtn"> <i class="fas fa-times"></i> Отмена </button> </div> </form> </div> </div> </div> </div> <script> const API_BASE = '/api/lab10/task2'; // DOM элементы const courseFilter = document.getElementById('courseFilter'); const groupFilter = document.getElementById('groupFilter'); const teacherFilter = document.getElementById('teacherFilter'); const dayFilter = document.getElementById('dayFilter'); const weekFilter = document.getElementById('weekFilter'); const typeFilter = document.getElementById('typeFilter'); const applyFiltersBtn = document.getElementById('applyFilters'); const resetFiltersBtn = document.getElementById('resetFilters'); const refreshBtn = document.getElementById('refreshBtn'); const addScheduleBtn = document.getElementById('addScheduleBtn'); const viewAllBtn = document.getElementById('viewAllBtn'); const exportBtn = document.getElementById('exportBtn'); const scheduleTable = document.getElementById('scheduleTable'); const loading = document.getElementById('loading'); const noResults = document.getElementById('noResults'); const scheduleModal = document.getElementById('scheduleModal'); const closeModal = document.querySelector('.close'); const cancelBtn = document.getElementById('cancelBtn'); const scheduleForm = document.getElementById('scheduleForm'); // Элементы статистики const totalLessons = document.getElementById('totalLessons'); const totalGroups = document.getElementById('totalGroups'); const totalTeachers = document.getElementById('totalTeachers'); const totalClassrooms = document.getElementById('totalClassrooms'); // Данные справочников let groups = []; let teachers = []; let lessonTypes = []; let subjects = []; let classrooms = []; // Функции для индикатора загрузки function showLoading() { const indicator = document.createElement('div'); indicator.className = 'loading-indicator active'; indicator.id = 'loading-indicator'; document.body.appendChild(indicator); } function hideLoading() { const indicator = document.getElementById('loading-indicator'); if (indicator) { indicator.remove(); } } // Загрузить справочники async function loadReferences() { try { showLoading(); // Загружаем все справочники параллельно const [ groupsRes, teachersRes, typesRes, subjectsRes, classroomsRes ] = await Promise.all([ fetch(`${API_BASE}/groups`), fetch(`${API_BASE}/teachers`), fetch(`${API_BASE}/lesson-types`), fetch(`${API_BASE}/subjects`), fetch(`${API_BASE}/classrooms`) ]); groups = await groupsRes.json(); teachers = await teachersRes.json(); lessonTypes = await typesRes.json(); subjects = await subjectsRes.json(); classrooms = await classroomsRes.json(); // Заполняем фильтры populateGroupFilter(); populateTeacherFilter(); populateTypeFilter(); // Заполняем форму populateFormSelects(); hideLoading(); } catch (error) { console.error('Ошибка загрузки справочников:', error); alert('Не удалось загрузить справочники'); hideLoading(); } } // Заполнить фильтр групп function populateGroupFilter() { groupFilter.innerHTML = '<option value="">Все группы</option>'; groups.forEach(group => { const option = document.createElement('option'); option.value = group.id; option.textContent = `${group.name} (${group.course} курс)`; groupFilter.appendChild(option); }); } // Заполнить фильтр преподавателей function populateTeacherFilter() { teacherFilter.innerHTML = '<option value="">Все преподаватели</option>'; teachers.forEach(teacher => { const option = document.createElement('option'); option.value = teacher.id; option.textContent = `${teacher.last_name} ${teacher.first_name} ${teacher.middle_name || ''}`; teacherFilter.appendChild(option); }); } // Заполнить фильтр типов занятий function populateTypeFilter() { typeFilter.innerHTML = '<option value="">Все типы</option>'; lessonTypes.forEach(type => { const option = document.createElement('option'); option.value = type.id; option.textContent = type.name; typeFilter.appendChild(option); }); } // Заполнить селекты в форме function populateFormSelects() { const formGroup = document.getElementById('formGroup'); const formSubject = document.getElementById('formSubject'); const formTeacher = document.getElementById('formTeacher'); const formClassroom = document.getElementById('formClassroom'); const formType = document.getElementById('formType'); // Группы formGroup.innerHTML = '<option value="">Выберите группу</option>'; groups.forEach(group => { const option = document.createElement('option'); option.value = group.id; option.textContent = `${group.name} (${group.course} курс)`; formGroup.appendChild(option); }); // Дисциплины formSubject.innerHTML = '<option value="">Выберите дисциплину</option>'; subjects.forEach(subject => { const option = document.createElement('option'); option.value = subject.id; option.textContent = subject.name; formSubject.appendChild(option); }); // Преподаватели formTeacher.innerHTML = '<option value="">Выберите преподавателя</option>'; teachers.forEach(teacher => { const option = document.createElement('option'); option.value = teacher.id; option.textContent = `${teacher.last_name} ${teacher.first_name} ${teacher.middle_name || ''}`; formTeacher.appendChild(option); }); // Аудитории formClassroom.innerHTML = '<option value="">Выберите аудиторию</option>'; classrooms.forEach(classroom => { const option = document.createElement('option'); option.value = classroom.id; option.textContent = `${classroom.building_name}, ауд. ${classroom.room_number}`; formClassroom.appendChild(option); }); // Типы занятий formType.innerHTML = '<option value="">Выберите тип</option>'; lessonTypes.forEach(type => { const option = document.createElement('option'); option.value = type.id; option.textContent = type.name; formType.appendChild(option); }); } // Загрузить расписание async function loadSchedule(filters = {}) { try { loading.style.display = 'block'; scheduleTable.style.display = 'none'; noResults.style.display = 'none'; showLoading(); const params = new URLSearchParams(); for (const [key, value] of Object.entries(filters)) { if (value) { params.append(key, value); } } const response = await fetch(`${API_BASE}/schedule?${params}`); const schedule = await response.json(); displaySchedule(schedule); updateStatistics(schedule); hideLoading(); } catch (error) { console.error('Ошибка загрузки расписания:', error); alert('Не удалось загрузить расписание'); hideLoading(); } finally { loading.style.display = 'none'; } } // Отобразить расписание function displaySchedule(schedule) { scheduleTable.innerHTML = ''; if (schedule.length === 0) { noResults.style.display = 'block'; return; } scheduleTable.style.display = 'block'; // Создаем таблицу const table = document.createElement('table'); table.className = 'table'; // Заголовок таблицы const thead = document.createElement('thead'); thead.innerHTML = ` <tr> <th><i class="fas fa-calendar-day"></i> День</th> <th><i class="fas fa-clock"></i> Время</th> <th><i class="fas fa-users"></i> Группа</th> <th><i class="fas fa-book"></i> Дисциплина</th> <th><i class="fas fa-chalkboard-teacher"></i> Преподаватель</th> <th><i class="fas fa-door-open"></i> Аудитория</th> <th><i class="fas fa-tasks"></i> Тип</th> <th><i class="fas fa-cog"></i> Действия</th> </tr> `; table.appendChild(thead); // Тело таблицы const tbody = document.createElement('tbody'); schedule.forEach((item, index) => { const row = document.createElement('tr'); row.style.animationDelay = `${index * 0.05}s`; // Определяем день недели const days = ['', 'Пн', 'Вт', 'Ср', 'Чт', 'Пт', 'Сб', 'Вс']; const dayName = item.day_of_week ? days[item.day_of_week] : '—'; // Определяем время const time = item.start_time && item.end_time ? `${item.start_time.slice(0,5)}-${item.end_time.slice(0,5)}` : '—'; // Определяем тип недели let weekType = ''; if (item.is_even_week && item.is_odd_week) { weekType = 'Каждую неделю'; } else if (item.is_even_week) { weekType = 'Чётная неделя'; } else if (item.is_odd_week) { weekType = 'Нечётная неделя'; } row.innerHTML = ` <td> <div class="day-cell"> <span class="day-name">${dayName}</span> ${item.date ? `<small>${new Date(item.date).toLocaleDateString('ru-RU')}</small>` : ''} ${weekType ? `<small class="week-type">${weekType}</small>` : ''} </div> </td> <td> <div class="time-cell"> <span class="lesson-number">${item.lesson_number || '—'}</span> <span class="time">${time}</span> </div> </td> <td> <div class="group-cell"> <strong>${item.group_name}</strong> <small>${item.course} курс</small> </div> </td> <td> <div class="subject-cell"> <strong>${item.subject_name}</strong> <small>${item.subject_short_name || ''}</small> </div> </td> <td> <div class="teacher-cell"> ${item.teacher_last_name ? `${item.teacher_last_name} ${item.teacher_first_name?.charAt(0)}.${item.teacher_middle_name?.charAt(0) || ''}` : '—' } </div> </td> <td> <div class="classroom-cell"> <span class="building">${item.building_name || '—'}</span> <span class="room">${item.room_number || '—'}</span> </div> </td> <td> <span class="lesson-type ${item.lesson_type_name?.toLowerCase() || ''}"> ${item.lesson_type_name || '—'} </span> </td> <td> <div class="action-buttons"> <button class="btn-icon btn-edit" data-id="${item.id}" title="Редактировать"> <i class="fas fa-edit"></i> </button> <button class="btn-icon btn-delete" data-id="${item.id}" title="Удалить"> <i class="fas fa-trash"></i> </button> </div> </td> `; tbody.appendChild(row); }); table.appendChild(tbody); scheduleTable.appendChild(table); // Добавить обработчики для кнопок действий document.querySelectorAll('.btn-edit').forEach(btn => { btn.addEventListener('click', () => editScheduleItem(btn.dataset.id)); }); document.querySelectorAll('.btn-delete').forEach(btn => { btn.addEventListener('click', () => deleteScheduleItem(btn.dataset.id)); }); } // Обновить статистику function updateStatistics(schedule) { // Подсчет уникальных значений const uniqueGroups = new Set(schedule.map(item => item.group_id)); const uniqueTeachers = new Set(schedule.map(item => item.teacher_id).filter(id => id)); const uniqueClassrooms = new Set(schedule.map(item => item.classroom_id).filter(id => id)); totalLessons.textContent = schedule.length; totalGroups.textContent = uniqueGroups.size; totalTeachers.textContent = uniqueTeachers.size; totalClassrooms.textContent = uniqueClassrooms.size; } // Применить фильтры function applyFilters() { const filters = {}; if (courseFilter.value) filters.course = courseFilter.value; if (groupFilter.value) filters.group_id = groupFilter.value; if (teacherFilter.value) filters.teacher_id = teacherFilter.value; if (dayFilter.value) filters.day_of_week = dayFilter.value; if (weekFilter.value) filters.week_number = weekFilter.value; if (typeFilter.value) filters.lesson_type_id = typeFilter.value; loadSchedule(filters); } // Сбросить фильтры function resetFilters() { courseFilter.value = ''; groupFilter.value = ''; teacherFilter.value = ''; dayFilter.value = ''; weekFilter.value = ''; typeFilter.value = ''; loadSchedule(); } // Редактировать элемент расписания async function editScheduleItem(id) { try { showLoading(); const response = await fetch(`${API_BASE}/schedule/${id}`); const item = await response.json(); if (item) { // Заполняем форму document.getElementById('formGroup').value = item.group_id; document.getElementById('formSubject').value = item.subject_id; document.getElementById('formTeacher').value = item.teacher_id || ''; document.getElementById('formClassroom').value = item.classroom_id || ''; document.getElementById('formType').value = item.lesson_type_id; document.getElementById('formDay').value = item.day_of_week || ''; document.getElementById('formLessonNumber').value = item.lesson_number || ''; document.getElementById('formWeek').value = item.week_number || ''; document.getElementById('formDate').value = item.date || ''; const evenWeekCheckbox = document.querySelector('input[name="is_even_week"]'); const oddWeekCheckbox = document.querySelector('input[name="is_odd_week"]'); evenWeekCheckbox.checked = item.is_even_week || false; oddWeekCheckbox.checked = item.is_odd_week || true; // Изменяем заголовок модального окна document.querySelector('.modal-header h3').innerHTML = '<i class="fas fa-edit"></i> Редактировать занятие'; // Изменяем обработчик формы scheduleForm.onsubmit = async (e) => { e.preventDefault(); await updateScheduleItem(id); }; scheduleModal.style.display = 'block'; hideLoading(); } } catch (error) { console.error('Ошибка редактирования:', error); hideLoading(); } } // Обновить элемент расписания async function updateScheduleItem(id) { try { showLoading(); const formData = new FormData(scheduleForm); const data = Object.fromEntries(formData.entries()); // Преобразуем данные data.is_even_week = data.is_even_week === 'true'; data.is_odd_week = data.is_odd_week === 'true'; const response = await fetch(`${API_BASE}/schedule/${id}`, { method: 'PUT', headers: { 'Content-Type': 'application/json' }, body: JSON.stringify(data) }); if (response.ok) { alert('Расписание успешно обновлено!'); scheduleModal.style.display = 'none'; applyFilters(); } else { const error = await response.json(); throw new Error(error.error || 'Ошибка обновления'); } hideLoading(); } catch (error) { console.error('Ошибка обновления:', error); alert(`Ошибка: ${error.message}`); hideLoading(); } } // Удалить элемент расписания async function deleteScheduleItem(id) { if (!confirm('Вы уверены, что хотите удалить это занятие?')) { return; } try { showLoading(); const response = await fetch(`${API_BASE}/schedule/${id}`, { method: 'DELETE' }); if (response.ok) { alert('Занятие успешно удалено!'); applyFilters(); } else { const error = await response.json(); throw new Error(error.error || 'Ошибка удаления'); } hideLoading(); } catch (error) { console.error('Ошибка удаления:', error); alert(`Ошибка: ${error.message}`); hideLoading(); } } // Добавить новое занятие async function addNewSchedule() { // Сбрасываем форму scheduleForm.reset(); // Устанавливаем обработчик scheduleForm.onsubmit = async (e) => { e.preventDefault(); await createScheduleItem(); }; // Изменяем заголовок document.querySelector('.modal-header h3').innerHTML = '<i class="fas fa-calendar-plus"></i> Добавить занятие'; scheduleModal.style.display = 'block'; } // Создать новый элемент расписания async function createScheduleItem() { try { showLoading(); const formData = new FormData(scheduleForm); const data = Object.fromEntries(formData.entries()); // Преобразуем данные data.is_even_week = data.is_even_week === 'true'; data.is_odd_week = data.is_odd_week === 'true'; const response = await fetch(`${API_BASE}/schedule`, { method: 'POST', headers: { 'Content-Type': 'application/json' }, body: JSON.stringify(data) }); if (response.ok) { alert('Занятие успешно добавлено!'); scheduleModal.style.display = 'none'; applyFilters(); } else { const error = await response.json(); throw new Error(error.error || 'Ошибка создания'); } hideLoading(); } catch (error) { console.error('Ошибка создания:', error); alert(`Ошибка: ${error.message}`); hideLoading(); } } // Экспорт расписания async function exportSchedule() { try { showLoading(); const filters = {}; if (courseFilter.value) filters.course = courseFilter.value; if (groupFilter.value) filters.group_id = groupFilter.value; const params = new URLSearchParams(filters); const response = await fetch(`${API_BASE}/schedule?${params}`); const schedule = await response.json(); // Преобразуем в CSV const headers = ['День', 'Время', 'Группа', 'Дисциплина', 'Преподаватель', 'Аудитория', 'Тип']; const csvRows = [ headers.join(','), ...schedule.map(item => [ item.day_of_week ? ['Пн', 'Вт', 'Ср', 'Чт', 'Пт', 'Сб', 'Вс'][item.day_of_week - 1] : '', item.start_time && item.end_time ? `${item.start_time}-${item.end_time}` : '', item.group_name, item.subject_name, item.teacher_last_name ? `${item.teacher_last_name} ${item.teacher_first_name?.charAt(0)}.` : '', item.room_number ? `${item.building_name}, ауд. ${item.room_number}` : '', item.lesson_type_name ].map(value => `"${value}"`).join(',')) ]; const csvContent = csvRows.join('\n'); const blob = new Blob([csvContent], { type: 'text/csv;charset=utf-8;' }); const url = window.URL.createObjectURL(blob); const a = document.createElement('a'); a.href = url; a.download = `расписание_${new Date().toISOString().split('T')[0]}.csv`; document.body.appendChild(a); a.click(); document.body.removeChild(a); window.URL.revokeObjectURL(url); hideLoading(); } catch (error) { console.error('Ошибка экспорта:', error); alert('Не удалось экспортировать расписание'); hideLoading(); } } // Инициализация document.addEventListener('DOMContentLoaded', async () => { // Загружаем справочники и расписание await loadReferences(); await loadSchedule(); // Обработчики событий applyFiltersBtn.addEventListener('click', applyFilters); resetFiltersBtn.addEventListener('click', resetFilters); refreshBtn.addEventListener('click', () => { loadReferences(); applyFilters(); }); addScheduleBtn.addEventListener('click', addNewSchedule); viewAllBtn.addEventListener('click', resetFilters); exportBtn.addEventListener('click', exportSchedule); // Модальное окно closeModal.addEventListener('click', () => { scheduleModal.style.display = 'none'; }); cancelBtn.addEventListener('click', () => { scheduleModal.style.display = 'none'; }); window.addEventListener('click', (e) => { if (e.target === scheduleModal) { scheduleModal.style.display = 'none'; } }); // Устанавливаем текущую неделю const now = new Date(); const start = new Date(now.getFullYear(), 0, 1); const diff = now - start; const oneDay = 1000 * 60 * 60 * 24; const currentWeek = Math.floor(diff / oneDay / 7) + 1; weekFilter.value = currentWeek; }); </script> </body> </html>