Compare commits

5 Commits

16 changed files with 753 additions and 102 deletions

View File

@@ -0,0 +1,240 @@
package com.magistr.app.controller;
import com.magistr.app.dto.CreateLessonRequest;
import com.magistr.app.dto.LessonResponse;
import com.magistr.app.model.*;
import com.magistr.app.repository.*;
import com.magistr.app.utils.DayAndWeekValidator;
import org.slf4j.LoggerFactory;
import org.springframework.http.ResponseEntity;
import org.springframework.web.bind.annotation.*;
import java.util.*;
import org.slf4j.Logger;
import org.springframework.http.HttpStatus;
@RestController
@RequestMapping("/api/users/lessons")
public class LessonsController {
private static final Logger logger = LoggerFactory.getLogger(LessonsController.class);
private final LessonRepository lessonRepository;
private final UserRepository teacherRepository;
private final GroupRepository groupRepository;
private final SubjectRepository subjectRepository;
private final EducationFormRepository educationFormRepository;
public LessonsController(LessonRepository lessonRepository, UserRepository teacherRepository, GroupRepository groupRepository, SubjectRepository subjectRepository, EducationFormRepository educationForm) {
this.lessonRepository = lessonRepository;
this.teacherRepository = teacherRepository;
this.groupRepository = groupRepository;
this.subjectRepository = subjectRepository;
this.educationFormRepository = educationForm;
}
@PostMapping("/create")
public ResponseEntity<?> createLesson(@RequestBody CreateLessonRequest request) {
//Полное логирование входящего запроса
logger.info("Получен запрос на создание занятия: teacherId={}, groupId={}, lessonTypeId={}, day={}, week={}, time={}",
request.getTeacherId(), request.getGroupId(), request.getSubjectId(), request.getDay(), request.getWeek(), request.getTime());
//Проверка teacherId
if (request.getTeacherId() == null || request.getTeacherId() == 0) {
String errorMessage = "ID преподавателя обязателен";
logger.info("Ошибка валидации: {}", errorMessage);
return ResponseEntity.badRequest().body(Map.of("message", errorMessage));
}
//Проверка groupId
if (request.getGroupId() == null || request.getGroupId() == 0) {
String errorMessage = "ID группы обязателен";
logger.info("Ошибка валидации: {}", errorMessage);
return ResponseEntity.badRequest().body(Map.of("message", errorMessage));
}
//Проверка lessonTypeId
if (request.getSubjectId() == null || request.getSubjectId() == 0) {
String errorMessage = "ID предмета обязателен";
logger.info("Ошибка валидации: {}", errorMessage);
return ResponseEntity.badRequest().body(Map.of("message", errorMessage));
}
//Проверка day
if (request.getDay() == null || request.getDay().isBlank()) {
String errorMessage = "Выбор дня обязателен";
logger.info("Ошибка валидации: {}", errorMessage);
return ResponseEntity.badRequest().body(Map.of("message", errorMessage));
} else if(!DayAndWeekValidator.isValidDay(request.getDay())){
String errorMessage = "Некорректный день недели. " + DayAndWeekValidator.getValidDaysMessage();
logger.info("Ошибка валидации дня: '{}' - {}", request.getDay(), errorMessage);
return ResponseEntity.badRequest().body(Map.of("message", errorMessage));
}
//Проверка week
if (request.getWeek() == null || request.getWeek().isBlank()) {
String errorMessage = "Выбор недели обязателен";
logger.info("Ошибка валидации: {}", errorMessage);
return ResponseEntity.badRequest().body(Map.of("message", errorMessage));
} else if(!DayAndWeekValidator.isValidWeek(request.getWeek())){
String errorMessage = "Некорректная неделя. " + DayAndWeekValidator.getValidWeekMessage();
logger.info("Ошибка валидации недели: '{}' - {}", request.getWeek(), errorMessage);
return ResponseEntity.badRequest().body(Map.of("message", errorMessage));
}
//Проверка time
if (request.getTime() == null || request.getTime().isBlank()) {
String errorMessage = "Время обязательно";
logger.info("Ошибка валидации: {}", errorMessage);
return ResponseEntity.badRequest().body(Map.of("message", errorMessage));
}
//Сохранение полученных данных и формирование ответа клиенту
try {
Lesson lesson = new Lesson();
lesson.setTeacherId(request.getTeacherId());
lesson.setSubjectId(request.getSubjectId());
lesson.setGroupId(request.getGroupId());
lesson.setDay(request.getDay());
lesson.setWeek(request.getWeek());
lesson.setTime(request.getTime());
Lesson savedLesson = lessonRepository.save(lesson);
Map<String, Object> response = new LinkedHashMap<>();
response.put("id", savedLesson.getId());
response.put("teacherId", savedLesson.getTeacherId());
response.put("groupId", savedLesson.getGroupId());
response.put("subjectId", savedLesson.getSubjectId());
response.put("day", savedLesson.getDay());
response.put("week", savedLesson.getWeek());
response.put("time", savedLesson.getTime());
logger.info("Занятие успешно создано с ID: {}", savedLesson.getId());
return ResponseEntity.ok(response);
} catch (Exception e) {
logger.error("Ошибка при сохранении занятия: {}", e.getMessage(),e);
return ResponseEntity.status(HttpStatus.INTERNAL_SERVER_ERROR)
.body(Map.of("message", "Произошла ошибка при создании занятия: " + e.getMessage()));
}
}
@GetMapping
public List<LessonResponse> getAllLessons() {
logger.info("Запрос на получение всех занятий");
try {
List<Lesson> lessons = lessonRepository.findAll();
List<LessonResponse> response = lessons.stream()
.map(lesson -> {
String teacherName = teacherRepository.findById(lesson.getTeacherId())
.map(User::getUsername)
.orElse("Неизвестно");
StudentGroup group = groupRepository.findById(lesson.getGroupId()).orElse(null);
String groupName = groupRepository.findById(lesson.getGroupId())
.map(StudentGroup::getName)
.orElse("Неизвестно");
String educationFormName = "Неизвестно";
if(group != null && group.getEducationForm() != null) {
Long educationFormId = group.getEducationForm().getId();
educationFormName = educationFormRepository.findById(educationFormId)
.map(EducationForm::getName)
.orElse("Неизвестно");
}
String subjectName = subjectRepository.findById(lesson.getSubjectId())
.map(Subject::getName)
.orElse("Неизвестно");
return new LessonResponse(
lesson.getId(),
teacherName,
groupName,
educationFormName,
subjectName,
lesson.getDay(),
lesson.getWeek(),
lesson.getTime()
);
})
.toList();
logger.info("Получено {} занятий", lessons.size());
return response;
} catch (Exception e) {
logger.error("Ошибка при получении списка всех занятий: {}", e.getMessage(), e);
throw e;
}
}
@GetMapping("/{teacherId}")
public ResponseEntity<?> getLessonsById(@PathVariable Long teacherId) {
logger.info("Запрос на получение занятий для преподавателя с ID: {}", teacherId);
try {
List<Lesson> lessons = lessonRepository.findByTeacherId(teacherId);
if(lessons.isEmpty()) {
logger.info("У преподавателя с ID {} нет занятий", teacherId);
return ResponseEntity.ok(Map.of(
"message", "У преподавателя с ID " + teacherId +" нет занятий.",
"lessons", Collections.emptyList()
));
}
List<LessonResponse> lessonResponses = lessons.stream()
.map(l -> new LessonResponse(
l.getId(),
l.getTeacherId(),
l.getSubjectId(),
l.getGroupId(),
l.getDay(),
l.getWeek(),
l.getTime()
))
.toList();
logger.info("Найдено {} занянтий для преподавателя с ID: {}", lessonResponses.size(), teacherId);
return ResponseEntity.ok(lessonResponses);
} catch (Exception e ){
logger.error("Ошибка при получении занятий для преподавателя с ID {}: {}", teacherId, e.getMessage(), e);
return ResponseEntity.status(HttpStatus.INTERNAL_SERVER_ERROR)
.body(Map.of("message", "Ошибка при поиске занятий: " + e.getMessage()));
}
}
@GetMapping("/debug/subjects")
public ResponseEntity<?> debugSubjects() {
Map<String, Object> result = new HashMap<>();
// Через JPA репозиторий
List<Subject> allSubjects = subjectRepository.findAll();
result.put("jpa_count", allSubjects.size());
result.put("jpa_subjects", allSubjects.stream()
.map(s -> Map.of("id", s.getId(), "name", s.getName()))
.toList());
// Проверка конкретных ID
Map<Long, Boolean> existenceCheck = new HashMap<>();
for (long id = 1; id <= 6; id++) {
boolean exists = subjectRepository.existsById(id);
existenceCheck.put(id, exists);
}
result.put("existence_check", existenceCheck);
return ResponseEntity.ok(result);
}
//Тестовый запрос на проверку доступности контроллера
@GetMapping("/ping")
public String ping() {
logger.debug("Получен ping запрос");
String response = "pong";
logger.debug("Ответ на ping: {}", response);
return response;
}
}

View File

@@ -1,67 +0,0 @@
package com.magistr.app.controller;
import com.magistr.app.dto.CreateLessonRequest;
import com.magistr.app.dto.LessonResponse;
import com.magistr.app.model.Lesson;
import com.magistr.app.repository.LessonRepository;
import org.springframework.http.ResponseEntity;
import org.springframework.web.bind.annotation.*;
import java.util.List;
import java.util.Map;
@RestController
@RequestMapping("/api/users/test")
public class TestController {
private final LessonRepository lessonRepository;
public TestController(LessonRepository lessonRepository) {
this.lessonRepository = lessonRepository;
}
@PostMapping("/create")
public ResponseEntity<?> createLesson(@RequestBody CreateLessonRequest request) {
if (request.getTeacherId() == null || request.getTeacherId() == 0) {
return ResponseEntity.badRequest().body(Map.of("message", "ID преподавателя обязателен"));
}
if (request.getGroupId() == null || request.getGroupId() == 0) {
return ResponseEntity.badRequest().body(Map.of("message", "ID группы обязателен"));
}
if (request.getLessonTypeId() == null || request.getLessonTypeId() == 0) {
return ResponseEntity.badRequest().body(Map.of("message", "ID предмета обязателен"));
}
if (request.getDay() == null || request.getDay().isBlank()) {
return ResponseEntity.badRequest().body(Map.of("message", "Выбор дня обязателен"));
}
if (request.getWeek() == null || request.getWeek().isBlank()) {
return ResponseEntity.badRequest().body(Map.of("message", "Выбор недели обязателен"));
}
if (request.getTime() == null || request.getTime().isBlank()) {
return ResponseEntity.badRequest().body(Map.of("message", "Время обязательно"));
}
Lesson lesson = new Lesson();
lesson.setTeacherId(request.getTeacherId());
lesson.setLessonTypeId(request.getLessonTypeId());
lesson.setGroupId(request.getGroupId());
lesson.setDay(request.getDay());
lesson.setWeek(request.getWeek());
lesson.setTime(request.getTime());
lessonRepository.save(lesson);
return ResponseEntity.ok(new LessonResponse(lesson.getId(), lesson.getDay(), lesson.getWeek(), lesson.getTime()));
}
@GetMapping
public List<LessonResponse> getAllLessons() {
return lessonRepository.findAll().stream()
.map(l -> new LessonResponse(l.getId(), l.getTeacherId(), l.getLessonTypeId(), l.getDay(), l.getWeek(), l.getTime()))
.toList();
}
@GetMapping("/ping")
public String ping() {
return "pong";
}
}

View File

@@ -4,7 +4,7 @@ public class CreateLessonRequest {
private Long teacherId; private Long teacherId;
private Long groupId; private Long groupId;
private Long lessonTypeId; private Long subjectId;
private String day; private String day;
private String week; private String week;
private String time; private String time;
@@ -28,12 +28,12 @@ public class CreateLessonRequest {
this.groupId = groupId; this.groupId = groupId;
} }
public Long getLessonTypeId() { public Long getSubjectId() {
return lessonTypeId; return subjectId;
} }
public void setLessonTypeId(Long lessonTypeId) { public void setSubjectId(Long subjectId) {
this.lessonTypeId= lessonTypeId; this.subjectId = subjectId;
} }
public String getDay() { public String getDay() {

View File

@@ -1,11 +1,19 @@
package com.magistr.app.dto; package com.magistr.app.dto;
import com.fasterxml.jackson.annotation.JsonInclude;
@JsonInclude(JsonInclude.Include.NON_NULL)
public class LessonResponse { public class LessonResponse {
private Long id; private Long id;
private Long teacherId; private Long teacherId;
private String teacherName;
private Long groupId; private Long groupId;
private Long lessonTypeId; private String groupName;
private String educationFormName;
private Long subjectId;
private String subjectName;
private String day; private String day;
private String week; private String week;
private String time; private String time;
@@ -13,17 +21,22 @@ public class LessonResponse {
public LessonResponse() { public LessonResponse() {
} }
public LessonResponse(Long lessonTypeId, String day, String week, String time) { public LessonResponse(Long id, Long teacherId, Long groupId, Long subjectId, String day, String week, String time) {
this.lessonTypeId = lessonTypeId; this.id = id;
this.teacherId = teacherId;
this.groupId = groupId;
this.subjectId = subjectId;
this.day = day; this.day = day;
this.week = week; this.week = week;
this.time = time; this.time = time;
} }
public LessonResponse(Long id, Long teacherId, Long lessonTypeId, String day, String week, String time) { public LessonResponse(Long id, String teacherName, String groupName, String educationFormName, String subjectName, String day, String week, String time) {
this.id = id; this.id = id;
this.teacherId = teacherId; this.teacherName = teacherName;
this.lessonTypeId = lessonTypeId; this.groupName = groupName;
this.educationFormName = educationFormName;
this.subjectName = subjectName;
this.day = day; this.day = day;
this.week = week; this.week = week;
this.time = time; this.time = time;
@@ -45,6 +58,14 @@ public class LessonResponse {
this.teacherId = teacherId; this.teacherId = teacherId;
} }
public String getTeacherName() {
return teacherName;
}
public void setTeacherName(String teacherName) {
this.teacherName = teacherName;
}
public Long getGroupId() { public Long getGroupId() {
return groupId; return groupId;
} }
@@ -53,12 +74,36 @@ public class LessonResponse {
this.groupId = groupId; this.groupId = groupId;
} }
public Long getLessonTypeId() { public String getGroupName() {
return lessonTypeId; return groupName;
} }
public void setLessonTypeId(Long lessonTypeId) { public void setGroupName(String groupName) {
this.lessonTypeId = lessonTypeId; this.groupName = groupName;
}
public String getEducationFormName() {
return educationFormName;
}
public void setEducationFormName(String educationFormName) {
this.educationFormName = educationFormName;
}
public Long getSubjectId() {
return subjectId;
}
public void setSubjectId(Long subjectId) {
this.subjectId = subjectId;
}
public String getSubjectName() {
return subjectName;
}
public void setSubjectName(String subjectName) {
this.subjectName = subjectName;
} }
public String getDay() { public String getDay() {

View File

@@ -16,8 +16,8 @@ public class Lesson {
@Column(name = "group_id", nullable = false) @Column(name = "group_id", nullable = false)
private Long groupId; private Long groupId;
@Column(name = "lesson_type_id", nullable = false) @Column(name = "subject_id", nullable = false)
private Long lessonTypeId; private Long subjectId;
@Column(name = "day", nullable = false, length = 255) @Column(name = "day", nullable = false, length = 255)
private String day; private String day;
@@ -55,12 +55,12 @@ public class Lesson {
this.groupId = groupId; this.groupId = groupId;
} }
public Long getLessonTypeId() { public Long getSubjectId() {
return lessonTypeId; return subjectId;
} }
public void setLessonTypeId(Long lessonTypeId) { public void setSubjectId(Long subjectId) {
this.lessonTypeId = lessonTypeId; this.subjectId = subjectId;
} }
public String getDay() { public String getDay() {

View File

@@ -3,9 +3,12 @@ package com.magistr.app.repository;
import com.magistr.app.model.Lesson; import com.magistr.app.model.Lesson;
import org.springframework.data.jpa.repository.JpaRepository; import org.springframework.data.jpa.repository.JpaRepository;
import java.util.List;
import java.util.Optional; import java.util.Optional;
public interface LessonRepository extends JpaRepository<Lesson, Long> { public interface LessonRepository extends JpaRepository<Lesson, Long> {
Optional<Lesson> findByLessonTypeId(Long lessonTypeId); Optional<Lesson> findBySubjectId(Long subjectId);
List<Lesson> findByTeacherId(Long teacherId);
} }

View File

@@ -0,0 +1,30 @@
package com.magistr.app.utils;
import java.util.Set;
public class DayAndWeekValidator {
private static final Set<String> VALID_DAYS = Set.of(
"Понедельник", "Вторник", "Среда", "Четверг", "Пятница", "Суббота"
);
private static final Set<String> VALID_WEEKS = Set.of(
"Верхняя", "Нижняя", "Обе"
);
public static boolean isValidDay(String day) {
return day != null && VALID_DAYS.contains(day);
}
public static boolean isValidWeek(String week) {
return week != null && VALID_WEEKS.contains(week);
}
public static String getValidDaysMessage() {
return "Допустимые дни: " + String.join(", ", VALID_DAYS);
}
public static String getValidWeekMessage() {
return "Допустимы для выбора: " + String.join(", ", VALID_WEEKS);
}
}

View File

@@ -10,3 +10,6 @@ spring.datasource.driver-class-name=org.postgresql.Driver
spring.jpa.hibernate.ddl-auto=validate spring.jpa.hibernate.ddl-auto=validate
spring.jpa.show-sql=false spring.jpa.show-sql=false
spring.jpa.open-in-view=false spring.jpa.open-in-view=false
#Eta nastroyka otvechayet za vklyucheniye vidimosti logov urovnya DEBUG v logakh BE, poka vyklyuchil chtoby ne zasoryat'. Zapisi INFO otobrazhat'sya budut
#logging.level.root=DEBUG

View File

@@ -185,7 +185,7 @@ CREATE TABLE IF NOT EXISTS lessons (
id BIGSERIAL PRIMARY KEY, id BIGSERIAL PRIMARY KEY,
teacher_id BIGINT NOT NULL REFERENCES users(id), teacher_id BIGINT NOT NULL REFERENCES users(id),
group_id BIGINT NOT NULL REFERENCES student_groups(id), group_id BIGINT NOT NULL REFERENCES student_groups(id),
lesson_type_id BIGINT NOT NULL REFERENCES lesson_types(id), subject_id BIGINT NOT NULL REFERENCES subjects(id),
day VARCHAR(255) NOT NULL, day VARCHAR(255) NOT NULL,
week VARCHAR(255) NOT NULL, week VARCHAR(255) NOT NULL,
time VARCHAR(255) NOT NULL time VARCHAR(255) NOT NULL

View File

@@ -585,3 +585,49 @@ tbody tr:hover {
.modal-close:hover { .modal-close:hover {
color: var(--error); 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 File

@@ -86,6 +86,15 @@
</svg> </svg>
Дисциплины Дисциплины
</a> </a>
<a href="#" class="nav-item" data-tab="schedule">
<svg width="20" height="20" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round">
<rect x="3" y="4" width="18" height="18" rx="2" ry="2"></rect>
<line x1="16" y1="2" x2="16" y2="6"></line>
<line x1="8" y1="2" x2="8" y2="6"></line>
<line x1="3" y1="10" x2="21" y2="10"></line>
</svg>
Расписание занятий
</a>
</nav> </nav>
<div class="sidebar-footer"> <div class="sidebar-footer">
<button class="btn-logout" id="btn-logout"> <button class="btn-logout" id="btn-logout">

View File

@@ -7,6 +7,7 @@ import { initEduForms } from './views/edu-forms.js';
import { initEquipments } from './views/equipments.js'; import { initEquipments } from './views/equipments.js';
import { initClassrooms } from './views/classrooms.js'; import { initClassrooms } from './views/classrooms.js';
import { initSubjects } from './views/subjects.js'; import { initSubjects } from './views/subjects.js';
import {initSchedule} from "./views/schedule.js";
// Configuration // Configuration
const ROUTES = { const ROUTES = {
@@ -16,6 +17,8 @@ const ROUTES = {
equipments: { title: 'Оборудование', file: 'views/equipments.html', init: initEquipments }, equipments: { title: 'Оборудование', file: 'views/equipments.html', init: initEquipments },
classrooms: { title: 'Аудитории', file: 'views/classrooms.html', init: initClassrooms }, classrooms: { title: 'Аудитории', file: 'views/classrooms.html', init: initClassrooms },
subjects: { title: 'Дисциплины и преподаватели', file: 'views/subjects.html', init: initSubjects }, subjects: { title: 'Дисциплины и преподаватели', file: 'views/subjects.html', init: initSubjects },
// Новая вкладка
schedule: { title: 'Расписание занятий', file: 'views/schedule.html', init: initSchedule },
}; };
let currentTab = null; let currentTab = null;

View File

@@ -0,0 +1,50 @@
import { api } from '../api.js';
import { escapeHtml } from '../utils.js';
export async function initSchedule() {
const tbody = document.getElementById('schedule-tbody');
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');
renderSchedule(lessons);
} catch (e) {
tbody.innerHTML = `<tr><td colspan="7" class="loading-row">Ошибка загрузки: ${escapeHtml(e.message)}</td></tr>`;
}
}
function renderSchedule(lessons) {
if (!lessons || !lessons.length) {
tbody.innerHTML = '<tr><td colspan="7" class="loading-row">Нет занятий</td></tr>';
return;
}
tbody.innerHTML = lessons.map(lesson => {
// Извлекаем имена из вложенных объектов или используем запасные поля
const teacherName = lesson.teacher?.username || lesson.teacherName || '—';
const groupName = lesson.group?.name || lesson.groupName || '—';
const educationForm = lesson.educationForm?.name || lesson.educationFormName || '-';
const subjectName = lesson.subject?.name || lesson.subjectName || '—';
const day = lesson.day || '—';
const week = lesson.week || '—';
const time = lesson.time || '—';
return `<tr>
<td>${escapeHtml(lesson.id)}</td>
<td>${escapeHtml(teacherName)}</td>
<td>${escapeHtml(groupName)}</td>
<td>${escapeHtml(educationForm)}</td>
<td>${escapeHtml(subjectName)}</td>
<td>${escapeHtml(day)}</td>
<td>${escapeHtml(week)}</td>
<td>${escapeHtml(time)}</td>
</tr>`;
}).join('');
}
await loadSchedule();
}

View File

@@ -7,7 +7,91 @@ const ROLE_BADGE = { ADMIN: 'badge-admin', TEACHER: 'badge-teacher', STUDENT: 'b
export async function initUsers() { export async function initUsers() {
const usersTbody = document.getElementById('users-tbody'); const usersTbody = document.getElementById('users-tbody');
const createForm = document.getElementById('create-form'); const createForm = document.getElementById('create-form');
const createAlert = document.getElementById('create-alert');
// Элементы модального окна добавления занятия
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 lessonUserId = document.getElementById('lesson-user-id');
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');
// Переменные для хранения загруженных данных
let groups = [];
let subjects = [];
// NEW: массивы с временными слотами
const weekdaysTimes = [
"8:00-9:30",
"9:40-11:10",
"11:40-13:10",
"13:20-14:50",
"15:00-16:30",
"16:50-18:20",
"18:30-19:00"
];
const saturdayTimes = [
"8:20-9:50",
"10:00-11:30",
"11:40-13:10",
"13:20-14:50"
];
// Загрузка групп с сервера
async function loadGroups() {
try {
groups = await api.get('/api/groups');
renderGroupOptions();
} catch (e) {
console.error('Ошибка загрузки групп:', e);
}
}
// Загрузка дисциплин
async function loadSubjects() {
try {
subjects = await api.get('/api/subjects');
renderSubjectOptions();
} catch (e) {
console.error('Ошибка загрузки дисциплин:', e);
}
}
// Заполнение select группами
function renderGroupOptions() {
lessonGroupSelect.innerHTML = '<option value="">Выберите группу</option>' +
groups.map(g => `<option value="${g.id}">${escapeHtml(g.name)}</option>`).join('');
}
// Заполнение select дисциплинами
function renderSubjectOptions() {
lessonDisciplineSelect.innerHTML = '<option value="">Выберите дисциплину</option>' +
subjects.map(s => `<option value="${s.id}">${escapeHtml(s.name)}</option>`).join('');
}
// NEW: функция обновления списка времени в зависимости от дня
function updateTimeOptions(dayValue) {
let times = [];
if (dayValue === "Суббота") {
times = saturdayTimes;
} else if (dayValue && dayValue !== '') {
times = weekdaysTimes;
} else {
lessonTimeSelect.innerHTML = '<option value="">Сначала выберите день</option>';
lessonTimeSelect.disabled = true;
return;
}
lessonTimeSelect.innerHTML = '<option value="">Выберите время</option>' +
times.map(t => `<option value="${t}">${t}</option>`).join('');
lessonTimeSelect.disabled = false;
}
async function loadUsers() { async function loadUsers() {
try { try {
@@ -29,9 +113,93 @@ export async function initUsers() {
<td>${escapeHtml(u.username)}</td> <td>${escapeHtml(u.username)}</td>
<td><span class="badge ${ROLE_BADGE[u.role] || ''}">${ROLE_LABELS[u.role] || escapeHtml(u.role)}</span></td> <td><span class="badge ${ROLE_BADGE[u.role] || ''}">${ROLE_LABELS[u.role] || escapeHtml(u.role)}</span></td>
<td><button class="btn-delete" data-id="${u.id}">Удалить</button></td> <td><button class="btn-delete" data-id="${u.id}">Удалить</button></td>
<td><button class="btn-add-lesson" data-id="${u.id}">Добавить занятие</button></td>
</tr>`).join(''); </tr>`).join('');
} }
// Сброс формы модального окна
function resetLessonForm() {
addLessonForm.reset();
lessonUserId.value = '';
if (weekUpper) weekUpper.checked = false;
if (weekLower) weekLower.checked = false;
// NEW: сбрасываем селект времени
lessonTimeSelect.innerHTML = '<option value="">Сначала выберите день</option>';
lessonTimeSelect.disabled = true;
hideAlert('add-lesson-alert');
}
// Открытие модалки с установкой userId
function openAddLessonModal(userId) {
lessonUserId.value = userId;
// NEW: сбрасываем выбранный день и время
lessonDaySelect.value = '';
updateTimeOptions('');
modalAddLesson.classList.add('open');
}
// Обработчик отправки формы добавления занятия
addLessonForm.addEventListener('submit', async (e) => {
e.preventDefault();
hideAlert('add-lesson-alert');
const userId = lessonUserId.value;
const groupId = lessonGroupSelect.value;
const subjectId = lessonDisciplineSelect.value;
const dayOfWeek = lessonDaySelect.value;
const timeSlot = lessonTimeSelect.value; // NEW: получаем выбранное время
// Проверка обязательных полей
if (!groupId) {
showAlert('add-lesson-alert', 'Выберите группу', 'error');
return;
}
if (!subjectId) {
showAlert('add-lesson-alert', 'Выберите дисциплину', 'error');
return;
}
if (!dayOfWeek) {
showAlert('add-lesson-alert', 'Выберите день недели', 'error');
return;
}
// NEW: проверка времени
if (!timeSlot) {
showAlert('add-lesson-alert', 'Выберите время', 'error');
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 = 'Нижняя';
}
try {
// Отправляем данные на сервер
const response = await api.post('/api/users/lessons/create', {
teacherId: parseInt(userId),
groupId: parseInt(groupId),
subjectId: parseInt(subjectId),
day: dayOfWeek,
week: weekType,
time: timeSlot // передаём время
});
showAlert('add-lesson-alert', 'Занятие добавлено', 'success');
setTimeout(() => {
modalAddLesson.classList.remove('open');
resetLessonForm();
}, 1500);
} catch (e) {
showAlert('add-lesson-alert', e.message || 'Ошибка добавления занятия', 'error');
}
});
createForm.addEventListener('submit', async (e) => { createForm.addEventListener('submit', async (e) => {
e.preventDefault(); e.preventDefault();
hideAlert('create-alert'); hideAlert('create-alert');
@@ -50,18 +218,52 @@ export async function initUsers() {
} }
}); });
// Обработчик кликов по таблице
usersTbody.addEventListener('click', async (e) => { usersTbody.addEventListener('click', async (e) => {
const btn = e.target.closest('.btn-delete'); const deleteBtn = e.target.closest('.btn-delete');
if (!btn) return; if (deleteBtn) {
if (!confirm('Удалить пользователя?')) return; if (!confirm('Удалить пользователя?')) return;
try { try {
await api.delete('/api/users/' + btn.dataset.id); await api.delete('/api/users/' + deleteBtn.dataset.id);
loadUsers(); loadUsers();
} catch (e) { } catch (e) {
alert(e.message || 'Ошибка удаления'); alert(e.message || 'Ошибка удаления');
} }
return;
}
const addLessonBtn = e.target.closest('.btn-add-lesson');
if (addLessonBtn) {
e.preventDefault();
if (modalAddLesson) {
openAddLessonModal(addLessonBtn.dataset.id);
}
}
}); });
// Initial load // NEW: обработчик изменения дня недели для обновления списка времени
loadUsers(); 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()]);
} }

View File

@@ -0,0 +1,24 @@
<div class="card">
<h2>Расписание занятий</h2>
<div class="table-wrap">
<table id="schedule-table">
<thead>
<tr>
<th>ID</th>
<th>Преподаватель</th>
<th>Группа</th>
<th>Форма обучения</th>
<th>Дисциплина</th>
<th>День недели</th>
<th>Неделя</th>
<th>Время</th>
</tr>
</thead>
<tbody id="schedule-tbody">
<tr>
<td colspan="7" class="loading-row">Загрузка...</td>
</tr>
</tbody>
</table>
</div>
</div>

View File

@@ -45,3 +45,66 @@
</table> </table>
</div> </div>
</div> </div>
<!-- Add Lesson Modal -->
<div class="modal-overlay" id="modal-add-lesson">
<div class="modal-content card">
<h2>Добавить занятие</h2>
<button class="modal-close" id="modal-add-lesson-close">&times;</button>
<form id="add-lesson-form">
<input type="hidden" id="lesson-user-id">
<div class="form-group" style="margin-top: 1rem;">
<label for="lesson-group">Группа</label>
<select id="lesson-group" required>
<option value="">Выберите группу</option>
</select>
</div>
<div class="form-group" style="margin-top: 1rem;">
<label for="lesson-discipline">Дисциплина</label>
<select id="lesson-discipline" required>
<option value="">Выберите дисциплину</option>
</select>
</div>
<div class="form-row" style="margin-top: 1rem;">
<div class="form-group" style="flex: 1;">
<label for="lesson-day">День недели</label>
<select id="lesson-day" required>
<option value="">Выберите день</option>
<option value="Понедельник">Понедельник</option>
<option value="Вторник">Вторник</option>
<option value="Среда">Среда</option>
<option value="Четверг">Четверг</option>
<option value="Пятница">Пятница</option>
<option value="Суббота">Суббота</option>
</select>
</div>
<div class="form-group" style="flex: 1;">
<label>Неделя</label>
<div style="display: flex; gap: 0.5rem;">
<label class="btn-checkbox">
<input type="checkbox" name="weekType" value="Верхняя" id="week-upper">
<span class="checkbox-btn">Верхняя</span>
</label>
<label class="btn-checkbox">
<input type="checkbox" name="weekType" value="Нижняя" id="week-lower">
<span class="checkbox-btn">Нижняя</span>
</label>
</div>
</div>
</div>
<div class="form-group" style="margin-top: 1rem;">
<label for="lesson-time">Время занятия</label>
<select id="lesson-time" required disabled>
<option value="">Сначала выберите день</option>
</select>
</div>
<button type="submit" class="btn-primary" style="width: 100%; margin-top: 1rem;">Сохранить</button>
<div class="form-alert" id="add-lesson-alert" role="alert"></div>
</form>
</div>
</div>