From 2563c769def6e00abf5682b7b77655bb15037aaf Mon Sep 17 00:00:00 2001 From: Zuev Date: Wed, 4 Mar 2026 22:46:36 +0300 Subject: [PATCH] =?UTF-8?q?=D0=A0=D0=B5=D0=B0=D0=BB=D0=B8=D0=B7=D0=BE?= =?UTF-8?q?=D0=B2=D0=B0=D0=BD=D0=B0=20=D1=84=D0=B8=D0=BB=D1=8C=D1=82=D1=80?= =?UTF-8?q?=D0=B0=D1=86=D0=B8=D1=8F=20=D1=81=D1=82=D0=BE=D0=BB=D0=B1=D1=86?= =?UTF-8?q?=D0=BE=D0=B2=20=D0=B8=20=D1=83=D0=BB=D1=83=D1=87=D1=88=D0=B5?= =?UTF-8?q?=D0=BD=D0=B0=20=D1=81=D0=BE=D1=80=D1=82=D0=B8=D1=80=D0=BE=D0=B2?= =?UTF-8?q?=D0=BA=D0=B0=20=D0=B2=20=D1=80=D0=B0=D1=81=D0=BF=D0=B8=D1=81?= =?UTF-8?q?=D0=B0=D0=BD=D0=B8=D0=B8?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- backend/Dockerfile | 0 backend/pom.xml | 0 .../java/com/magistr/app/Application.java | 0 .../src/main/java/com/magistr/app/README.md | 0 .../com/magistr/app/config/AppConfig.java | 0 .../magistr/app/config/DataInitializer.java | 0 .../app/controller/AuthController.java | 0 .../app/controller/ClassroomController.java | 0 .../controller/EducationFormController.java | 0 .../app/controller/EquipmentController.java | 0 .../app/controller/GroupController.java | 0 .../app/controller/LessonsController.java | 0 .../app/controller/SubjectController.java | 0 .../controller/TeacherSubjectController.java | 0 .../app/controller/UserController.java | 0 .../com/magistr/app/dto/ClassroomRequest.java | 0 .../magistr/app/dto/ClassroomResponse.java | 0 .../magistr/app/dto/CreateGroupRequest.java | 0 .../magistr/app/dto/CreateLessonRequest.java | 0 .../magistr/app/dto/CreateUserRequest.java | 0 .../com/magistr/app/dto/GroupResponse.java | 0 .../com/magistr/app/dto/LessonResponse.java | 0 .../com/magistr/app/dto/LoginRequest.java | 0 .../com/magistr/app/dto/LoginResponse.java | 0 .../app/dto/TeacherSubjectResponse.java | 0 .../com/magistr/app/dto/UserResponse.java | 0 .../java/com/magistr/app/model/Classroom.java | 0 .../com/magistr/app/model/EducationForm.java | 0 .../java/com/magistr/app/model/Equipment.java | 0 .../java/com/magistr/app/model/Lesson.java | 0 .../main/java/com/magistr/app/model/Role.java | 0 .../com/magistr/app/model/StudentGroup.java | 0 .../java/com/magistr/app/model/Subject.java | 0 .../com/magistr/app/model/TeacherSubject.java | 0 .../magistr/app/model/TeacherSubjectId.java | 0 .../main/java/com/magistr/app/model/User.java | 0 .../app/repository/ClassroomRepository.java | 0 .../repository/EducationFormRepository.java | 0 .../app/repository/EquipmentRepository.java | 0 .../app/repository/GroupRepository.java | 0 .../app/repository/LessonRepository.java | 0 .../app/repository/SubjectRepository.java | 0 .../repository/TeacherSubjectRepository.java | 0 .../app/repository/UserRepository.java | 0 .../app/utils/DayAndWeekValidator.java | 0 .../src/main/resources/application.properties | 0 compose.yaml | 0 db/init/init.sql | 0 frontend/.dockerignore | 0 frontend/Dockerfile | 0 frontend/admin/css/components.css | 232 ++++++++++++- frontend/admin/css/layout.css | 0 frontend/admin/css/main.css | 0 frontend/admin/index.html | 0 frontend/admin/js/api.js | 0 frontend/admin/js/main.js | 0 frontend/admin/js/utils.js | 0 frontend/admin/js/views/classrooms.js | 0 frontend/admin/js/views/edu-forms.js | 0 frontend/admin/js/views/equipments.js | 0 frontend/admin/js/views/groups.js | 0 frontend/admin/js/views/schedule.js | 324 +++++++++++++++++- frontend/admin/js/views/subjects.js | 0 frontend/admin/js/views/users.js | 0 frontend/admin/views/classrooms.html | 0 frontend/admin/views/edu-forms.html | 0 frontend/admin/views/equipments.html | 0 frontend/admin/views/groups.html | 0 frontend/admin/views/schedule.html | 36 +- frontend/admin/views/subjects.html | 0 frontend/admin/views/users.html | 0 frontend/index.html | 0 frontend/script.js | 0 frontend/student/index.html | 0 frontend/style.css | 0 frontend/teacher/index.html | 0 frontend/theme-toggle.js | 0 77 files changed, 569 insertions(+), 23 deletions(-) mode change 100644 => 100755 backend/Dockerfile mode change 100644 => 100755 backend/pom.xml mode change 100644 => 100755 backend/src/main/java/com/magistr/app/Application.java mode change 100644 => 100755 backend/src/main/java/com/magistr/app/README.md mode change 100644 => 100755 backend/src/main/java/com/magistr/app/config/AppConfig.java mode change 100644 => 100755 backend/src/main/java/com/magistr/app/config/DataInitializer.java mode change 100644 => 100755 backend/src/main/java/com/magistr/app/controller/AuthController.java mode change 100644 => 100755 backend/src/main/java/com/magistr/app/controller/ClassroomController.java mode change 100644 => 100755 backend/src/main/java/com/magistr/app/controller/EducationFormController.java mode change 100644 => 100755 backend/src/main/java/com/magistr/app/controller/EquipmentController.java mode change 100644 => 100755 backend/src/main/java/com/magistr/app/controller/GroupController.java mode change 100644 => 100755 backend/src/main/java/com/magistr/app/controller/LessonsController.java mode change 100644 => 100755 backend/src/main/java/com/magistr/app/controller/SubjectController.java mode change 100644 => 100755 backend/src/main/java/com/magistr/app/controller/TeacherSubjectController.java mode change 100644 => 100755 backend/src/main/java/com/magistr/app/controller/UserController.java mode change 100644 => 100755 backend/src/main/java/com/magistr/app/dto/ClassroomRequest.java mode change 100644 => 100755 backend/src/main/java/com/magistr/app/dto/ClassroomResponse.java mode change 100644 => 100755 backend/src/main/java/com/magistr/app/dto/CreateGroupRequest.java mode change 100644 => 100755 backend/src/main/java/com/magistr/app/dto/CreateLessonRequest.java mode change 100644 => 100755 backend/src/main/java/com/magistr/app/dto/CreateUserRequest.java mode change 100644 => 100755 backend/src/main/java/com/magistr/app/dto/GroupResponse.java mode change 100644 => 100755 backend/src/main/java/com/magistr/app/dto/LessonResponse.java mode change 100644 => 100755 backend/src/main/java/com/magistr/app/dto/LoginRequest.java mode change 100644 => 100755 backend/src/main/java/com/magistr/app/dto/LoginResponse.java mode change 100644 => 100755 backend/src/main/java/com/magistr/app/dto/TeacherSubjectResponse.java mode change 100644 => 100755 backend/src/main/java/com/magistr/app/dto/UserResponse.java mode change 100644 => 100755 backend/src/main/java/com/magistr/app/model/Classroom.java mode change 100644 => 100755 backend/src/main/java/com/magistr/app/model/EducationForm.java mode change 100644 => 100755 backend/src/main/java/com/magistr/app/model/Equipment.java mode change 100644 => 100755 backend/src/main/java/com/magistr/app/model/Lesson.java mode change 100644 => 100755 backend/src/main/java/com/magistr/app/model/Role.java mode change 100644 => 100755 backend/src/main/java/com/magistr/app/model/StudentGroup.java mode change 100644 => 100755 backend/src/main/java/com/magistr/app/model/Subject.java mode change 100644 => 100755 backend/src/main/java/com/magistr/app/model/TeacherSubject.java mode change 100644 => 100755 backend/src/main/java/com/magistr/app/model/TeacherSubjectId.java mode change 100644 => 100755 backend/src/main/java/com/magistr/app/model/User.java mode change 100644 => 100755 backend/src/main/java/com/magistr/app/repository/ClassroomRepository.java mode change 100644 => 100755 backend/src/main/java/com/magistr/app/repository/EducationFormRepository.java mode change 100644 => 100755 backend/src/main/java/com/magistr/app/repository/EquipmentRepository.java mode change 100644 => 100755 backend/src/main/java/com/magistr/app/repository/GroupRepository.java mode change 100644 => 100755 backend/src/main/java/com/magistr/app/repository/LessonRepository.java mode change 100644 => 100755 backend/src/main/java/com/magistr/app/repository/SubjectRepository.java mode change 100644 => 100755 backend/src/main/java/com/magistr/app/repository/TeacherSubjectRepository.java mode change 100644 => 100755 backend/src/main/java/com/magistr/app/repository/UserRepository.java mode change 100644 => 100755 backend/src/main/java/com/magistr/app/utils/DayAndWeekValidator.java mode change 100644 => 100755 backend/src/main/resources/application.properties mode change 100644 => 100755 compose.yaml mode change 100644 => 100755 db/init/init.sql mode change 100644 => 100755 frontend/.dockerignore mode change 100644 => 100755 frontend/Dockerfile mode change 100644 => 100755 frontend/admin/css/components.css mode change 100644 => 100755 frontend/admin/css/layout.css mode change 100644 => 100755 frontend/admin/css/main.css mode change 100644 => 100755 frontend/admin/index.html mode change 100644 => 100755 frontend/admin/js/api.js mode change 100644 => 100755 frontend/admin/js/main.js mode change 100644 => 100755 frontend/admin/js/utils.js mode change 100644 => 100755 frontend/admin/js/views/classrooms.js mode change 100644 => 100755 frontend/admin/js/views/edu-forms.js mode change 100644 => 100755 frontend/admin/js/views/equipments.js mode change 100644 => 100755 frontend/admin/js/views/groups.js mode change 100644 => 100755 frontend/admin/js/views/schedule.js mode change 100644 => 100755 frontend/admin/js/views/subjects.js mode change 100644 => 100755 frontend/admin/js/views/users.js mode change 100644 => 100755 frontend/admin/views/classrooms.html mode change 100644 => 100755 frontend/admin/views/edu-forms.html mode change 100644 => 100755 frontend/admin/views/equipments.html mode change 100644 => 100755 frontend/admin/views/groups.html mode change 100644 => 100755 frontend/admin/views/schedule.html mode change 100644 => 100755 frontend/admin/views/subjects.html mode change 100644 => 100755 frontend/admin/views/users.html mode change 100644 => 100755 frontend/index.html mode change 100644 => 100755 frontend/script.js mode change 100644 => 100755 frontend/student/index.html mode change 100644 => 100755 frontend/style.css mode change 100644 => 100755 frontend/teacher/index.html mode change 100644 => 100755 frontend/theme-toggle.js diff --git a/backend/Dockerfile b/backend/Dockerfile old mode 100644 new mode 100755 diff --git a/backend/pom.xml b/backend/pom.xml old mode 100644 new mode 100755 diff --git a/backend/src/main/java/com/magistr/app/Application.java b/backend/src/main/java/com/magistr/app/Application.java old mode 100644 new mode 100755 diff --git a/backend/src/main/java/com/magistr/app/README.md b/backend/src/main/java/com/magistr/app/README.md old mode 100644 new mode 100755 diff --git a/backend/src/main/java/com/magistr/app/config/AppConfig.java b/backend/src/main/java/com/magistr/app/config/AppConfig.java old mode 100644 new mode 100755 diff --git a/backend/src/main/java/com/magistr/app/config/DataInitializer.java b/backend/src/main/java/com/magistr/app/config/DataInitializer.java old mode 100644 new mode 100755 diff --git a/backend/src/main/java/com/magistr/app/controller/AuthController.java b/backend/src/main/java/com/magistr/app/controller/AuthController.java old mode 100644 new mode 100755 diff --git a/backend/src/main/java/com/magistr/app/controller/ClassroomController.java b/backend/src/main/java/com/magistr/app/controller/ClassroomController.java old mode 100644 new mode 100755 diff --git a/backend/src/main/java/com/magistr/app/controller/EducationFormController.java b/backend/src/main/java/com/magistr/app/controller/EducationFormController.java old mode 100644 new mode 100755 diff --git a/backend/src/main/java/com/magistr/app/controller/EquipmentController.java b/backend/src/main/java/com/magistr/app/controller/EquipmentController.java old mode 100644 new mode 100755 diff --git a/backend/src/main/java/com/magistr/app/controller/GroupController.java b/backend/src/main/java/com/magistr/app/controller/GroupController.java old mode 100644 new mode 100755 diff --git a/backend/src/main/java/com/magistr/app/controller/LessonsController.java b/backend/src/main/java/com/magistr/app/controller/LessonsController.java old mode 100644 new mode 100755 diff --git a/backend/src/main/java/com/magistr/app/controller/SubjectController.java b/backend/src/main/java/com/magistr/app/controller/SubjectController.java old mode 100644 new mode 100755 diff --git a/backend/src/main/java/com/magistr/app/controller/TeacherSubjectController.java b/backend/src/main/java/com/magistr/app/controller/TeacherSubjectController.java old mode 100644 new mode 100755 diff --git a/backend/src/main/java/com/magistr/app/controller/UserController.java b/backend/src/main/java/com/magistr/app/controller/UserController.java old mode 100644 new mode 100755 diff --git a/backend/src/main/java/com/magistr/app/dto/ClassroomRequest.java b/backend/src/main/java/com/magistr/app/dto/ClassroomRequest.java old mode 100644 new mode 100755 diff --git a/backend/src/main/java/com/magistr/app/dto/ClassroomResponse.java b/backend/src/main/java/com/magistr/app/dto/ClassroomResponse.java old mode 100644 new mode 100755 diff --git a/backend/src/main/java/com/magistr/app/dto/CreateGroupRequest.java b/backend/src/main/java/com/magistr/app/dto/CreateGroupRequest.java old mode 100644 new mode 100755 diff --git a/backend/src/main/java/com/magistr/app/dto/CreateLessonRequest.java b/backend/src/main/java/com/magistr/app/dto/CreateLessonRequest.java old mode 100644 new mode 100755 diff --git a/backend/src/main/java/com/magistr/app/dto/CreateUserRequest.java b/backend/src/main/java/com/magistr/app/dto/CreateUserRequest.java old mode 100644 new mode 100755 diff --git a/backend/src/main/java/com/magistr/app/dto/GroupResponse.java b/backend/src/main/java/com/magistr/app/dto/GroupResponse.java old mode 100644 new mode 100755 diff --git a/backend/src/main/java/com/magistr/app/dto/LessonResponse.java b/backend/src/main/java/com/magistr/app/dto/LessonResponse.java old mode 100644 new mode 100755 diff --git a/backend/src/main/java/com/magistr/app/dto/LoginRequest.java b/backend/src/main/java/com/magistr/app/dto/LoginRequest.java old mode 100644 new mode 100755 diff --git a/backend/src/main/java/com/magistr/app/dto/LoginResponse.java b/backend/src/main/java/com/magistr/app/dto/LoginResponse.java old mode 100644 new mode 100755 diff --git a/backend/src/main/java/com/magistr/app/dto/TeacherSubjectResponse.java b/backend/src/main/java/com/magistr/app/dto/TeacherSubjectResponse.java old mode 100644 new mode 100755 diff --git a/backend/src/main/java/com/magistr/app/dto/UserResponse.java b/backend/src/main/java/com/magistr/app/dto/UserResponse.java old mode 100644 new mode 100755 diff --git a/backend/src/main/java/com/magistr/app/model/Classroom.java b/backend/src/main/java/com/magistr/app/model/Classroom.java old mode 100644 new mode 100755 diff --git a/backend/src/main/java/com/magistr/app/model/EducationForm.java b/backend/src/main/java/com/magistr/app/model/EducationForm.java old mode 100644 new mode 100755 diff --git a/backend/src/main/java/com/magistr/app/model/Equipment.java b/backend/src/main/java/com/magistr/app/model/Equipment.java old mode 100644 new mode 100755 diff --git a/backend/src/main/java/com/magistr/app/model/Lesson.java b/backend/src/main/java/com/magistr/app/model/Lesson.java old mode 100644 new mode 100755 diff --git a/backend/src/main/java/com/magistr/app/model/Role.java b/backend/src/main/java/com/magistr/app/model/Role.java old mode 100644 new mode 100755 diff --git a/backend/src/main/java/com/magistr/app/model/StudentGroup.java b/backend/src/main/java/com/magistr/app/model/StudentGroup.java old mode 100644 new mode 100755 diff --git a/backend/src/main/java/com/magistr/app/model/Subject.java b/backend/src/main/java/com/magistr/app/model/Subject.java old mode 100644 new mode 100755 diff --git a/backend/src/main/java/com/magistr/app/model/TeacherSubject.java b/backend/src/main/java/com/magistr/app/model/TeacherSubject.java old mode 100644 new mode 100755 diff --git a/backend/src/main/java/com/magistr/app/model/TeacherSubjectId.java b/backend/src/main/java/com/magistr/app/model/TeacherSubjectId.java old mode 100644 new mode 100755 diff --git a/backend/src/main/java/com/magistr/app/model/User.java b/backend/src/main/java/com/magistr/app/model/User.java old mode 100644 new mode 100755 diff --git a/backend/src/main/java/com/magistr/app/repository/ClassroomRepository.java b/backend/src/main/java/com/magistr/app/repository/ClassroomRepository.java old mode 100644 new mode 100755 diff --git a/backend/src/main/java/com/magistr/app/repository/EducationFormRepository.java b/backend/src/main/java/com/magistr/app/repository/EducationFormRepository.java old mode 100644 new mode 100755 diff --git a/backend/src/main/java/com/magistr/app/repository/EquipmentRepository.java b/backend/src/main/java/com/magistr/app/repository/EquipmentRepository.java old mode 100644 new mode 100755 diff --git a/backend/src/main/java/com/magistr/app/repository/GroupRepository.java b/backend/src/main/java/com/magistr/app/repository/GroupRepository.java old mode 100644 new mode 100755 diff --git a/backend/src/main/java/com/magistr/app/repository/LessonRepository.java b/backend/src/main/java/com/magistr/app/repository/LessonRepository.java old mode 100644 new mode 100755 diff --git a/backend/src/main/java/com/magistr/app/repository/SubjectRepository.java b/backend/src/main/java/com/magistr/app/repository/SubjectRepository.java old mode 100644 new mode 100755 diff --git a/backend/src/main/java/com/magistr/app/repository/TeacherSubjectRepository.java b/backend/src/main/java/com/magistr/app/repository/TeacherSubjectRepository.java old mode 100644 new mode 100755 diff --git a/backend/src/main/java/com/magistr/app/repository/UserRepository.java b/backend/src/main/java/com/magistr/app/repository/UserRepository.java old mode 100644 new mode 100755 diff --git a/backend/src/main/java/com/magistr/app/utils/DayAndWeekValidator.java b/backend/src/main/java/com/magistr/app/utils/DayAndWeekValidator.java old mode 100644 new mode 100755 diff --git a/backend/src/main/resources/application.properties b/backend/src/main/resources/application.properties old mode 100644 new mode 100755 diff --git a/compose.yaml b/compose.yaml old mode 100644 new mode 100755 diff --git a/db/init/init.sql b/db/init/init.sql old mode 100644 new mode 100755 diff --git a/frontend/.dockerignore b/frontend/.dockerignore old mode 100644 new mode 100755 diff --git a/frontend/Dockerfile b/frontend/Dockerfile old mode 100644 new mode 100755 diff --git a/frontend/admin/css/components.css b/frontend/admin/css/components.css old mode 100644 new mode 100755 index 34af449..e579611 --- a/frontend/admin/css/components.css +++ b/frontend/admin/css/components.css @@ -432,6 +432,230 @@ thead th { border-bottom: 1px solid var(--bg-card-border); } +/* Sortable columns */ +thead th.sortable { + cursor: pointer; + user-select: none; + white-space: nowrap; + transition: color 0.2s ease; +} + +thead th.sortable:hover { + color: var(--accent); +} + +thead th.sortable.sort-active { + color: var(--accent); +} + +.sort-arrow { + display: inline-block; + width: 0; + height: 0; + margin-left: 5px; + vertical-align: middle; + border-left: 4px solid transparent; + border-right: 4px solid transparent; + border-bottom: 5px solid currentColor; + opacity: 0.3; + transition: transform 0.25s ease, opacity 0.25s ease; +} + +thead th.sortable:hover .sort-arrow { + opacity: 0.6; +} + +thead th.sortable.sort-asc .sort-arrow { + opacity: 1; + border-bottom: 5px solid var(--accent); + transform: rotate(0deg); +} + +thead th.sortable.sort-desc .sort-arrow { + opacity: 1; + border-bottom: 5px solid var(--accent); + transform: rotate(180deg); +} + +/* ===== Filter Icon & Popup ===== */ +thead th.filterable { + cursor: pointer; + user-select: none; + white-space: nowrap; + transition: color 0.2s ease; +} + +thead th.filterable:hover { + color: var(--accent); +} + +.filter-icon { + display: inline-block; + margin-left: 4px; + font-size: 0.65rem; + opacity: 0.4; + cursor: pointer; + transition: opacity 0.2s ease, color 0.2s ease; + vertical-align: middle; +} + +thead th.filterable:hover .filter-icon { + opacity: 0.8; +} + +thead th.filter-active .filter-icon { + opacity: 1; + color: var(--accent); +} + +thead th.filter-active { + color: var(--accent); +} + +.filter-popup { + position: absolute; + top: 100%; + left: 0; + min-width: 220px; + max-width: 280px; + background: rgba(15, 23, 42, 0.97); + backdrop-filter: blur(16px); + -webkit-backdrop-filter: blur(16px); + border: 1px solid var(--bg-card-border); + border-radius: var(--radius-sm); + box-shadow: 0 12px 32px rgba(0, 0, 0, 0.45); + padding: 0.75rem; + z-index: 200; + animation: filterPopupIn 0.2s ease-out both; + text-transform: none; +} + +[data-theme="light"] .filter-popup { + background: rgba(255, 255, 255, 0.98); + box-shadow: 0 12px 32px rgba(0, 0, 0, 0.15); +} + +@keyframes filterPopupIn { + from { + opacity: 0; + transform: translateY(-6px); + } + + to { + opacity: 1; + transform: translateY(0); + } +} + +.filter-search { + width: 100%; + padding: 0.5rem 0.7rem; + background: var(--bg-input); + border: 1px solid var(--bg-card-border); + border-radius: 6px; + color: var(--text-primary); + font-family: inherit; + font-size: 0.85rem; + outline: none; + transition: border-color 0.2s ease, box-shadow 0.2s ease; + margin-bottom: 0.5rem; +} + +.filter-search::placeholder { + color: var(--text-placeholder); +} + +.filter-search:focus { + border-color: var(--accent); + box-shadow: 0 0 0 3px var(--accent-glow); +} + +.filter-btn-row { + display: flex; + gap: 0.4rem; + margin-bottom: 0.5rem; +} + +.filter-btn-action { + flex: 1; + padding: 0.3rem 0.5rem; + background: var(--bg-input); + border: 1px solid var(--bg-card-border); + border-radius: 6px; + color: var(--text-secondary); + font-family: inherit; + font-size: 0.75rem; + cursor: pointer; + transition: all 0.2s ease; +} + +.filter-btn-action:hover { + background: var(--bg-hover); + color: var(--text-primary); + border-color: var(--accent); +} + +.filter-list { + max-height: 180px; + overflow-y: auto; + margin-bottom: 0.5rem; + scrollbar-width: thin; + scrollbar-color: var(--bg-card-border) transparent; +} + +.filter-list::-webkit-scrollbar { + width: 4px; +} + +.filter-list::-webkit-scrollbar-thumb { + background: var(--bg-card-border); + border-radius: 2px; +} + +.filter-item { + display: flex; + align-items: center; + gap: 0.5rem; + padding: 0.35rem 0.3rem; + border-radius: 4px; + cursor: pointer; + font-size: 0.85rem; + color: var(--text-primary); + transition: background 0.15s ease; +} + +.filter-item:hover { + background: var(--bg-hover); +} + +.filter-item input[type="checkbox"] { + cursor: pointer; + width: 1rem; + height: 1rem; + accent-color: var(--accent); + flex-shrink: 0; +} + +.filter-btn-apply { + width: 100%; + padding: 0.5rem; + background: linear-gradient(135deg, var(--accent), var(--accent-secondary)); + border: none; + border-radius: 6px; + color: #fff; + font-family: inherit; + font-size: 0.82rem; + font-weight: 600; + cursor: pointer; + transition: transform 0.2s ease, box-shadow 0.2s ease; + box-shadow: 0 3px 10px var(--accent-glow); +} + +.filter-btn-apply:hover { + transform: translateY(-1px); + box-shadow: 0 5px 15px var(--accent-glow); +} + tbody td { padding: 0.85rem 1rem; font-size: 0.95rem; @@ -610,12 +834,14 @@ tbody tr:hover { 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; @@ -626,8 +852,10 @@ tbody tr:hover { transition: all var(--transition); user-select: none; } -.btn-checkbox input:checked + .checkbox-btn { - background: var(--success, #10b981); /* используем success или зелёный */ + +.btn-checkbox input:checked+.checkbox-btn { + background: var(--success, #10b981); + /* используем success или зелёный */ border-color: var(--success, #10b981); color: white; } \ No newline at end of file diff --git a/frontend/admin/css/layout.css b/frontend/admin/css/layout.css old mode 100644 new mode 100755 diff --git a/frontend/admin/css/main.css b/frontend/admin/css/main.css old mode 100644 new mode 100755 diff --git a/frontend/admin/index.html b/frontend/admin/index.html old mode 100644 new mode 100755 diff --git a/frontend/admin/js/api.js b/frontend/admin/js/api.js old mode 100644 new mode 100755 diff --git a/frontend/admin/js/main.js b/frontend/admin/js/main.js old mode 100644 new mode 100755 diff --git a/frontend/admin/js/utils.js b/frontend/admin/js/utils.js old mode 100644 new mode 100755 diff --git a/frontend/admin/js/views/classrooms.js b/frontend/admin/js/views/classrooms.js old mode 100644 new mode 100755 diff --git a/frontend/admin/js/views/edu-forms.js b/frontend/admin/js/views/edu-forms.js old mode 100644 new mode 100755 diff --git a/frontend/admin/js/views/equipments.js b/frontend/admin/js/views/equipments.js old mode 100644 new mode 100755 diff --git a/frontend/admin/js/views/groups.js b/frontend/admin/js/views/groups.js old mode 100644 new mode 100755 diff --git a/frontend/admin/js/views/schedule.js b/frontend/admin/js/views/schedule.js old mode 100644 new mode 100755 index 273cf02..5312a3b --- a/frontend/admin/js/views/schedule.js +++ b/frontend/admin/js/views/schedule.js @@ -3,28 +3,336 @@ import { escapeHtml } from '../utils.js'; export async function initSchedule() { const tbody = document.getElementById('schedule-tbody'); + const table = document.getElementById('schedule-table'); + + let lessonsData = []; + let sortKey = null; + let sortDir = 'asc'; + + // Активные фильтры: { teacher: Set, group: Set, subject: Set, day: Set } + const activeFilters = {}; + + // Маппинг дней недели для корректной сортировки + const dayOrder = { + 'понедельник': 1, 'вторник': 2, 'среда': 3, + 'четверг': 4, 'пятница': 5, 'суббота': 6, 'воскресенье': 7 + }; + + // ===================== Фильтрация ===================== + + // Извлечение отображаемого значения поля для фильтрации + function getDisplayValue(lesson, key) { + switch (key) { + case 'teacher': + return lesson.teacher?.username || lesson.teacherName || '—'; + case 'group': + return lesson.group?.name || lesson.groupName || '—'; + case 'subject': + return lesson.subject?.name || lesson.subjectName || '—'; + case 'day': + return lesson.day || '—'; + case 'educationForm': + return lesson.educationForm?.name || lesson.educationFormName || '—'; + default: + return ''; + } + } + + // Собрать уникальные значения из данных + function getUniqueValues(key) { + const vals = new Set(); + lessonsData.forEach(lesson => { + vals.add(getDisplayValue(lesson, key)); + }); + // Для дней — сортируем по порядку + if (key === 'day') { + return [...vals].sort((a, b) => (dayOrder[a.toLowerCase()] ?? 99) - (dayOrder[b.toLowerCase()] ?? 99)); + } + return [...vals].sort((a, b) => a.localeCompare(b, 'ru')); + } + + // Применить все фильтры + function applyFilters(lessons) { + return lessons.filter(lesson => { + for (const key of Object.keys(activeFilters)) { + const filterSet = activeFilters[key]; + if (filterSet && filterSet.size > 0) { + const val = getDisplayValue(lesson, key); + if (!filterSet.has(val)) return false; + } + } + return true; + }); + } + + // ===================== Попап фильтра ===================== + + let currentPopup = null; + + function closePopup() { + if (currentPopup) { + currentPopup.remove(); + currentPopup = null; + } + document.removeEventListener('click', onDocumentClick, true); + } + + function onDocumentClick(e) { + if (currentPopup && !currentPopup.contains(e.target)) { + // Проверяем, не кликнули ли по иконке фильтра + if (!e.target.closest('.filter-icon')) { + closePopup(); + } + } + } + + function openFilterPopup(th, filterKey) { + // Если уже открыт этот же — закрыть + if (currentPopup && currentPopup.dataset.filterKey === filterKey) { + closePopup(); + return; + } + closePopup(); + + const uniqueValues = getUniqueValues(filterKey); + const currentFilter = activeFilters[filterKey]; + + // Создаём попап + const popup = document.createElement('div'); + popup.className = 'filter-popup'; + popup.dataset.filterKey = filterKey; + + // Поисковое поле + const searchInput = document.createElement('input'); + searchInput.type = 'text'; + searchInput.className = 'filter-search'; + searchInput.placeholder = 'Поиск...'; + popup.appendChild(searchInput); + + // Кнопки «Выбрать все» / «Сбросить» + const btnRow = document.createElement('div'); + btnRow.className = 'filter-btn-row'; + + const btnAll = document.createElement('button'); + btnAll.className = 'filter-btn-action'; + btnAll.textContent = 'Все'; + btnAll.addEventListener('click', (e) => { + e.stopPropagation(); + checkboxes.forEach(cb => { cb.checked = true; }); + }); + + const btnNone = document.createElement('button'); + btnNone.className = 'filter-btn-action'; + btnNone.textContent = 'Сбросить'; + btnNone.addEventListener('click', (e) => { + e.stopPropagation(); + checkboxes.forEach(cb => { cb.checked = false; }); + }); + + btnRow.appendChild(btnAll); + btnRow.appendChild(btnNone); + popup.appendChild(btnRow); + + // Список чекбоксов + const listWrap = document.createElement('div'); + listWrap.className = 'filter-list'; + + const checkboxes = []; + + uniqueValues.forEach(val => { + const label = document.createElement('label'); + label.className = 'filter-item'; + + const cb = document.createElement('input'); + cb.type = 'checkbox'; + cb.value = val; + // Если фильтр активен — отмечаем только выбранные; если нет — все отмечены + cb.checked = currentFilter ? currentFilter.has(val) : true; + + const span = document.createElement('span'); + span.textContent = val; + + label.appendChild(cb); + label.appendChild(span); + listWrap.appendChild(label); + checkboxes.push(cb); + }); + + popup.appendChild(listWrap); + + // Кнопка «Применить» + const btnApply = document.createElement('button'); + btnApply.className = 'filter-btn-apply'; + btnApply.textContent = 'Применить'; + btnApply.addEventListener('click', (e) => { + e.stopPropagation(); + const selected = new Set(); + checkboxes.forEach(cb => { + if (cb.checked) selected.add(cb.value); + }); + + // Если все выбраны — снимаем фильтр + if (selected.size === uniqueValues.length) { + delete activeFilters[filterKey]; + th.classList.remove('filter-active'); + } else { + activeFilters[filterKey] = selected; + th.classList.add('filter-active'); + } + + closePopup(); + renderSchedule(lessonsData); + }); + popup.appendChild(btnApply); + + // Поиск по чекбоксам + searchInput.addEventListener('input', () => { + const query = searchInput.value.toLowerCase(); + listWrap.querySelectorAll('.filter-item').forEach(item => { + const text = item.querySelector('span').textContent.toLowerCase(); + item.style.display = text.includes(query) ? '' : 'none'; + }); + }); + + // Предотвращаем всплытие кликов внутри попапа (чтобы не срабатывала сортировка th) + popup.addEventListener('click', (e) => e.stopPropagation()); + searchInput.addEventListener('click', (e) => e.stopPropagation()); + + // Позиционируем попап под th + th.style.position = 'relative'; + th.appendChild(popup); + currentPopup = popup; + + // Фокус на поиск + setTimeout(() => searchInput.focus(), 50); + + // Закрытие по клику вне + setTimeout(() => { + document.addEventListener('click', onDocumentClick, true); + }, 10); + } + + // Обработчики кликов по заголовкам с фильтрами (клик по всей ячейке) + table.querySelectorAll('thead th.filterable').forEach(th => { + th.addEventListener('click', (e) => { + // Не открываем попап при клике внутри самого попапа + if (e.target.closest('.filter-popup')) return; + const filterKey = th.dataset.filterKey; + openFilterPopup(th, filterKey); + }); + }); + + // ===================== Сортировка ===================== + + function getSortValue(lesson, key) { + switch (key) { + case 'id': + return lesson.id ?? 0; + case 'teacher': + return (lesson.teacher?.username || lesson.teacherName || '').toLowerCase(); + case 'group': + return (lesson.group?.name || lesson.groupName || '').toLowerCase(); + case 'educationForm': + return (lesson.educationForm?.name || lesson.educationFormName || '').toLowerCase(); + case 'subject': + return (lesson.subject?.name || lesson.subjectName || '').toLowerCase(); + case 'day': { + const d = (lesson.day || '').toLowerCase(); + return dayOrder[d] ?? 99; + } + case 'week': + return (lesson.week || '').toLowerCase(); + case 'time': { + // Составной ключ: день + время для правильной сортировки + const d = (lesson.day || '').toLowerCase(); + const dayNum = dayOrder[d] ?? 99; + const t = lesson.time || '99:99'; + return String(dayNum).padStart(2, '0') + '_' + t; + } + default: + return ''; + } + } + + function sortLessons(lessons) { + if (!sortKey) return lessons; + + return [...lessons].sort((a, b) => { + let va = getSortValue(a, sortKey); + let vb = getSortValue(b, sortKey); + + if (typeof va === 'number' && typeof vb === 'number') { + return sortDir === 'asc' ? va - vb : vb - va; + } + + va = String(va); + vb = String(vb); + const cmp = va.localeCompare(vb, 'ru'); + return sortDir === 'asc' ? cmp : -cmp; + }); + } + + function updateSortHeaders() { + table.querySelectorAll('thead th.sortable').forEach(th => { + th.classList.remove('sort-asc', 'sort-desc', 'sort-active'); + if (th.dataset.sortKey === sortKey) { + th.classList.add('sort-active', sortDir === 'asc' ? 'sort-asc' : 'sort-desc'); + } + }); + } + + // Навешиваем обработчики клика на заголовки (сортировка) + table.querySelectorAll('thead th.sortable').forEach(th => { + th.addEventListener('click', (e) => { + // Не сортируем, если кликнули по иконке фильтра или внутри попапа + if (e.target.closest('.filter-icon') || e.target.closest('.filter-popup')) return; + + const key = th.dataset.sortKey; + if (sortKey === key) { + if (sortDir === 'asc') { + sortDir = 'desc'; + } else { + sortKey = null; + sortDir = 'asc'; + } + } else { + sortKey = key; + sortDir = 'asc'; + } + updateSortHeaders(); + renderSchedule(lessonsData); + }); + }); + + // ===================== Загрузка и рендер ===================== async function loadSchedule() { try { - // Предполагается, что на сервере есть endpoint GET /api/lessons, - // возвращающий массив объектов с полями: - // id, teacher (объект с username), group (объект с name), - // subject (объект с name), day, week, time. const lessons = await api.get('/api/users/lessons'); + lessonsData = lessons; renderSchedule(lessons); } catch (e) { - tbody.innerHTML = `Ошибка загрузки: ${escapeHtml(e.message)}`; + tbody.innerHTML = `Ошибка загрузки: ${escapeHtml(e.message)}`; } } function renderSchedule(lessons) { if (!lessons || !lessons.length) { - tbody.innerHTML = 'Нет занятий'; + tbody.innerHTML = 'Нет занятий'; return; } - tbody.innerHTML = lessons.map(lesson => { - // Извлекаем имена из вложенных объектов или используем запасные поля + // Сначала фильтруем, потом сортируем + const filtered = applyFilters(lessons); + + if (!filtered.length) { + tbody.innerHTML = 'Нет занятий по выбранным фильтрам'; + return; + } + + const sorted = sortLessons(filtered); + + tbody.innerHTML = sorted.map(lesson => { const teacherName = lesson.teacher?.username || lesson.teacherName || '—'; const groupName = lesson.group?.name || lesson.groupName || '—'; const educationForm = lesson.educationForm?.name || lesson.educationFormName || '-'; diff --git a/frontend/admin/js/views/subjects.js b/frontend/admin/js/views/subjects.js old mode 100644 new mode 100755 diff --git a/frontend/admin/js/views/users.js b/frontend/admin/js/views/users.js old mode 100644 new mode 100755 diff --git a/frontend/admin/views/classrooms.html b/frontend/admin/views/classrooms.html old mode 100644 new mode 100755 diff --git a/frontend/admin/views/edu-forms.html b/frontend/admin/views/edu-forms.html old mode 100644 new mode 100755 diff --git a/frontend/admin/views/equipments.html b/frontend/admin/views/equipments.html old mode 100644 new mode 100755 diff --git a/frontend/admin/views/groups.html b/frontend/admin/views/groups.html old mode 100644 new mode 100755 diff --git a/frontend/admin/views/schedule.html b/frontend/admin/views/schedule.html old mode 100644 new mode 100755 index 06df00a..1ee1ce0 --- a/frontend/admin/views/schedule.html +++ b/frontend/admin/views/schedule.html @@ -3,21 +3,31 @@
- - - - - - - - - - + + + + + + + + + + - - - + + +
IDПреподавательГруппаФорма обученияДисциплинаДень неделиНеделяВремя
ID + Преподаватель + + Группа + + Форма обучения + + Дисциплина + + День недели + НеделяВремя
Загрузка...
Загрузка...
diff --git a/frontend/admin/views/subjects.html b/frontend/admin/views/subjects.html old mode 100644 new mode 100755 diff --git a/frontend/admin/views/users.html b/frontend/admin/views/users.html old mode 100644 new mode 100755 diff --git a/frontend/index.html b/frontend/index.html old mode 100644 new mode 100755 diff --git a/frontend/script.js b/frontend/script.js old mode 100644 new mode 100755 diff --git a/frontend/student/index.html b/frontend/student/index.html old mode 100644 new mode 100755 diff --git a/frontend/style.css b/frontend/style.css old mode 100644 new mode 100755 diff --git a/frontend/teacher/index.html b/frontend/teacher/index.html old mode 100644 new mode 100755 diff --git a/frontend/theme-toggle.js b/frontend/theme-toggle.js old mode 100644 new mode 100755