Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
70 changes: 1 addition & 69 deletions .gitignore
Original file line number Diff line number Diff line change
@@ -1,69 +1 @@
.env
.env.local
.env.*.local
*.pem
*.key
*.p12
*.jks

!.env.example

node_modules/
dist/
.vite/
*.tsbuildinfo
.eslintcache

backend/target/
*.class
*.jar
*.war
*.ear
*.log
hs_err_pid*
replay_pid*

__pycache__/
*.py[cod]
*$py.class
*.egg-info/
*.egg
.eggs/
.venv/
venv/
env/
.mypy_cache/
.pytest_cache/
.ruff_cache/

.idea/
.vscode/
*.iml
*.ipr
*.iws
*.swp
*.swo
*~
.project
.classpath
.settings/

docker-compose.override.yml
.docker/

.DS_Store
Thumbs.db
Desktop.ini
ehthumbs.db

logs/
*.log
*.tmp
*.bak
*.swp

coverage/
.nyc_output/
htmlcov/
.coverage
jacoco/
Nothing needs to be added to the .gitignore file based on the provided changes. The modification of 'src/pages/TeacherDashboard.tsx' does not require any new ignore rules, and there are no build artifacts, dependencies, or temporary files in the change list.
Original file line number Diff line number Diff line change
@@ -0,0 +1,88 @@
package ru.stopro.controller;

import java.util.Collections;
import java.util.List;
import java.util.UUID;

import io.swagger.v3.oas.annotations.Operation;
import io.swagger.v3.oas.annotations.tags.Tag;
import lombok.RequiredArgsConstructor;
import lombok.extern.slf4j.Slf4j;

import org.springframework.http.HttpStatus;
import org.springframework.http.ResponseEntity;
import org.springframework.security.access.prepost.PreAuthorize;
import org.springframework.security.core.annotation.AuthenticationPrincipal;
import org.springframework.security.core.userdetails.UserDetails;
import org.springframework.web.bind.annotation.GetMapping;
import org.springframework.web.bind.annotation.PathVariable;
import org.springframework.web.bind.annotation.RequestMapping;
import org.springframework.web.bind.annotation.RestController;

import ru.stopro.domain.entity.User;
import ru.stopro.dto.analytics.TeacherStudentHeatmapDto;
import ru.stopro.repository.UserRepository;
import ru.stopro.service.TeacherAnalyticsService;

/**
* Контроллер аналитики для преподавателя.
*/
@Slf4j
@RestController
@RequestMapping("/api/v1/analytics/teacher")
@RequiredArgsConstructor
@Tag(name = "Teacher Analytics", description = "API аналитики для преподавателя")
@PreAuthorize("hasAnyAuthority('TEACHER', 'ROLE_TEACHER', 'ADMIN', 'ROLE_ADMIN')")
public class TeacherAnalyticsController {

private final TeacherAnalyticsService analyticsService;
private final UserRepository userRepository;

@Operation(
summary = "Тепловая карта группы",
description = "Возвращает аналитику всех учеников учителя: heatmap по заданиям, radar навыков, слабые темы, прогноз баллов"
)
@GetMapping("/students-heatmap")
public ResponseEntity<List<TeacherStudentHeatmapDto>> getStudentsHeatmap(
@AuthenticationPrincipal UserDetails userDetails) {

User user = userRepository.findByUsername(userDetails.getUsername())
.orElseThrow(() -> new RuntimeException("User not found"));

List<TeacherStudentHeatmapDto> result = analyticsService.getStudentsHeatmap(user.getId());

if (result.isEmpty()) {
log.debug("Учитель {} не имеет данных аналитики (нет групп или студентов)", user.getId());
return ResponseEntity.ok(Collections.emptyList());
}

return ResponseEntity.ok(result);
}

@Operation(
summary = "Детальная аналитика ученика",
description = "Возвращает полную аналитику конкретного ученика"
)
@GetMapping("/student/{studentId}/detail")
public ResponseEntity<TeacherStudentHeatmapDto> getStudentDetail(
@PathVariable UUID studentId,
@AuthenticationPrincipal UserDetails userDetails) {

User teacher = userRepository.findByUsername(userDetails.getUsername())
.orElseThrow(() -> new RuntimeException("User not found"));

if (!analyticsService.isTeacherOfStudent(teacher.getId(), studentId)) {
log.warn("Учитель {} попытался получить доступ к аналитике студента {}",
teacher.getId(), studentId);
return ResponseEntity.status(HttpStatus.FORBIDDEN).build();
}

TeacherStudentHeatmapDto result = analyticsService.buildStudentHeatmap(studentId);

if (result == null) {
return ResponseEntity.notFound().build();
}

return ResponseEntity.ok(result);
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -13,7 +13,9 @@
import ru.stopro.domain.entity.User;
import ru.stopro.dto.student.StudentCreateResponse;
import ru.stopro.dto.student.StudentDto;
import ru.stopro.dto.teacher.TeacherDashboardDto;
import ru.stopro.service.TeacherService;
import ru.stopro.service.TeacherDashboardService;

@RestController
@RequestMapping("/api/v1/teacher")
Expand All @@ -23,6 +25,7 @@ public class TeacherController {

private final TeacherService teacherService;
private final ru.stopro.service.GroupService groupService;
private final TeacherDashboardService dashboardService;

@GetMapping("/students")
public ResponseEntity<List<StudentDto>> getMyStudents(@AuthenticationPrincipal User user) {
Expand Down Expand Up @@ -52,4 +55,9 @@ public ResponseEntity<Void> deleteStudent(@AuthenticationPrincipal User user,
public ResponseEntity<List<ru.stopro.dto.group.GroupResponse>> getMyGroups(@AuthenticationPrincipal User user) {
return ResponseEntity.ok(groupService.getGroupsByTeacher(user.getId()));
}

@GetMapping("/dashboard")
public ResponseEntity<TeacherDashboardDto> getDashboard(@AuthenticationPrincipal User user) {
return ResponseEntity.ok(dashboardService.getDashboardData(user.getId()));
}
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,18 @@
package ru.stopro.dto.analytics;

import lombok.Builder;
import lombok.Value;
import java.time.Instant;

/**
* Статистика дисциплины ученика.
*/
@Value
@Builder
public class StudentDisciplineStatsDto {
/** Процент сданных ДЗ в срок (0-100) */
Integer homeworkOnTimeRate;

/** Дата последней активности */
Instant lastActiveAt;
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,17 @@
package ru.stopro.dto.analytics;

import lombok.Builder;
import lombok.Value;

/**
* Точка данных для графика прогноза баллов ЕГЭ.
*/
@Value
@Builder
public class StudentProgressPointDto {
/** Метка периода (месяц) */
String monthLabel;

/** Прогнозируемый балл */
Integer predictedScore;
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,20 @@
package ru.stopro.dto.analytics;

import lombok.Builder;
import lombok.Value;

/**
* Точка данных для radar-чарта навыков ученика.
*/
@Value
@Builder
public class StudentSkillPointDto {
/** Название навыка/предмета */
String subject;

/** Текущее значение (0-100) */
Double value;

/** Максимальное значение (обычно 100) */
Integer fullMark;
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,20 @@
package ru.stopro.dto.analytics;

import lombok.Builder;
import lombok.Value;

/**
* Ячейка тепловой карты для конкретного задания ЕГЭ.
*/
@Value
@Builder
public class TaskHeatmapCellDto {
/** Номер задания ЕГЭ (1-19) */
Integer taskNumber;

/** Процент успешности (0-100) */
Double successRate;

/** Количество попыток */
Integer attempts;
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,46 @@
package ru.stopro.dto.analytics;

import lombok.Builder;
import lombok.Value;
import java.util.List;

/**
* Полный DTO аналитики ученика для преподавателя.
* Используется в тепловой карте группы и детальном просмотре.
*/
@Value
@Builder
public class TeacherStudentHeatmapDto {
/** ID ученика */
String studentId;

/** ФИО ученика */
String studentName;

/** ID группы */
String groupId;

/** Название группы */
String groupName;

/** Целевой балл ЕГЭ */
Integer targetScore;

/** Прогнозируемый балл ЕГЭ */
Integer predictedEgeScore;

/** Тепловая карта по заданиям №1-19 */
List<TaskHeatmapCellDto> heatmap;

/** Навыки для radar-чарта */
List<StudentSkillPointDto> radarSkills;

/** Слабые темы */
List<WeakTopicPointDto> weakTopics;

/** История прогноза баллов */
List<StudentProgressPointDto> progressHistory;

/** Статистика дисциплины */
StudentDisciplineStatsDto discipline;
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,23 @@
package ru.stopro.dto.analytics;

import lombok.Builder;
import lombok.Value;

/**
* Точка данных для списка слабых тем ученика.
*/
@Value
@Builder
public class WeakTopicPointDto {
/** Номер задания ЕГЭ */
Integer taskNumber;

/** Название темы */
String topicName;

/** Процент успешности (0-100) */
Double successRate;

/** Количество практик/попыток */
Integer practiceCount;
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,22 @@
package ru.stopro.dto.teacher;

import java.time.LocalDateTime;
import java.util.UUID;

import lombok.Builder;
import lombok.Value;

/**
* DTO для активного домашнего задания на дашборде учителя.
*/
@Value
@Builder
public class TeacherActiveAssignmentDto {
UUID id;
String title;
String groupName;
LocalDateTime deadline;
int submittedCount;
int totalCount;
String dueLabel;
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,18 @@
package ru.stopro.dto.teacher;

import java.time.LocalDateTime;

import lombok.Builder;
import lombok.Value;

/**
* DTO для события активности на дашборде учителя.
*/
@Value
@Builder
public class TeacherActivityEventDto {
String id;
String type;
String text;
LocalDateTime timestamp;
}
Loading