From b996f5b822d320774657d307e3e49ab9d925aa2d Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?=D0=A0=D0=BE=D1=81=D0=B8=D0=BD=20=D0=A1=D0=B5=D0=BC=D1=91?= =?UTF-8?q?=D0=BD?= Date: Sat, 25 Apr 2026 17:43:10 +0300 Subject: [PATCH 01/23] feat: add groups in filter --- .../Controllers/CoursesController.cs | 3 ++- .../AuthService/ViewModels/InviteExpertViewModel.cs | 2 ++ .../CoursesService/CourseFilterModels.cs | 2 ++ .../CoursesService/DTO/CourseFilterDTO.cs | 2 ++ .../CoursesService/DTO/CreateCourseFilterDTO.cs | 2 ++ .../CoursesService/DTO/EditMentorWorkspaceDTO.cs | 2 ++ .../CoursesService/ViewModels/WorkspaceViewModel.cs | 2 ++ .../HwProj.CoursesService.API/Models/Filter.cs | 3 +++ .../Services/CourseFilterUtils.cs | 2 ++ hwproj.front/src/api/api.ts | 12 ++++++++++++ 10 files changed, 31 insertions(+), 1 deletion(-) diff --git a/HwProj.APIGateway/HwProj.APIGateway.API/Controllers/CoursesController.cs b/HwProj.APIGateway/HwProj.APIGateway.API/Controllers/CoursesController.cs index c3c94084a..d8c73cfa8 100644 --- a/HwProj.APIGateway/HwProj.APIGateway.API/Controllers/CoursesController.cs +++ b/HwProj.APIGateway/HwProj.APIGateway.API/Controllers/CoursesController.cs @@ -275,7 +275,8 @@ public async Task GetMentorWorkspace(long courseId, string mentor var workspace = new WorkspaceViewModel { Homeworks = mentorCourseView.Value.Homeworks, - Students = students.OrderBy(x => x.Surname).ThenBy(x => x.Name).ToArray() + Students = students.OrderBy(x => x.Surname).ThenBy(x => x.Name).ToArray(), + Groups = mentorCourseView.Value.Groups, }; return Ok(workspace); } diff --git a/HwProj.Common/HwProj.Models/AuthService/ViewModels/InviteExpertViewModel.cs b/HwProj.Common/HwProj.Models/AuthService/ViewModels/InviteExpertViewModel.cs index ba6d40d25..222cb1319 100644 --- a/HwProj.Common/HwProj.Models/AuthService/ViewModels/InviteExpertViewModel.cs +++ b/HwProj.Common/HwProj.Models/AuthService/ViewModels/InviteExpertViewModel.cs @@ -10,6 +10,8 @@ public class InviteExpertViewModel public long CourseId { get; set; } + public List GroupIds { get; set; } = new List(); + public List StudentIds { get; set; } = new List(); public List HomeworkIds { get; set; } = new List(); diff --git a/HwProj.Common/HwProj.Models/CoursesService/CourseFilterModels.cs b/HwProj.Common/HwProj.Models/CoursesService/CourseFilterModels.cs index b1612eabe..135b859b4 100644 --- a/HwProj.Common/HwProj.Models/CoursesService/CourseFilterModels.cs +++ b/HwProj.Common/HwProj.Models/CoursesService/CourseFilterModels.cs @@ -8,6 +8,8 @@ public class CreateCourseFilterModel public long CourseId { get; set; } + public List GroupIds { get; set; } = new List(); + public List StudentIds { get; set; } = new List(); public List HomeworkIds { get; set; } = new List(); diff --git a/HwProj.Common/HwProj.Models/CoursesService/DTO/CourseFilterDTO.cs b/HwProj.Common/HwProj.Models/CoursesService/DTO/CourseFilterDTO.cs index 8b456d742..4eb0612a1 100644 --- a/HwProj.Common/HwProj.Models/CoursesService/DTO/CourseFilterDTO.cs +++ b/HwProj.Common/HwProj.Models/CoursesService/DTO/CourseFilterDTO.cs @@ -4,6 +4,8 @@ namespace HwProj.Models.CoursesService.DTO { public class CourseFilterDTO { + public List GroupIds { get; set; } = new List(); + public List StudentIds { get; set; } = new List(); public List HomeworkIds { get; set; } = new List(); diff --git a/HwProj.Common/HwProj.Models/CoursesService/DTO/CreateCourseFilterDTO.cs b/HwProj.Common/HwProj.Models/CoursesService/DTO/CreateCourseFilterDTO.cs index a0838be69..165a006fa 100644 --- a/HwProj.Common/HwProj.Models/CoursesService/DTO/CreateCourseFilterDTO.cs +++ b/HwProj.Common/HwProj.Models/CoursesService/DTO/CreateCourseFilterDTO.cs @@ -6,6 +6,8 @@ public class CreateCourseFilterDTO { public string Id { get; set; } + public List GroupIds { get; set; } = new List(); + public List StudentIds { get; set; } = new List(); public List HomeworkIds { get; set; } = new List(); diff --git a/HwProj.Common/HwProj.Models/CoursesService/DTO/EditMentorWorkspaceDTO.cs b/HwProj.Common/HwProj.Models/CoursesService/DTO/EditMentorWorkspaceDTO.cs index f9fc7b17c..402485f47 100644 --- a/HwProj.Common/HwProj.Models/CoursesService/DTO/EditMentorWorkspaceDTO.cs +++ b/HwProj.Common/HwProj.Models/CoursesService/DTO/EditMentorWorkspaceDTO.cs @@ -4,6 +4,8 @@ namespace HwProj.Models.CoursesService.DTO { public class EditMentorWorkspaceDTO { + public List GroupIds { get; set; } = new List(); + public List StudentIds { get; set; } = new List(); public List HomeworkIds { get; set; } = new List(); diff --git a/HwProj.Common/HwProj.Models/CoursesService/ViewModels/WorkspaceViewModel.cs b/HwProj.Common/HwProj.Models/CoursesService/ViewModels/WorkspaceViewModel.cs index 795374fcc..50c02ffe1 100644 --- a/HwProj.Common/HwProj.Models/CoursesService/ViewModels/WorkspaceViewModel.cs +++ b/HwProj.Common/HwProj.Models/CoursesService/ViewModels/WorkspaceViewModel.cs @@ -5,6 +5,8 @@ namespace HwProj.Models.CoursesService.ViewModels { public class WorkspaceViewModel { + public GroupViewModel[] Groups { get; set; } + public AccountDataDto[] Students { get; set; } public HomeworkViewModel[] Homeworks { get; set; } diff --git a/HwProj.CoursesService/HwProj.CoursesService.API/Models/Filter.cs b/HwProj.CoursesService/HwProj.CoursesService.API/Models/Filter.cs index 46a1eb4b8..edc0ba632 100644 --- a/HwProj.CoursesService/HwProj.CoursesService.API/Models/Filter.cs +++ b/HwProj.CoursesService/HwProj.CoursesService.API/Models/Filter.cs @@ -5,6 +5,9 @@ namespace HwProj.CoursesService.API.Models { public class Filter { + [JsonProperty(PropertyName = "GRP")] + public List GroupIds { get; set; } + [JsonProperty(PropertyName = "STUD")] public List StudentIds { get; set; } diff --git a/HwProj.CoursesService/HwProj.CoursesService.API/Services/CourseFilterUtils.cs b/HwProj.CoursesService/HwProj.CoursesService.API/Services/CourseFilterUtils.cs index 9e02c9cef..fc39da3c9 100644 --- a/HwProj.CoursesService/HwProj.CoursesService.API/Services/CourseFilterUtils.cs +++ b/HwProj.CoursesService/HwProj.CoursesService.API/Services/CourseFilterUtils.cs @@ -10,6 +10,7 @@ public static Filter CreateFilter(CreateCourseFilterModel courseFilterModel) { return new Filter { + GroupIds = courseFilterModel.GroupIds, HomeworkIds = courseFilterModel.HomeworkIds, MentorIds = courseFilterModel.MentorIds, StudentIds = courseFilterModel.StudentIds @@ -18,6 +19,7 @@ public static Filter CreateFilter(CreateCourseFilterModel courseFilterModel) public static Filter FillEmptyFields(this Filter filter) { + filter.GroupIds ??= new List(); filter.StudentIds ??= new List(); filter.HomeworkIds ??= new List(); filter.MentorIds ??= new List(); diff --git a/hwproj.front/src/api/api.ts b/hwproj.front/src/api/api.ts index e42934f1c..c6b075094 100644 --- a/hwproj.front/src/api/api.ts +++ b/hwproj.front/src/api/api.ts @@ -756,6 +756,12 @@ export interface EditMentorWorkspaceDTO { * @memberof EditMentorWorkspaceDTO */ homeworkIds?: Array; + /** + * + * @type {Array} + * @memberof EditMentorWorkspaceDTO + */ + groupIds?: Array; } /** * @@ -2939,6 +2945,12 @@ export interface WorkspaceViewModel { * @memberof WorkspaceViewModel */ homeworks?: Array; + /** + * + * @type {Array} + * @memberof WorkspaceViewModel + */ + groups?: Array; } /** * AccountApi - fetch parameter creator From c6cd741a8fa3002376f834610da8a716d414b4f6 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?=D0=A0=D0=BE=D1=81=D0=B8=D0=BD=20=D0=A1=D0=B5=D0=BC=D1=91?= =?UTF-8?q?=D0=BD?= Date: Sat, 25 Apr 2026 17:54:55 +0300 Subject: [PATCH 02/23] feat: fix filter service for groups in filter work --- .../Services/CourseFilterService.cs | 63 ++++++++++++------- 1 file changed, 39 insertions(+), 24 deletions(-) diff --git a/HwProj.CoursesService/HwProj.CoursesService.API/Services/CourseFilterService.cs b/HwProj.CoursesService/HwProj.CoursesService.API/Services/CourseFilterService.cs index e5e51dfc8..0fa475aa0 100644 --- a/HwProj.CoursesService/HwProj.CoursesService.API/Services/CourseFilterService.cs +++ b/HwProj.CoursesService/HwProj.CoursesService.API/Services/CourseFilterService.cs @@ -21,12 +21,15 @@ public enum ApplyFilterOperation public class CourseFilterService : ICourseFilterService { private const string GlobalFilterId = ""; + private const string StudentsGroupName = ""; private readonly ICourseFilterRepository _courseFilterRepository; + private readonly IGroupsService _groupsService; public CourseFilterService( - ICourseFilterRepository courseFilterRepository) + ICourseFilterRepository courseFilterRepository, IGroupsService groupsService) { _courseFilterRepository = courseFilterRepository; + _groupsService = groupsService; } public async Task> CreateOrUpdateCourseFilter(CreateCourseFilterModel courseFilterModel) @@ -130,6 +133,7 @@ public async Task ApplyFilter(CourseDTO course, string userId) public async Task GetAssignedStudentsIds(long courseId, string[] mentorsIds) { var usersCourseFilters = await _courseFilterRepository.GetAsync(mentorsIds, courseId); + var mentorsGroups = await _groupsService.GetGroupsAsync(usersCourseFilters.SelectMany(u => u.CourseFilter.Filter.GroupIds).ToArray()); return usersCourseFilters .Where(u => u.CourseFilter.Filter.HomeworkIds.Count == 0) @@ -137,6 +141,10 @@ public async Task GetAssignedStudentsIds(long cou { MentorId = u.Id, SelectedStudentsIds = u.CourseFilter.Filter.StudentIds + .Concat(mentorsGroups + .Where(g => u.CourseFilter.Filter.GroupIds.Contains(g.Id)) + .SelectMany(g => g.GroupMates.Select(gm => gm.StudentId))) + .ToList() }) .ToArray(); } @@ -176,6 +184,15 @@ private CourseDTO ApplyFilterInternal(CourseDTO initialCourseDto, CourseDTO edit } : editingCourseDto.Homeworks; + var groups = filter.GroupIds.Any() + ? editingCourseDto.Groups.Where(g => filter.GroupIds.Contains(g.Id)).ToArray() + : Array.Empty(); + + var filteredStudentIds = groups + .SelectMany(g => g.StudentsIds) + .Concat(filter.StudentIds) + .ToHashSet(); + return new CourseDTO { Id = editingCourseDto.Id, @@ -184,31 +201,28 @@ private CourseDTO ApplyFilterInternal(CourseDTO initialCourseDto, CourseDTO edit IsCompleted = editingCourseDto.IsCompleted, IsOpen = editingCourseDto.IsOpen, InviteCode = editingCourseDto.InviteCode, - Groups = - (filter.StudentIds.Any() - ? editingCourseDto.Groups.Select(gs => - { - var filteredStudentsIds = gs.StudentsIds.Intersect(filter.StudentIds).ToArray(); - return filteredStudentsIds.Any() - ? new GroupViewModel - { - Id = gs.Id, - Name = gs.Name, - StudentsIds = filteredStudentsIds - } - : null; - }) - .Where(t => t != null) - .ToArray() - : editingCourseDto.Groups)!, + Groups = groups.Concat( + editingCourseDto.Groups + .Where(gs => gs.Name == StudentsGroupName) + .Select(gs => + { + var groupStudentsIds = gs.StudentsIds.Intersect(filteredStudentIds).ToArray(); + return groupStudentsIds.Any() + ? new GroupViewModel + { + Id = gs.Id, + Name = gs.Name, + StudentsIds = groupStudentsIds + } + : null; + }) + .Where(t => t != null)) + .ToArray(), MentorIds = filter.MentorIds.Any() ? editingCourseDto.MentorIds.Intersect(filter.MentorIds).ToArray() : editingCourseDto.MentorIds, - CourseMates = - filter.StudentIds.Any() - ? editingCourseDto.CourseMates - .Where(mate => !mate.IsAccepted || filter.StudentIds.Contains(mate.StudentId)).ToArray() - : editingCourseDto.CourseMates, + CourseMates = editingCourseDto.CourseMates + .Where(mate => !mate.IsAccepted || filteredStudentIds.Contains(mate.StudentId)).ToArray(), Homeworks = homeworks.OrderBy(hw => hw.PublicationDate).ToArray() }; } @@ -225,7 +239,8 @@ public async Task UpdateGroupFilters(long courseId, long homeworkId, Group group { var existingCourseFilter = filters.SingleOrDefault(f => f.Id == filterId)?.CourseFilter; var newFilter = existingCourseFilter?.Filter - ?? new Filter { StudentIds = new List(), HomeworkIds = new List(), MentorIds = new List() }; + ?? new Filter { GroupIds = new List(), StudentIds = new List(), + HomeworkIds = new List(), MentorIds = new List() }; newFilter.HomeworkIds.Add(homeworkId); if (existingCourseFilter != null) From 5c26b55b7266b5a0c25df9b70a7518df0b973b3d Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?=D0=A0=D0=BE=D1=81=D0=B8=D0=BD=20=D0=A1=D0=B5=D0=BC=D1=91?= =?UTF-8?q?=D0=BD?= Date: Sat, 25 Apr 2026 17:56:25 +0300 Subject: [PATCH 03/23] feat: add assining lecturer to groups in frontend --- .../src/components/Courses/CourseFilter.tsx | 53 +++++++++++++++++-- .../Courses/MentorWorkspaceModal.tsx | 15 ++++-- .../src/components/Experts/InviteModal.tsx | 6 +++ 3 files changed, 67 insertions(+), 7 deletions(-) diff --git a/hwproj.front/src/components/Courses/CourseFilter.tsx b/hwproj.front/src/components/Courses/CourseFilter.tsx index 04d2fea2c..3775e3c7c 100644 --- a/hwproj.front/src/components/Courses/CourseFilter.tsx +++ b/hwproj.front/src/components/Courses/CourseFilter.tsx @@ -1,5 +1,5 @@ import React, {FC, useEffect, useState} from 'react'; -import {HomeworkViewModel, AccountDataDto, MentorToAssignedStudentsDTO} from '../../api'; +import {HomeworkViewModel, AccountDataDto, MentorToAssignedStudentsDTO, GroupViewModel } from '../../api'; import Grid from "@material-ui/core/Grid"; import {Autocomplete, Chip, Stack, Typography} from "@mui/material"; import TextField from "@material-ui/core/TextField"; @@ -13,6 +13,7 @@ interface ICourseFilterProps { mentorId: string; onSelectedHomeworksChange: (homeworks: HomeworkViewModel[]) => void; onSelectedStudentsChange: (students: AccountDataDto[]) => void; + onSelectedGroupsChange: (groups: GroupViewModel[]) => void; onWorkspaceInitialize: (success: boolean, errors?: string[]) => void; isStudentsSelectionHidden: boolean; } @@ -20,8 +21,10 @@ interface ICourseFilterProps { interface ICourseFilterState { courseHomeworks: HomeworkViewModel[]; courseStudents: AccountDataDto[]; + courseGroups: GroupViewModel[]; selectedHomeworks: HomeworkViewModel[]; selectedStudents: AccountDataDto[]; + selectedGroups: GroupViewModel[]; mentors: AccountDataDto[]; assignedStudents: MentorToAssignedStudentsDTO[] } @@ -31,8 +34,10 @@ const CourseFilter: FC = (props) => { const [state, setState] = useState({ courseHomeworks: [], courseStudents: [], + courseGroups: [], selectedHomeworks: [], selectedStudents: [], + selectedGroups: [], assignedStudents: [], mentors: [] }); @@ -57,6 +62,7 @@ const CourseFilter: FC = (props) => { props.onSelectedStudentsChange(mentorWorkspace.students ?? []) props.onSelectedHomeworksChange(mentorWorkspace.homeworks ?? []) + props.onSelectedGroupsChange(mentorWorkspace.groups ?? []) // Для корректного отображения "Все" при инцициализации (получении данных с бэкенда) const allCourseStudentsCount = (course.acceptedStudents?.length ?? 0) + (course.newStudents?.length ?? 0); @@ -64,13 +70,17 @@ const CourseFilter: FC = (props) => { [] : (mentorWorkspace.students) ?? []; const initSelectedHomeworksView = mentorWorkspace.homeworks?.length === course.homeworks?.length ? [] : (mentorWorkspace.homeworks ?? []); + const initSelectedGroupsView = (mentorWorkspace.groups?.length === course.groups?.length ? + [] : (mentorWorkspace.groups ?? [])).filter(g => g.name?.trim()); setState(prevState => ({ ...prevState, courseHomeworks: course.homeworks ?? [], courseStudents: course.acceptedStudents ?? [], - selectedStudents: initSelectedStudentsView, + courseGroups: course.groups?.filter(g => g.name?.trim()) ?? [], + selectedStudents: initSelectedStudentsView.filter(s => !initSelectedGroupsView.some(g => g.studentsIds?.includes(s.userId!))), selectedHomeworks: initSelectedHomeworksView, + selectedGroups: initSelectedGroupsView, mentors: course.mentors!, assignedStudents: assignedStudents.filter(x => x.mentorId !== props.mentorId) })) @@ -99,6 +109,10 @@ const CourseFilter: FC = (props) => { props.onSelectedHomeworksChange(state.selectedHomeworks) }, [state.selectedHomeworks]); + useEffect(() => { + props.onSelectedGroupsChange(state.selectedGroups) + }, [state.selectedGroups]); + //TODO: memoize? const getAssignedMentors = (studentId: string) => state.assignedStudents @@ -163,13 +177,44 @@ const CourseFilter: FC = (props) => { ) : ( + <> + + + + g.name!} + getOptionKey={(option: GroupViewModel) => option.id ?? -1} + filterSelectedOptions + isOptionEqualToValue={(option, value) => option.id === value.id} + renderInput={(params) => ( + )} + noOptionsText={'Больше нет групп для выбора'} + value={state.selectedGroups} + onChange={(_, values) => { + setState((prevState) => ({ + ...prevState, + selectedGroups: values + })); + }} + /> + + + !state.selectedGroups.some(g => g.studentsIds?.includes(s.userId!)))} getOptionLabel={(option: AccountDataDto) => { const assignedMentors = getAssignedMentors(option.userId!) const suffix = assignedMentors.length > 0 ? " — преподаватель " + assignedMentors[0] + "" : "" @@ -209,7 +254,7 @@ const CourseFilter: FC = (props) => { - + )} )} diff --git a/hwproj.front/src/components/Courses/MentorWorkspaceModal.tsx b/hwproj.front/src/components/Courses/MentorWorkspaceModal.tsx index 772ebb29c..5611449f7 100644 --- a/hwproj.front/src/components/Courses/MentorWorkspaceModal.tsx +++ b/hwproj.front/src/components/Courses/MentorWorkspaceModal.tsx @@ -7,7 +7,7 @@ import DialogTitle from '@material-ui/core/DialogTitle'; import ApiSingleton from "../../api/ApiSingleton"; import Typography from "@material-ui/core/Typography"; import Grid from '@material-ui/core/Grid'; -import {HomeworkViewModel, AccountDataDto, EditMentorWorkspaceDTO} from "../../api"; +import {HomeworkViewModel, AccountDataDto, EditMentorWorkspaceDTO, GroupViewModel} from "../../api"; import {Alert} from "@mui/material"; import ErrorsHandler from "../Utils/ErrorsHandler"; import {Snackbar} from "@material-ui/core"; @@ -25,6 +25,7 @@ interface MentorWorkspaceProps { interface MentorWorkspaceState { selectedHomeworks: HomeworkViewModel[]; selectedStudents: AccountDataDto[]; + selectedGroups: GroupViewModel[]; errors: string[]; } @@ -32,6 +33,7 @@ const MentorWorkspaceModal: FC = (props) => { const [state, setState] = useState({ selectedHomeworks: [], selectedStudents: [], + selectedGroups: [], errors: [] }); @@ -43,12 +45,13 @@ const MentorWorkspaceModal: FC = (props) => { const [isWorkspaceUpdated, setIsWorkspaceUpdated] = useState(false); - // Если преподаватель не выбрал ни одного студента, по умолчанию регистрируем всех. Аналогично с выбором домашних работ + // Если преподаватель не выбрал ни одного студента, по умолчанию регистрируем всех. Аналогично с выбором домашних работ и групп const handleWorkspaceChanges = async () => { try { const workspaceViewModel: EditMentorWorkspaceDTO = { homeworkIds: state.selectedHomeworks.map(homeworkViewModel => homeworkViewModel.id!), - studentIds: state.selectedStudents.map(accountData => accountData.userId!) + studentIds: state.selectedStudents.map(accountData => accountData.userId!), + groupIds: state.selectedGroups.map(groupViewModel => groupViewModel.id!) } await ApiSingleton.coursesApi.coursesEditMentorWorkspace( @@ -98,6 +101,12 @@ const MentorWorkspaceModal: FC = (props) => { selectedStudents: students })) } + onSelectedGroupsChange={(groups) => + setState(prevState => ({ + ...prevState, + selectedGroups: groups + })) + } onWorkspaceInitialize={(success, errors) => { if (!success) { setState(prevState => ({ diff --git a/hwproj.front/src/components/Experts/InviteModal.tsx b/hwproj.front/src/components/Experts/InviteModal.tsx index 0fe1d93e8..d92934a64 100644 --- a/hwproj.front/src/components/Experts/InviteModal.tsx +++ b/hwproj.front/src/components/Experts/InviteModal.tsx @@ -218,6 +218,12 @@ const InviteExpertModal: FC = (props) => { selectedStudents: students })) } + onSelectedGroupsChange={(groups) => + setState(prevState => ({ + ...prevState, + selectedGroups: groups + })) + } onWorkspaceInitialize={(success, errors) => { if (!success) { setState(prevState => ({ From d459ded8ceeb09e7de9243a5478652664d091c19 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?=D0=A0=D0=BE=D1=81=D0=B8=D0=BD=20=D0=A1=D0=B5=D0=BC=D1=91?= =?UTF-8?q?=D0=BD?= Date: Sat, 25 Apr 2026 19:49:44 +0300 Subject: [PATCH 04/23] refactor: add grouped students in studentIds --- .../Services/CourseFilterService.cs | 16 ++-------------- .../components/Courses/MentorWorkspaceModal.tsx | 3 ++- 2 files changed, 4 insertions(+), 15 deletions(-) diff --git a/HwProj.CoursesService/HwProj.CoursesService.API/Services/CourseFilterService.cs b/HwProj.CoursesService/HwProj.CoursesService.API/Services/CourseFilterService.cs index 0fa475aa0..d98eef4a5 100644 --- a/HwProj.CoursesService/HwProj.CoursesService.API/Services/CourseFilterService.cs +++ b/HwProj.CoursesService/HwProj.CoursesService.API/Services/CourseFilterService.cs @@ -23,13 +23,11 @@ public class CourseFilterService : ICourseFilterService private const string GlobalFilterId = ""; private const string StudentsGroupName = ""; private readonly ICourseFilterRepository _courseFilterRepository; - private readonly IGroupsService _groupsService; public CourseFilterService( ICourseFilterRepository courseFilterRepository, IGroupsService groupsService) { _courseFilterRepository = courseFilterRepository; - _groupsService = groupsService; } public async Task> CreateOrUpdateCourseFilter(CreateCourseFilterModel courseFilterModel) @@ -133,7 +131,6 @@ public async Task ApplyFilter(CourseDTO course, string userId) public async Task GetAssignedStudentsIds(long courseId, string[] mentorsIds) { var usersCourseFilters = await _courseFilterRepository.GetAsync(mentorsIds, courseId); - var mentorsGroups = await _groupsService.GetGroupsAsync(usersCourseFilters.SelectMany(u => u.CourseFilter.Filter.GroupIds).ToArray()); return usersCourseFilters .Where(u => u.CourseFilter.Filter.HomeworkIds.Count == 0) @@ -141,10 +138,6 @@ public async Task GetAssignedStudentsIds(long cou { MentorId = u.Id, SelectedStudentsIds = u.CourseFilter.Filter.StudentIds - .Concat(mentorsGroups - .Where(g => u.CourseFilter.Filter.GroupIds.Contains(g.Id)) - .SelectMany(g => g.GroupMates.Select(gm => gm.StudentId))) - .ToList() }) .ToArray(); } @@ -188,11 +181,6 @@ private CourseDTO ApplyFilterInternal(CourseDTO initialCourseDto, CourseDTO edit ? editingCourseDto.Groups.Where(g => filter.GroupIds.Contains(g.Id)).ToArray() : Array.Empty(); - var filteredStudentIds = groups - .SelectMany(g => g.StudentsIds) - .Concat(filter.StudentIds) - .ToHashSet(); - return new CourseDTO { Id = editingCourseDto.Id, @@ -206,7 +194,7 @@ private CourseDTO ApplyFilterInternal(CourseDTO initialCourseDto, CourseDTO edit .Where(gs => gs.Name == StudentsGroupName) .Select(gs => { - var groupStudentsIds = gs.StudentsIds.Intersect(filteredStudentIds).ToArray(); + var groupStudentsIds = gs.StudentsIds.Intersect(filter.StudentIds).ToArray(); return groupStudentsIds.Any() ? new GroupViewModel { @@ -222,7 +210,7 @@ private CourseDTO ApplyFilterInternal(CourseDTO initialCourseDto, CourseDTO edit ? editingCourseDto.MentorIds.Intersect(filter.MentorIds).ToArray() : editingCourseDto.MentorIds, CourseMates = editingCourseDto.CourseMates - .Where(mate => !mate.IsAccepted || filteredStudentIds.Contains(mate.StudentId)).ToArray(), + .Where(mate => !mate.IsAccepted || filter.StudentIds.Contains(mate.StudentId)).ToArray(), Homeworks = homeworks.OrderBy(hw => hw.PublicationDate).ToArray() }; } diff --git a/hwproj.front/src/components/Courses/MentorWorkspaceModal.tsx b/hwproj.front/src/components/Courses/MentorWorkspaceModal.tsx index 5611449f7..a4ec99951 100644 --- a/hwproj.front/src/components/Courses/MentorWorkspaceModal.tsx +++ b/hwproj.front/src/components/Courses/MentorWorkspaceModal.tsx @@ -50,7 +50,8 @@ const MentorWorkspaceModal: FC = (props) => { try { const workspaceViewModel: EditMentorWorkspaceDTO = { homeworkIds: state.selectedHomeworks.map(homeworkViewModel => homeworkViewModel.id!), - studentIds: state.selectedStudents.map(accountData => accountData.userId!), + studentIds: state.selectedStudents.map(accountData => accountData.userId!) + .concat(state.selectedGroups.flatMap(g => g.studentsIds ?? [])), groupIds: state.selectedGroups.map(groupViewModel => groupViewModel.id!) } From 3973f8840bc0bb2c9ec4fe4d3be7f2a56000253e Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?=D0=A0=D0=BE=D1=81=D0=B8=D0=BD=20=D0=A1=D0=B5=D0=BC=D1=91?= =?UTF-8?q?=D0=BD?= Date: Thu, 30 Apr 2026 19:57:18 +0300 Subject: [PATCH 05/23] fix: groups in filter counting --- .../Services/CourseFilterService.cs | 36 ++++++++++--------- 1 file changed, 19 insertions(+), 17 deletions(-) diff --git a/HwProj.CoursesService/HwProj.CoursesService.API/Services/CourseFilterService.cs b/HwProj.CoursesService/HwProj.CoursesService.API/Services/CourseFilterService.cs index d98eef4a5..ac167c043 100644 --- a/HwProj.CoursesService/HwProj.CoursesService.API/Services/CourseFilterService.cs +++ b/HwProj.CoursesService/HwProj.CoursesService.API/Services/CourseFilterService.cs @@ -181,6 +181,22 @@ private CourseDTO ApplyFilterInternal(CourseDTO initialCourseDto, CourseDTO edit ? editingCourseDto.Groups.Where(g => filter.GroupIds.Contains(g.Id)).ToArray() : Array.Empty(); + var filteredGroups = editingCourseDto.Groups + .Select(gs => + { + var groupStudentsIds = gs.StudentsIds.Intersect(filter.StudentIds).ToArray(); + return groupStudentsIds.Any() + ? new GroupViewModel + { + Id = gs.Id, + Name = gs.Name, + StudentsIds = groupStudentsIds + } + : null; + }) + .Where(t => t != null) + .ToArray(); + return new CourseDTO { Id = editingCourseDto.Id, @@ -189,23 +205,9 @@ private CourseDTO ApplyFilterInternal(CourseDTO initialCourseDto, CourseDTO edit IsCompleted = editingCourseDto.IsCompleted, IsOpen = editingCourseDto.IsOpen, InviteCode = editingCourseDto.InviteCode, - Groups = groups.Concat( - editingCourseDto.Groups - .Where(gs => gs.Name == StudentsGroupName) - .Select(gs => - { - var groupStudentsIds = gs.StudentsIds.Intersect(filter.StudentIds).ToArray(); - return groupStudentsIds.Any() - ? new GroupViewModel - { - Id = gs.Id, - Name = gs.Name, - StudentsIds = groupStudentsIds - } - : null; - }) - .Where(t => t != null)) - .ToArray(), + Groups = groups.Any() + ? groups.Where(g => g != null).Concat(filteredGroups.Where(g => g.Name == StudentsGroupName)).ToArray() + : filteredGroups, MentorIds = filter.MentorIds.Any() ? editingCourseDto.MentorIds.Intersect(filter.MentorIds).ToArray() : editingCourseDto.MentorIds, From b8a327e4e9209e51e1015594588a156c6eada23a Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?=D0=A0=D0=BE=D1=81=D0=B8=D0=BD=20=D0=A1=D0=B5=D0=BC=D1=91?= =?UTF-8?q?=D0=BD?= Date: Sat, 2 May 2026 21:51:10 +0300 Subject: [PATCH 06/23] fix: group students counting --- .../Services/CourseFilterService.cs | 57 +++++++++++++++---- .../Courses/MentorWorkspaceModal.tsx | 3 +- 2 files changed, 48 insertions(+), 12 deletions(-) diff --git a/HwProj.CoursesService/HwProj.CoursesService.API/Services/CourseFilterService.cs b/HwProj.CoursesService/HwProj.CoursesService.API/Services/CourseFilterService.cs index ac167c043..4ce600ea8 100644 --- a/HwProj.CoursesService/HwProj.CoursesService.API/Services/CourseFilterService.cs +++ b/HwProj.CoursesService/HwProj.CoursesService.API/Services/CourseFilterService.cs @@ -23,11 +23,13 @@ public class CourseFilterService : ICourseFilterService private const string GlobalFilterId = ""; private const string StudentsGroupName = ""; private readonly ICourseFilterRepository _courseFilterRepository; + private readonly IGroupsService _groupsService; public CourseFilterService( ICourseFilterRepository courseFilterRepository, IGroupsService groupsService) { _courseFilterRepository = courseFilterRepository; + _groupsService = groupsService; } public async Task> CreateOrUpdateCourseFilter(CreateCourseFilterModel courseFilterModel) @@ -131,15 +133,43 @@ public async Task ApplyFilter(CourseDTO course, string userId) public async Task GetAssignedStudentsIds(long courseId, string[] mentorsIds) { var usersCourseFilters = await _courseFilterRepository.GetAsync(mentorsIds, courseId); + if (usersCourseFilters == null || usersCourseFilters.Count == 0) + return Array.Empty(); - return usersCourseFilters + var groupIds = usersCourseFilters + .SelectMany(filter => filter.CourseFilter.Filter.GroupIds ?? Enumerable.Empty()) + .Distinct() + .ToArray(); + + var groupToStudents = groupIds.Any() + ? (await _groupsService.GetGroupsAsync(groupIds)) + .ToDictionary( + g => g.Id, + g => g.GroupMates?.Select(gm => gm.StudentId).ToArray() ?? Array.Empty() + ) + : new Dictionary(); + + var result = usersCourseFilters .Where(u => u.CourseFilter.Filter.HomeworkIds.Count == 0) - .Select(u => new MentorToAssignedStudentsDTO + .Select(u => { - MentorId = u.Id, - SelectedStudentsIds = u.CourseFilter.Filter.StudentIds + var directStudents = u.CourseFilter.Filter.StudentIds ?? new List() {}; + var groupIdsForMentor = u.CourseFilter.Filter.GroupIds ?? Enumerable.Empty(); + var studentsFromGroups = groupIdsForMentor + .Where(gid => groupToStudents.ContainsKey(gid)) + .SelectMany(gid => groupToStudents[gid]) + .Distinct() + .ToList(); + + return new MentorToAssignedStudentsDTO + { + MentorId = u.Id, + SelectedStudentsIds = directStudents.Concat(studentsFromGroups).Distinct().ToList() + }; }) .ToArray(); + + return result; } private async Task AddCourseFilter(Filter filter, long courseId, string userId) @@ -179,12 +209,14 @@ private CourseDTO ApplyFilterInternal(CourseDTO initialCourseDto, CourseDTO edit var groups = filter.GroupIds.Any() ? editingCourseDto.Groups.Where(g => filter.GroupIds.Contains(g.Id)).ToArray() - : Array.Empty(); + : editingCourseDto.Groups; + + var filteredStudentIds = filter.StudentIds.Concat(groups.SelectMany(g => g.StudentsIds)); var filteredGroups = editingCourseDto.Groups .Select(gs => { - var groupStudentsIds = gs.StudentsIds.Intersect(filter.StudentIds).ToArray(); + var groupStudentsIds = gs.StudentsIds.Intersect(filteredStudentIds).ToArray(); return groupStudentsIds.Any() ? new GroupViewModel { @@ -205,15 +237,20 @@ private CourseDTO ApplyFilterInternal(CourseDTO initialCourseDto, CourseDTO edit IsCompleted = editingCourseDto.IsCompleted, IsOpen = editingCourseDto.IsOpen, InviteCode = editingCourseDto.InviteCode, - Groups = groups.Any() - ? groups.Where(g => g != null).Concat(filteredGroups.Where(g => g.Name == StudentsGroupName)).ToArray() + Groups = filter.GroupIds.Any() + ? groups + .Concat(filteredGroups.Where(g => g.Name == StudentsGroupName)) + .ToArray() : filteredGroups, MentorIds = filter.MentorIds.Any() ? editingCourseDto.MentorIds.Intersect(filter.MentorIds).ToArray() : editingCourseDto.MentorIds, CourseMates = editingCourseDto.CourseMates - .Where(mate => !mate.IsAccepted || filter.StudentIds.Contains(mate.StudentId)).ToArray(), - Homeworks = homeworks.OrderBy(hw => hw.PublicationDate).ToArray() + .Where(mate => !mate.IsAccepted || filteredStudentIds.Contains(mate.StudentId)).ToArray(), + Homeworks = homeworks + .Where(hw => hw.GroupId == null || groups.Any(g => g.Id == hw.GroupId)) + .OrderBy(hw => hw.PublicationDate) + .ToArray() }; } diff --git a/hwproj.front/src/components/Courses/MentorWorkspaceModal.tsx b/hwproj.front/src/components/Courses/MentorWorkspaceModal.tsx index a4ec99951..5611449f7 100644 --- a/hwproj.front/src/components/Courses/MentorWorkspaceModal.tsx +++ b/hwproj.front/src/components/Courses/MentorWorkspaceModal.tsx @@ -50,8 +50,7 @@ const MentorWorkspaceModal: FC = (props) => { try { const workspaceViewModel: EditMentorWorkspaceDTO = { homeworkIds: state.selectedHomeworks.map(homeworkViewModel => homeworkViewModel.id!), - studentIds: state.selectedStudents.map(accountData => accountData.userId!) - .concat(state.selectedGroups.flatMap(g => g.studentsIds ?? [])), + studentIds: state.selectedStudents.map(accountData => accountData.userId!), groupIds: state.selectedGroups.map(groupViewModel => groupViewModel.id!) } From b01ee28326c3eac6e0b631bc3b588f0751f415ef Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?=D0=A0=D0=BE=D1=81=D0=B8=D0=BD=20=D0=A1=D0=B5=D0=BC=D1=91?= =?UTF-8?q?=D0=BD?= Date: Mon, 4 May 2026 21:42:17 +0300 Subject: [PATCH 07/23] refactor: simplify student groups filtering --- .../HwProj.CoursesService.API/Services/CourseFilterService.cs | 3 +-- 1 file changed, 1 insertion(+), 2 deletions(-) diff --git a/HwProj.CoursesService/HwProj.CoursesService.API/Services/CourseFilterService.cs b/HwProj.CoursesService/HwProj.CoursesService.API/Services/CourseFilterService.cs index 4ce600ea8..43e13246d 100644 --- a/HwProj.CoursesService/HwProj.CoursesService.API/Services/CourseFilterService.cs +++ b/HwProj.CoursesService/HwProj.CoursesService.API/Services/CourseFilterService.cs @@ -21,7 +21,6 @@ public enum ApplyFilterOperation public class CourseFilterService : ICourseFilterService { private const string GlobalFilterId = ""; - private const string StudentsGroupName = ""; private readonly ICourseFilterRepository _courseFilterRepository; private readonly IGroupsService _groupsService; @@ -239,7 +238,7 @@ private CourseDTO ApplyFilterInternal(CourseDTO initialCourseDto, CourseDTO edit InviteCode = editingCourseDto.InviteCode, Groups = filter.GroupIds.Any() ? groups - .Concat(filteredGroups.Where(g => g.Name == StudentsGroupName)) + .Concat(filteredGroups.Where(g => g.Name == String.Empty)) .ToArray() : filteredGroups, MentorIds = filter.MentorIds.Any() From 7a94f4d0b5b4a363866f265326f956e7e040832f Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?=D0=A0=D0=BE=D1=81=D0=B8=D0=BD=20=D0=A1=D0=B5=D0=BC=D1=91?= =?UTF-8?q?=D0=BD?= Date: Tue, 5 May 2026 21:28:45 +0300 Subject: [PATCH 08/23] fix: groups counting --- .../Services/CourseFilterService.cs | 35 ++-- .../src/components/Courses/CourseFilter.tsx | 155 ++++++++++++++---- 2 files changed, 141 insertions(+), 49 deletions(-) diff --git a/HwProj.CoursesService/HwProj.CoursesService.API/Services/CourseFilterService.cs b/HwProj.CoursesService/HwProj.CoursesService.API/Services/CourseFilterService.cs index 43e13246d..ac87cc4fd 100644 --- a/HwProj.CoursesService/HwProj.CoursesService.API/Services/CourseFilterService.cs +++ b/HwProj.CoursesService/HwProj.CoursesService.API/Services/CourseFilterService.cs @@ -149,7 +149,6 @@ public async Task GetAssignedStudentsIds(long cou : new Dictionary(); var result = usersCourseFilters - .Where(u => u.CourseFilter.Filter.HomeworkIds.Count == 0) .Select(u => { var directStudents = u.CourseFilter.Filter.StudentIds ?? new List() {}; @@ -207,26 +206,28 @@ private CourseDTO ApplyFilterInternal(CourseDTO initialCourseDto, CourseDTO edit : editingCourseDto.Homeworks; var groups = filter.GroupIds.Any() - ? editingCourseDto.Groups.Where(g => filter.GroupIds.Contains(g.Id)).ToArray() - : editingCourseDto.Groups; + ? editingCourseDto.Groups.Where(g => filter.GroupIds.Contains(g.Id)) + : Array.Empty(); var filteredStudentIds = filter.StudentIds.Concat(groups.SelectMany(g => g.StudentsIds)); - var filteredGroups = editingCourseDto.Groups - .Select(gs => - { - var groupStudentsIds = gs.StudentsIds.Intersect(filteredStudentIds).ToArray(); - return groupStudentsIds.Any() - ? new GroupViewModel + var filteredGroups = filteredStudentIds.Any() + ? editingCourseDto.Groups + .Select(gs => + { + var groupStudentsIds = gs.StudentsIds.Intersect(filteredStudentIds).ToArray(); + return groupStudentsIds.Any() + ? new GroupViewModel { Id = gs.Id, Name = gs.Name, StudentsIds = groupStudentsIds } : null; - }) - .Where(t => t != null) - .ToArray(); + }) + .Where(t => t != null) + .ToArray() + : editingCourseDto.Groups; return new CourseDTO { @@ -237,17 +238,19 @@ private CourseDTO ApplyFilterInternal(CourseDTO initialCourseDto, CourseDTO edit IsOpen = editingCourseDto.IsOpen, InviteCode = editingCourseDto.InviteCode, Groups = filter.GroupIds.Any() - ? groups - .Concat(filteredGroups.Where(g => g.Name == String.Empty)) + ? editingCourseDto.Groups + .Where(g => filter.GroupIds.Contains(g.Id)) + .Concat(filteredGroups.Where(g => g.Name == string.Empty)) .ToArray() : filteredGroups, MentorIds = filter.MentorIds.Any() ? editingCourseDto.MentorIds.Intersect(filter.MentorIds).ToArray() : editingCourseDto.MentorIds, CourseMates = editingCourseDto.CourseMates - .Where(mate => !mate.IsAccepted || filteredStudentIds.Contains(mate.StudentId)).ToArray(), + .Where(mate => !mate.IsAccepted || filteredStudentIds.Contains(mate.StudentId)) + .ToArray(), Homeworks = homeworks - .Where(hw => hw.GroupId == null || groups.Any(g => g.Id == hw.GroupId)) + .Where(hw => hw.GroupId == null || (groups.Any() ? groups.Any(g => g.Id == hw.GroupId) : true)) .OrderBy(hw => hw.PublicationDate) .ToArray() }; diff --git a/hwproj.front/src/components/Courses/CourseFilter.tsx b/hwproj.front/src/components/Courses/CourseFilter.tsx index 3775e3c7c..e77eaf85c 100644 --- a/hwproj.front/src/components/Courses/CourseFilter.tsx +++ b/hwproj.front/src/components/Courses/CourseFilter.tsx @@ -48,6 +48,9 @@ const CourseFilter: FC = (props) => { // Состояние для отображения поля выбора студентов const [isStudentsSelectionHidden, setIsStudentsSelectionHidden] = useState(props.isStudentsSelectionHidden); + const isAccountDataDto = (obj: any): obj is AccountDataDto => 'userId' in obj; + const isGroupViewModel = (obj: any): obj is GroupViewModel => 'id' in obj && 'name' in obj; + useEffect(() => { const fetchCourseDataForMentor = async () => { try { @@ -68,14 +71,23 @@ const CourseFilter: FC = (props) => { const allCourseStudentsCount = (course.acceptedStudents?.length ?? 0) + (course.newStudents?.length ?? 0); const initSelectedStudentsView = mentorWorkspace.students?.length === allCourseStudentsCount ? [] : (mentorWorkspace.students) ?? []; - const initSelectedHomeworksView = mentorWorkspace.homeworks?.length === course.homeworks?.length ? - [] : (mentorWorkspace.homeworks ?? []); - const initSelectedGroupsView = (mentorWorkspace.groups?.length === course.groups?.length ? - [] : (mentorWorkspace.groups ?? [])).filter(g => g.name?.trim()); + + const courseStudentIds = (mentorWorkspace.students?.length === 0 ? course.acceptedStudents : mentorWorkspace.students) + ?.map(st => st.userId) ?? []; + const courseGroups = course.groups?.filter(g => g.studentsIds?.some(sid => courseStudentIds.includes(sid))) || course.groups; + const initSelectedGroupsView = (mentorWorkspace.groups?.length === courseGroups?.length ? + [] : (mentorWorkspace.groups ?? [])) + .filter(g => g.name?.trim()); + + const courseHomeworks = initSelectedGroupsView.length > 0 + ? course.homeworks?.filter(h => !h.groupId || initSelectedGroupsView?.some(g => g.id === h.groupId)) + : course.homeworks; + const initSelectedHomeworksView = mentorWorkspace.homeworks?.length === courseHomeworks?.length ? + [] : (mentorWorkspace.homeworks ?? []) setState(prevState => ({ ...prevState, - courseHomeworks: course.homeworks ?? [], + courseHomeworks: courseHomeworks ?? [], courseStudents: course.acceptedStudents ?? [], courseGroups: course.groups?.filter(g => g.name?.trim()) ?? [], selectedStudents: initSelectedStudentsView.filter(s => !initSelectedGroupsView.some(g => g.studentsIds?.includes(s.userId!))), @@ -214,37 +226,114 @@ const CourseFilter: FC = (props) => { !state.selectedGroups.some(g => g.studentsIds?.includes(s.userId!)))} - getOptionLabel={(option: AccountDataDto) => { - const assignedMentors = getAssignedMentors(option.userId!) - const suffix = assignedMentors.length > 0 ? " — преподаватель " + assignedMentors[0] + "" : "" - return option.surname + ' ' + option.name + suffix; + options={(() => { + const availableStudents = state.courseStudents.filter( + s => !state.selectedGroups.some(g => g.studentsIds?.includes(s.userId!)) + ); + return [...availableStudents, ...state.courseGroups]; + })()} + getOptionKey={(option) => { + if (isAccountDataDto(option)) return option.userId ?? ''; + return option.id?.toString() ?? ''; }} - getOptionKey={(option: AccountDataDto) => option.userId ?? ""} filterSelectedOptions - isOptionEqualToValue={(option, value) => option.userId === value.userId} - renderInput={(params) => ( - )} - renderTags={(value, getTagProps) => - value.map((option, index) => - ) - } - noOptionsText={'Больше нет студентов для выбора'} - value={state.selectedStudents} + isOptionEqualToValue={(option, value) => { + if (isAccountDataDto(option) && isAccountDataDto(value)) { + return option.userId === value.userId; + } + if (isGroupViewModel(option) && isGroupViewModel(value)) { + return option.id === value.id; + } + return false; + }} + renderInput={(params) => { + const totalSelectedStudents = + state.selectedStudents.length + + state.selectedGroups.reduce((acc, g) => acc + (g.studentsIds?.length ?? 0), 0); + + return ( + + ); + }} + renderTags={(value, getTagProps) => ( + <> + {value.map((option, index) => { + // Исключаем поле key из пропсов, если оно там есть + const { key: _key, ...chipProps } = getTagProps({ index }); + + if (isAccountDataDto(option)) { + return ( + + ); + } else { + return ( + + ); + } + })} + + )} + renderOption={(props, option) => { + const isGroup = isGroupViewModel(option); + if(isGroup) { + return ( +
  • + {option.name} +
  • + ); + } else { + const assignedMentors = getAssignedMentors(option.userId!); + const suffix = assignedMentors.length > 0 ? ` — преподаватель ${assignedMentors[0]}` : ''; + return ( +
  • + {option.surname} {option.name}{suffix} +
  • + ); + } + }} + noOptionsText="Больше нет студентов для выбора" + value={[...state.selectedStudents, ...state.selectedGroups]} onChange={(_, values) => { - setState((prevState) => ({ - ...prevState, - selectedStudents: values - })); + const newGroups = new Set(); + const groupStudentIds = new Set(); + + // Сначала собираем все группы и id их студентов + for (const item of values) { + if (isGroupViewModel(item) && item.studentsIds) { + for (const studentId of item.studentsIds) { + groupStudentIds.add(studentId); + } + newGroups.add(item); + } + } + + // Добавляем только студентов, не входящих ни в одну из выбранных групп + const newSelectedStudents: AccountDataDto[] = []; + for (const item of values) { + if (isAccountDataDto(item) && item.userId && !groupStudentIds.has(item.userId)) { + newSelectedStudents.push(item); + } + } + + setState((prev) => ({ + ...prev, + selectedStudents: newSelectedStudents, + selectedGroups: [...newGroups], + })) }} /> {studentsWithMultipleReviewers.size > 0 && From 2f0aa32408ceb057ad73164e019786bbc4cba70b Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?=D0=A0=D0=BE=D1=81=D0=B8=D0=BD=20=D0=A1=D0=B5=D0=BC=D1=91?= =?UTF-8?q?=D0=BD?= Date: Wed, 6 May 2026 16:02:01 +0300 Subject: [PATCH 09/23] refactor --- .../src/components/Courses/CourseFilter.tsx | 47 +++---------------- 1 file changed, 6 insertions(+), 41 deletions(-) diff --git a/hwproj.front/src/components/Courses/CourseFilter.tsx b/hwproj.front/src/components/Courses/CourseFilter.tsx index e77eaf85c..e3de2d1af 100644 --- a/hwproj.front/src/components/Courses/CourseFilter.tsx +++ b/hwproj.front/src/components/Courses/CourseFilter.tsx @@ -189,37 +189,6 @@ const CourseFilter: FC = (props) => { ) : ( - <> - - - - g.name!} - getOptionKey={(option: GroupViewModel) => option.id ?? -1} - filterSelectedOptions - isOptionEqualToValue={(option, value) => option.id === value.id} - renderInput={(params) => ( - )} - noOptionsText={'Больше нет групп для выбора'} - value={state.selectedGroups} - onChange={(_, values) => { - setState((prevState) => ({ - ...prevState, - selectedGroups: values - })); - }} - /> - - - @@ -249,7 +218,7 @@ const CourseFilter: FC = (props) => { renderInput={(params) => { const totalSelectedStudents = state.selectedStudents.length + - state.selectedGroups.reduce((acc, g) => acc + (g.studentsIds?.length ?? 0), 0); + [...new Set(state.selectedGroups.flatMap(g => g.studentsIds))].length; return ( = (props) => { )} renderOption={(props, option) => { - const isGroup = isGroupViewModel(option); - if(isGroup) { + if(isGroupViewModel(option)) { return (
  • {option.name} @@ -314,24 +282,22 @@ const CourseFilter: FC = (props) => { // Сначала собираем все группы и id их студентов for (const item of values) { if (isGroupViewModel(item) && item.studentsIds) { - for (const studentId of item.studentsIds) { - groupStudentIds.add(studentId); - } + groupStudentIds.union(new Set(item.studentsIds)); newGroups.add(item); } } // Добавляем только студентов, не входящих ни в одну из выбранных групп - const newSelectedStudents: AccountDataDto[] = []; + const newStudents = new Array(); for (const item of values) { if (isAccountDataDto(item) && item.userId && !groupStudentIds.has(item.userId)) { - newSelectedStudents.push(item); + newStudents.push(item); } } setState((prev) => ({ ...prev, - selectedStudents: newSelectedStudents, + selectedStudents: newStudents, selectedGroups: [...newGroups], })) }} @@ -343,7 +309,6 @@ const CourseFilter: FC = (props) => { - )} )} From d17a22461174f40b208c596a2ccb69bb507aeeb0 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?=D0=A0=D0=BE=D1=81=D0=B8=D0=BD=20=D0=A1=D0=B5=D0=BC=D1=91?= =?UTF-8?q?=D0=BD?= Date: Wed, 6 May 2026 16:27:02 +0300 Subject: [PATCH 10/23] fix: new students set calculation --- hwproj.front/src/components/Courses/CourseFilter.tsx | 7 ++----- 1 file changed, 2 insertions(+), 5 deletions(-) diff --git a/hwproj.front/src/components/Courses/CourseFilter.tsx b/hwproj.front/src/components/Courses/CourseFilter.tsx index e3de2d1af..7b9d152d4 100644 --- a/hwproj.front/src/components/Courses/CourseFilter.tsx +++ b/hwproj.front/src/components/Courses/CourseFilter.tsx @@ -72,10 +72,7 @@ const CourseFilter: FC = (props) => { const initSelectedStudentsView = mentorWorkspace.students?.length === allCourseStudentsCount ? [] : (mentorWorkspace.students) ?? []; - const courseStudentIds = (mentorWorkspace.students?.length === 0 ? course.acceptedStudents : mentorWorkspace.students) - ?.map(st => st.userId) ?? []; - const courseGroups = course.groups?.filter(g => g.studentsIds?.some(sid => courseStudentIds.includes(sid))) || course.groups; - const initSelectedGroupsView = (mentorWorkspace.groups?.length === courseGroups?.length ? + const initSelectedGroupsView = (mentorWorkspace.groups?.length === course.groups?.length ? [] : (mentorWorkspace.groups ?? [])) .filter(g => g.name?.trim()); @@ -282,7 +279,7 @@ const CourseFilter: FC = (props) => { // Сначала собираем все группы и id их студентов for (const item of values) { if (isGroupViewModel(item) && item.studentsIds) { - groupStudentIds.union(new Set(item.studentsIds)); + item.studentsIds?.forEach(sid => groupStudentIds.add(sid)); newGroups.add(item); } } From 0772a765505d72c7d62d0afc957775a03ea6467b Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?=D0=A0=D0=BE=D1=81=D0=B8=D0=BD=20=D0=A1=D0=B5=D0=BC=D1=91?= =?UTF-8?q?=D0=BD?= Date: Wed, 6 May 2026 17:10:03 +0300 Subject: [PATCH 11/23] fix: students counting --- .../Services/CourseFilterService.cs | 15 +++++++++------ .../src/components/Courses/CourseFilter.tsx | 12 +++++++----- 2 files changed, 16 insertions(+), 11 deletions(-) diff --git a/HwProj.CoursesService/HwProj.CoursesService.API/Services/CourseFilterService.cs b/HwProj.CoursesService/HwProj.CoursesService.API/Services/CourseFilterService.cs index ac87cc4fd..9d06c2d41 100644 --- a/HwProj.CoursesService/HwProj.CoursesService.API/Services/CourseFilterService.cs +++ b/HwProj.CoursesService/HwProj.CoursesService.API/Services/CourseFilterService.cs @@ -207,9 +207,13 @@ private CourseDTO ApplyFilterInternal(CourseDTO initialCourseDto, CourseDTO edit var groups = filter.GroupIds.Any() ? editingCourseDto.Groups.Where(g => filter.GroupIds.Contains(g.Id)) - : Array.Empty(); + : editingCourseDto.Groups; - var filteredStudentIds = filter.StudentIds.Concat(groups.SelectMany(g => g.StudentsIds)); + var filteredStudentIds = filter.GroupIds.Any() + ? filter.StudentIds.Concat(groups.SelectMany(g => g.StudentsIds)) + : filter.StudentIds.Any() + ? filter.StudentIds + : editingCourseDto.AcceptedStudents.Select(st => st.StudentId); var filteredGroups = filteredStudentIds.Any() ? editingCourseDto.Groups @@ -238,9 +242,8 @@ private CourseDTO ApplyFilterInternal(CourseDTO initialCourseDto, CourseDTO edit IsOpen = editingCourseDto.IsOpen, InviteCode = editingCourseDto.InviteCode, Groups = filter.GroupIds.Any() - ? editingCourseDto.Groups - .Where(g => filter.GroupIds.Contains(g.Id)) - .Concat(filteredGroups.Where(g => g.Name == string.Empty)) + ? filteredGroups + .Where(g => filter.GroupIds.Contains(g.Id) || g.Name == string.Empty) .ToArray() : filteredGroups, MentorIds = filter.MentorIds.Any() @@ -250,7 +253,7 @@ private CourseDTO ApplyFilterInternal(CourseDTO initialCourseDto, CourseDTO edit .Where(mate => !mate.IsAccepted || filteredStudentIds.Contains(mate.StudentId)) .ToArray(), Homeworks = homeworks - .Where(hw => hw.GroupId == null || (groups.Any() ? groups.Any(g => g.Id == hw.GroupId) : true)) + .Where(hw => hw.GroupId == null || groups.Any(g => g.Id == hw.GroupId)) .OrderBy(hw => hw.PublicationDate) .ToArray() }; diff --git a/hwproj.front/src/components/Courses/CourseFilter.tsx b/hwproj.front/src/components/Courses/CourseFilter.tsx index 7b9d152d4..0bc37329a 100644 --- a/hwproj.front/src/components/Courses/CourseFilter.tsx +++ b/hwproj.front/src/components/Courses/CourseFilter.tsx @@ -67,14 +67,16 @@ const CourseFilter: FC = (props) => { props.onSelectedHomeworksChange(mentorWorkspace.homeworks ?? []) props.onSelectedGroupsChange(mentorWorkspace.groups ?? []) - // Для корректного отображения "Все" при инцициализации (получении данных с бэкенда) - const allCourseStudentsCount = (course.acceptedStudents?.length ?? 0) + (course.newStudents?.length ?? 0); - const initSelectedStudentsView = mentorWorkspace.students?.length === allCourseStudentsCount ? - [] : (mentorWorkspace.students) ?? []; - const initSelectedGroupsView = (mentorWorkspace.groups?.length === course.groups?.length ? [] : (mentorWorkspace.groups ?? [])) .filter(g => g.name?.trim()); + const selectedGroupsStudents = initSelectedGroupsView.flatMap(g =>g.studentsIds ?? []); + + const selectedStudentWithoutGroups = mentorWorkspace.students?.filter(st => !selectedGroupsStudents.includes(st.userId!)); + // Для корректного отображения "Все" при инцициализации (получении данных с бэкенда) + const allCourseStudentsCount = (course.acceptedStudents?.length ?? 0) + (course.newStudents?.length ?? 0); + const initSelectedStudentsView = selectedStudentWithoutGroups?.length === allCourseStudentsCount ? + [] : (selectedStudentWithoutGroups) ?? []; const courseHomeworks = initSelectedGroupsView.length > 0 ? course.homeworks?.filter(h => !h.groupId || initSelectedGroupsView?.some(g => g.id === h.groupId)) From 8375d5760ed4fbebbfb27b9a95c2de1df5643198 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?=D0=A0=D0=BE=D1=81=D0=B8=D0=BD=20=D0=A1=D0=B5=D0=BC=D1=91?= =?UTF-8?q?=D0=BD?= Date: Fri, 8 May 2026 12:03:24 +0300 Subject: [PATCH 12/23] feat: show only available homeworks --- hwproj.front/src/components/Courses/CourseFilter.tsx | 12 ++++++++---- 1 file changed, 8 insertions(+), 4 deletions(-) diff --git a/hwproj.front/src/components/Courses/CourseFilter.tsx b/hwproj.front/src/components/Courses/CourseFilter.tsx index 0bc37329a..492b2f8fb 100644 --- a/hwproj.front/src/components/Courses/CourseFilter.tsx +++ b/hwproj.front/src/components/Courses/CourseFilter.tsx @@ -76,17 +76,17 @@ const CourseFilter: FC = (props) => { // Для корректного отображения "Все" при инцициализации (получении данных с бэкенда) const allCourseStudentsCount = (course.acceptedStudents?.length ?? 0) + (course.newStudents?.length ?? 0); const initSelectedStudentsView = selectedStudentWithoutGroups?.length === allCourseStudentsCount ? - [] : (selectedStudentWithoutGroups) ?? []; + [] : selectedStudentWithoutGroups ?? []; const courseHomeworks = initSelectedGroupsView.length > 0 ? course.homeworks?.filter(h => !h.groupId || initSelectedGroupsView?.some(g => g.id === h.groupId)) : course.homeworks; const initSelectedHomeworksView = mentorWorkspace.homeworks?.length === courseHomeworks?.length ? - [] : (mentorWorkspace.homeworks ?? []) + [] : mentorWorkspace.homeworks ?? []; setState(prevState => ({ ...prevState, - courseHomeworks: courseHomeworks ?? [], + courseHomeworks: course.homeworks ?? [], courseStudents: course.acceptedStudents ?? [], courseGroups: course.groups?.filter(g => g.name?.trim()) ?? [], selectedStudents: initSelectedStudentsView.filter(s => !initSelectedGroupsView.some(g => g.studentsIds?.includes(s.userId!))), @@ -156,7 +156,11 @@ const CourseFilter: FC = (props) => { + !h.groupId + || !state.selectedGroups + || state.selectedGroups.some(g => g.id === h.groupId)) + } getOptionLabel={(option: HomeworkViewModel) => option.title ?? "Без названия"} getOptionKey={(option: HomeworkViewModel) => option.id ?? 0} filterSelectedOptions From 69fcb75822f6a4560bf2c11e11f70278cf194a17 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?=D0=A0=D0=BE=D1=81=D0=B8=D0=BD=20=D0=A1=D0=B5=D0=BC=D1=91?= =?UTF-8?q?=D0=BD?= Date: Sat, 9 May 2026 14:59:43 +0300 Subject: [PATCH 13/23] fix: add new group and homework to mentor filter after creation --- .../src/components/Common/GroupSelector.tsx | 13 ++- .../src/components/Courses/Course.tsx | 10 +- .../components/Courses/CourseExperimental.tsx | 3 +- .../src/components/Courses/CourseFilter.tsx | 37 ++------ .../Courses/MentorWorkspaceUtils.ts | 47 ++++++++++ .../Homeworks/CourseHomeworkExperimental.tsx | 94 +++++++++++++++++-- 6 files changed, 156 insertions(+), 48 deletions(-) create mode 100644 hwproj.front/src/components/Courses/MentorWorkspaceUtils.ts diff --git a/hwproj.front/src/components/Common/GroupSelector.tsx b/hwproj.front/src/components/Common/GroupSelector.tsx index 62d867337..be46f5c08 100644 --- a/hwproj.front/src/components/Common/GroupSelector.tsx +++ b/hwproj.front/src/components/Common/GroupSelector.tsx @@ -20,10 +20,10 @@ interface GroupSelectorProps { courseStudents: AccountDataDto[], groups: GroupViewModel[], onGroupIdChange: (groupId?: number) => void, - onGroupsUpdate: () => void, + onGroupsUpdate: () => Promise, selectedGroupId?: number, choiceDisabled?: boolean, - onCreateNewGroup?: () => void, + onCreateNewGroup?: (group: GroupViewModel) => Promise, } const GroupSelector: FC = (props) => { @@ -80,14 +80,19 @@ const GroupSelector: FC = (props) => { groupMates: formState.memberIds.map(studentId => ({studentId})), } ); - props.onGroupsUpdate(); + await props.onGroupsUpdate(); } else { const groupId = await ApiSingleton.courseGroupsApi.courseGroupsCreateCourseGroup(props.courseId, { name: formState.name.trim(), groupMatesIds: formState.memberIds, courseId: props.courseId, }); - props.onGroupsUpdate(); + await props.onCreateNewGroup?.({ + id: groupId, + name: formState.name.trim(), + studentsIds: formState.memberIds, + }); + await props.onGroupsUpdate(); props.onGroupIdChange(groupId); } } catch (error) { diff --git a/hwproj.front/src/components/Courses/Course.tsx b/hwproj.front/src/components/Courses/Course.tsx index c33122808..91066f8e7 100644 --- a/hwproj.front/src/components/Courses/Course.tsx +++ b/hwproj.front/src/components/Courses/Course.tsx @@ -93,8 +93,10 @@ const Course: React.FC = () => { groups } = courseState - const loadGroups = async () => { - const groups = await ApiSingleton.courseGroupsApi.courseGroupsGetAllCourseGroups(course.id!) + const loadGroups = async (targetCourseId: number = course.id!) => { + if (!targetCourseId) return; + + const groups = await ApiSingleton.courseGroupsApi.courseGroupsGetAllCourseGroups(targetCourseId) setCourseState(prevState => ({ ...prevState, groups: groups @@ -153,7 +155,6 @@ const Course: React.FC = () => { courseHomeworks: course.homeworks!, createHomework: false, mentors: course.mentors!, - groups: course.groups || [], acceptedStudents: course.acceptedStudents!, newStudents: course.newStudents!, })) @@ -161,7 +162,8 @@ const Course: React.FC = () => { useEffect(() => { setCurrentState() - }, []) + loadGroups(+courseId!) + }, [courseId]) useEffect(() => { ApiSingleton.statisticsApi.statisticsGetCourseStatistics(+courseId!) diff --git a/hwproj.front/src/components/Courses/CourseExperimental.tsx b/hwproj.front/src/components/Courses/CourseExperimental.tsx index d16fbb3a1..bdc184957 100644 --- a/hwproj.front/src/components/Courses/CourseExperimental.tsx +++ b/hwproj.front/src/components/Courses/CourseExperimental.tsx @@ -61,7 +61,7 @@ interface ICourseExperimentalProps { previouslyExistingFilesCount: number, waitingNewFilesCount: number, deletingFilesIds: number[]) => void; - onGroupsUpdate: () => void; + onGroupsUpdate: () => Promise; groups: GroupViewModel[]; } @@ -428,6 +428,7 @@ export const CourseExperimental: FC = (props) => { getAllHomeworks={() => homeworks} homeworkAndFilesInfo={{homework, filesInfo}} isMentor={isMentor} + userId={props.userId} initialEditMode={initialEditMode || homeworkEditMode} onMount={onSelectedItemMount} onAddTask={addNewTask} diff --git a/hwproj.front/src/components/Courses/CourseFilter.tsx b/hwproj.front/src/components/Courses/CourseFilter.tsx index 492b2f8fb..04fbb567e 100644 --- a/hwproj.front/src/components/Courses/CourseFilter.tsx +++ b/hwproj.front/src/components/Courses/CourseFilter.tsx @@ -7,6 +7,7 @@ import ApiSingleton from "../../api/ApiSingleton"; import ErrorsHandler from "../Utils/ErrorsHandler"; import {DotLottieReact} from '@lottiefiles/dotlottie-react'; import Button from "@material-ui/core/Button"; +import {getSelectedCourseView} from "./MentorWorkspaceUtils"; interface ICourseFilterProps { courseId: number; @@ -63,35 +64,11 @@ const CourseFilter: FC = (props) => { const mentorWorkspace = await ApiSingleton.coursesApi.coursesGetMentorWorkspace(props.courseId, props.mentorId); - props.onSelectedStudentsChange(mentorWorkspace.students ?? []) - props.onSelectedHomeworksChange(mentorWorkspace.homeworks ?? []) - props.onSelectedGroupsChange(mentorWorkspace.groups ?? []) - - const initSelectedGroupsView = (mentorWorkspace.groups?.length === course.groups?.length ? - [] : (mentorWorkspace.groups ?? [])) - .filter(g => g.name?.trim()); - const selectedGroupsStudents = initSelectedGroupsView.flatMap(g =>g.studentsIds ?? []); - - const selectedStudentWithoutGroups = mentorWorkspace.students?.filter(st => !selectedGroupsStudents.includes(st.userId!)); - // Для корректного отображения "Все" при инцициализации (получении данных с бэкенда) - const allCourseStudentsCount = (course.acceptedStudents?.length ?? 0) + (course.newStudents?.length ?? 0); - const initSelectedStudentsView = selectedStudentWithoutGroups?.length === allCourseStudentsCount ? - [] : selectedStudentWithoutGroups ?? []; - - const courseHomeworks = initSelectedGroupsView.length > 0 - ? course.homeworks?.filter(h => !h.groupId || initSelectedGroupsView?.some(g => g.id === h.groupId)) - : course.homeworks; - const initSelectedHomeworksView = mentorWorkspace.homeworks?.length === courseHomeworks?.length ? - [] : mentorWorkspace.homeworks ?? []; + const selectedCourseView = getSelectedCourseView(course, mentorWorkspace); setState(prevState => ({ ...prevState, - courseHomeworks: course.homeworks ?? [], - courseStudents: course.acceptedStudents ?? [], - courseGroups: course.groups?.filter(g => g.name?.trim()) ?? [], - selectedStudents: initSelectedStudentsView.filter(s => !initSelectedGroupsView.some(g => g.studentsIds?.includes(s.userId!))), - selectedHomeworks: initSelectedHomeworksView, - selectedGroups: initSelectedGroupsView, + ...selectedCourseView, mentors: course.mentors!, assignedStudents: assignedStudents.filter(x => x.mentorId !== props.mentorId) })) @@ -158,9 +135,9 @@ const CourseFilter: FC = (props) => { fullWidth options={state.courseHomeworks.filter(h => !h.groupId - || !state.selectedGroups - || state.selectedGroups.some(g => g.id === h.groupId)) - } + || state.selectedGroups.length === 0 + || state.selectedGroups.some(g => g.id === h.groupId) + )} getOptionLabel={(option: HomeworkViewModel) => option.title ?? "Без названия"} getOptionKey={(option: HomeworkViewModel) => option.id ?? 0} filterSelectedOptions @@ -319,4 +296,4 @@ const CourseFilter: FC = (props) => { ) } -export default CourseFilter; \ No newline at end of file +export default CourseFilter; diff --git a/hwproj.front/src/components/Courses/MentorWorkspaceUtils.ts b/hwproj.front/src/components/Courses/MentorWorkspaceUtils.ts new file mode 100644 index 000000000..32be3bc71 --- /dev/null +++ b/hwproj.front/src/components/Courses/MentorWorkspaceUtils.ts @@ -0,0 +1,47 @@ +import {AccountDataDto, CourseViewModel, GroupViewModel, HomeworkViewModel, WorkspaceViewModel} from "@/api"; + +export interface SelectedCourseView { + courseHomeworks: HomeworkViewModel[]; + courseStudents: AccountDataDto[]; + courseGroups: GroupViewModel[]; + selectedHomeworks: HomeworkViewModel[]; + selectedStudents: AccountDataDto[]; + selectedGroups: GroupViewModel[]; +} + +export const getSelectedCourseView = ( + course: CourseViewModel, + mentorWorkspace: WorkspaceViewModel +): SelectedCourseView => { + const courseGroups = course.groups?.filter(g => g.name?.trim()) ?? []; + + const selectedGroups = (mentorWorkspace.groups?.length === courseGroups.length + ? [] + : mentorWorkspace.groups ?? [] + ) + .filter(g => g.name?.trim()); + + const selectedGroupsStudents = selectedGroups.flatMap(g => g.studentsIds ?? []); + const selectedStudentWithoutGroups = mentorWorkspace.students + ?.filter(st => !selectedGroupsStudents.includes(st.userId!)) ?? []; + const allCourseStudentsCount = (course.acceptedStudents?.length ?? 0) + (course.newStudents?.length ?? 0); + const selectedStudents = selectedStudentWithoutGroups.length === allCourseStudentsCount + ? [] + : selectedStudentWithoutGroups; + + const availableHomeworks = selectedGroups.length > 0 + ? course.homeworks?.filter(h => !h.groupId || selectedGroups.some(g => g.id === h.groupId)) + : course.homeworks; + const selectedHomeworks = mentorWorkspace.homeworks?.length === availableHomeworks?.length + ? [] + : mentorWorkspace.homeworks ?? []; + + return { + courseHomeworks: course.homeworks ?? [], + courseStudents: course.acceptedStudents ?? [], + courseGroups, + selectedHomeworks, + selectedStudents: selectedStudents.filter(s => !selectedGroups.some(g => g.studentsIds?.includes(s.userId!))), + selectedGroups, + }; +} diff --git a/hwproj.front/src/components/Homeworks/CourseHomeworkExperimental.tsx b/hwproj.front/src/components/Homeworks/CourseHomeworkExperimental.tsx index ea6486fbe..fe2981715 100644 --- a/hwproj.front/src/components/Homeworks/CourseHomeworkExperimental.tsx +++ b/hwproj.front/src/components/Homeworks/CourseHomeworkExperimental.tsx @@ -21,7 +21,8 @@ import {IFileInfo} from "components/Files/IFileInfo"; import {FC, useEffect, useState} from "react" import Utils from "services/Utils"; import { - HomeworkViewModel, ActionOptions, HomeworkTaskViewModel, PostTaskViewModel, AccountDataDto, GroupViewModel + HomeworkViewModel, ActionOptions, HomeworkTaskViewModel, PostTaskViewModel, AccountDataDto, GroupViewModel, + WorkspaceViewModel, CourseViewModel } from "@/api"; import ApiSingleton from "../../api/ApiSingleton"; import Tags from "../Common/Tags"; @@ -45,6 +46,7 @@ import GroupIcon from '@mui/icons-material/Group'; import AssignmentIcon from '@mui/icons-material/Assignment'; import ErrorsHandler from "@/components/Utils/ErrorsHandler"; import {enqueueSnackbar} from "notistack"; +import {getSelectedCourseView} from "../Courses/MentorWorkspaceUtils"; export interface HomeworkAndFilesInfo { homework: HomeworkViewModel & { isModified?: boolean }, @@ -61,6 +63,7 @@ interface IEditHomeworkState { const CourseHomeworkEditor: FC<{ homeworkAndFilesInfo: HomeworkAndFilesInfo, + mentorId: string, getAllHomeworks: () => HomeworkViewModel[], onUpdate: (update: { homework: HomeworkViewModel } & { isDeleted?: boolean, @@ -71,7 +74,7 @@ const CourseHomeworkEditor: FC<{ previouslyExistingFilesCount: number, waitingNewFilesCount: number, deletingFilesIds: number[]) => void; - onGroupsUpdate: () => void; + onGroupsUpdate: () => Promise; groups: GroupViewModel[]; }> = (props) => { const homework = props.homeworkAndFilesInfo.homework @@ -126,19 +129,29 @@ const CourseHomeworkEditor: FC<{ const [description, setDescription] = useState(loadedHomework.description!) const [selectedGroupId, setSelectedGroupId] = useState(loadedHomework.groupId) const [courseStudents, setCourseStudents] = useState([]) + const [course, setCourse] = useState(undefined) + const [mentorWorkspace, setMentorWorkspace] = useState(undefined) const [page, setPage] = useState<"homework" | "group">("homework") useEffect(() => { - const loadCourseStudents = async () => { + const loadMentorWorkspace = async () => { try { - const courseData = await ApiSingleton.coursesApi.coursesGetAllCourseData(courseId) + const [courseData, mentorWorkspace] = await Promise.all([ + ApiSingleton.coursesApi.coursesGetAllCourseData(courseId), + isNewHomework + ? ApiSingleton.coursesApi.coursesGetMentorWorkspace(courseId, props.mentorId) + .catch(() => undefined) + : Promise.resolve(undefined) + ]); setCourseStudents(courseData.course?.acceptedStudents || []) + setCourse(courseData.course) + setMentorWorkspace(mentorWorkspace) } catch (error) { - console.error('Failed to load course students:', error) + console.error('Failed to load course data:', error) } } - loadCourseStudents() - }, [courseId]) + loadMentorWorkspace() + }, [courseId, props.mentorId, isNewHomework]) const [hasErrors, setHasErrors] = useState(false) @@ -229,6 +242,62 @@ const CourseHomeworkEditor: FC<{ props.onUpdate({homework: loadedHomework, isDeleted: true}) } + const updateMentorFilter = async (update: { + newGroup?: GroupViewModel, + newHomework?: HomeworkViewModel + }) => { + if (!course || !mentorWorkspace) return; + + const {newGroup, newHomework} = update; + const selectedCourseView = getSelectedCourseView(course, mentorWorkspace); + let updatedGroups = selectedCourseView.selectedGroups; + let updatedHomeworks = selectedCourseView.selectedHomeworks; + let hasChanges = false; + + if (newGroup?.id !== undefined && + selectedCourseView.selectedGroups.length > 0 && + !selectedCourseView.selectedGroups.some(g => g.id === newGroup.id)) { + updatedGroups = [...selectedCourseView.selectedGroups, newGroup]; + hasChanges = true; + } + + if (newHomework?.id !== undefined && + selectedCourseView.selectedHomeworks.length > 0 && + !selectedCourseView.selectedHomeworks.some(h => h.id === newHomework.id)) { + updatedHomeworks = [...selectedCourseView.selectedHomeworks, newHomework]; + hasChanges = true; + } + + setCourse(prev => prev ? ({ + ...prev, + groups: newGroup?.id !== undefined && !prev.groups?.some(g => g.id === newGroup.id) + ? [...(prev.groups ?? []), newGroup] + : prev.groups, + homeworks: newHomework?.id !== undefined && !prev.homeworks?.some(h => h.id === newHomework.id) + ? [...(prev.homeworks ?? []), newHomework] + : prev.homeworks, + }) : prev); + + if (!hasChanges) return; + + await ApiSingleton.coursesApi.coursesEditMentorWorkspace( + courseId, + props.mentorId, + { + homeworkIds: updatedHomeworks.map(h => h.id).filter((id): id is number => id !== undefined), + studentIds: selectedCourseView.selectedStudents.map(s => s.userId).filter((id): id is string => id !== undefined), + groupIds: updatedGroups.map(g => g.id).filter((id): id is number => id !== undefined), + } + ); + + setMentorWorkspace(prev => prev ? ({ + ...prev, + homeworks: updatedHomeworks, + students: selectedCourseView.selectedStudents, + groups: updatedGroups + }) : prev); + } + const getDeleteMessage = (homeworkName: string, filesInfo: IFileInfo[]) => { let message = `Вы точно хотите удалить задание "${homeworkName}"?`; if (filesInfo.length > 0) { @@ -272,6 +341,10 @@ const CourseHomeworkEditor: FC<{ ? await ApiSingleton.homeworksApi.homeworksAddHomework(courseId!, update) : await ApiSingleton.homeworksApi.homeworksUpdateHomework(+homeworkId!, update) + if (isNewHomework && updatedHomework.value) { + await updateMentorFilter({newHomework: updatedHomework.value}); + } + const updatedHomeworkId = updatedHomework.value!.id! await handleFilesChange( courseId, CourseUnitType.Homework, updatedHomeworkId, @@ -443,7 +516,8 @@ const CourseHomeworkEditor: FC<{ selectedGroupId={selectedGroupId} choiceDisabled={!isNewHomework} onGroupsUpdate={props.onGroupsUpdate} - groups={props.groups} + onCreateNewGroup={(newGroup: GroupViewModel) => updateMentorFilter({newGroup})} + groups={mentorWorkspace?.groups ?? props.groups} /> {!isNewHomework && !isPublished && @@ -471,6 +545,7 @@ const CourseHomeworkExperimental: FC<{ homeworkAndFilesInfo: HomeworkAndFilesInfo, getAllHomeworks: () => HomeworkViewModel[], isMentor: boolean, + userId: string, initialEditMode: boolean, onMount: () => void, onUpdate: (x: { homework: HomeworkViewModel } & { @@ -483,7 +558,7 @@ const CourseHomeworkExperimental: FC<{ previouslyExistingFilesCount: number, waitingNewFilesCount: number, deletingFilesIds: number[]) => void; - onGroupsUpdate: () => void; + onGroupsUpdate: () => Promise; groups: GroupViewModel[]; }> = (props) => { const {homework, filesInfo} = props.homeworkAndFilesInfo @@ -501,6 +576,7 @@ const CourseHomeworkExperimental: FC<{ if (editMode) return { if (update.isSaved) setEditMode(false) props.onUpdate(update) From 0b8ca6809685fbcf505788f0dd9908f3b0d740bd Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?=D0=A0=D0=BE=D1=81=D0=B8=D0=BD=20=D0=A1=D0=B5=D0=BC=D1=91?= =?UTF-8?q?=D0=BD?= Date: Mon, 18 May 2026 16:21:57 +0300 Subject: [PATCH 14/23] fix: groups showing in selector --- hwproj.front/src/components/Courses/CourseFilter.tsx | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/hwproj.front/src/components/Courses/CourseFilter.tsx b/hwproj.front/src/components/Courses/CourseFilter.tsx index 04fbb567e..110a033d1 100644 --- a/hwproj.front/src/components/Courses/CourseFilter.tsx +++ b/hwproj.front/src/components/Courses/CourseFilter.tsx @@ -179,7 +179,7 @@ const CourseFilter: FC = (props) => { const availableStudents = state.courseStudents.filter( s => !state.selectedGroups.some(g => g.studentsIds?.includes(s.userId!)) ); - return [...availableStudents, ...state.courseGroups]; + return [...state.courseGroups, ...availableStudents]; })()} getOptionKey={(option) => { if (isAccountDataDto(option)) return option.userId ?? ''; @@ -230,6 +230,7 @@ const CourseFilter: FC = (props) => { key={option.id} {...chipProps} label={option.name} + color="primary" /> ); } From ace247346c80ef2d3f7fcf82a0270e0d51034c41 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?=D0=A0=D0=BE=D1=81=D0=B8=D0=BD=20=D0=A1=D0=B5=D0=BC=D1=91?= =?UTF-8?q?=D0=BD?= Date: Tue, 19 May 2026 12:08:45 +0300 Subject: [PATCH 15/23] refactor: move filter update to backend --- .../Controllers/CourseGroupsController.cs | 21 ++++++++++- .../Controllers/HomeworksController.cs | 13 ++++++- .../Services/CourseFilterService.cs | 37 +++++++++++++++++++ .../Services/ICourseFilterService.cs | 1 + 4 files changed, 70 insertions(+), 2 deletions(-) diff --git a/HwProj.CoursesService/HwProj.CoursesService.API/Controllers/CourseGroupsController.cs b/HwProj.CoursesService/HwProj.CoursesService.API/Controllers/CourseGroupsController.cs index d248e5098..a5350e652 100644 --- a/HwProj.CoursesService/HwProj.CoursesService.API/Controllers/CourseGroupsController.cs +++ b/HwProj.CoursesService/HwProj.CoursesService.API/Controllers/CourseGroupsController.cs @@ -1,6 +1,8 @@ using System.Linq; using System.Threading.Tasks; +using System.Collections.Generic; using AutoMapper; +using HwProj.Common.Net8; using HwProj.CoursesService.API.Filters; using HwProj.CoursesService.API.Models; using HwProj.CoursesService.API.Services; @@ -15,11 +17,19 @@ public class CourseGroupsController : Controller { private readonly IGroupsService _groupsService; private readonly IMapper _mapper; + private readonly ICourseFilterService _courseFilterService; + private readonly ICoursesService _coursesService; - public CourseGroupsController(IMapper mapper, IGroupsService groupsService) + public CourseGroupsController( + IMapper mapper, + IGroupsService groupsService, + ICourseFilterService courseFilterService, + ICoursesService coursesService) { _mapper = mapper; _groupsService = groupsService; + _courseFilterService = courseFilterService; + _coursesService = coursesService; } [HttpGet("{courseId}/getAll")] @@ -47,6 +57,15 @@ public async Task CreateGroup([FromBody] CreateGroupViewModel gro GroupMates = groupViewModel.GroupMatesIds.Select(t => new GroupMate() { StudentId = t }).ToList() }; var id = await _groupsService.AddGroupAsync(group); + + var userId = Request.GetUserIdFromHeader(); + var courseMentorIds = await _coursesService.GetCourseLecturers(groupViewModel.CourseId); + if (userId != null && courseMentorIds.Contains(userId)) + await _courseFilterService.AddToFilter(groupViewModel.CourseId, userId, new Filter + { + GroupIds = new List { id } + }); + return Ok(id); } diff --git a/HwProj.CoursesService/HwProj.CoursesService.API/Controllers/HomeworksController.cs b/HwProj.CoursesService/HwProj.CoursesService.API/Controllers/HomeworksController.cs index dbbdb1ba6..0eddddd25 100644 --- a/HwProj.CoursesService/HwProj.CoursesService.API/Controllers/HomeworksController.cs +++ b/HwProj.CoursesService/HwProj.CoursesService.API/Controllers/HomeworksController.cs @@ -1,7 +1,9 @@ using System.Threading.Tasks; using HwProj.CoursesService.API.Domains; using HwProj.CoursesService.API.Filters; +using HwProj.CoursesService.API.Models; using HwProj.CoursesService.API.Services; +using HwProj.Common.Net8; using HwProj.Models.CoursesService.ViewModels; using Microsoft.AspNetCore.Mvc; using System.Linq; @@ -13,10 +15,12 @@ namespace HwProj.CoursesService.API.Controllers public class HomeworksController : Controller { private readonly IHomeworksService _homeworksService; + private readonly ICourseFilterService _courseFilterService; - public HomeworksController(IHomeworksService homeworksService) + public HomeworksController(IHomeworksService homeworksService, ICourseFilterService courseFilterService) { _homeworksService = homeworksService; + _courseFilterService = courseFilterService; } [HttpPost("{courseId}/add")] @@ -28,6 +32,13 @@ public async Task AddHomework(long courseId, if (validationResult.Any()) return BadRequest(validationResult); var newHomework = await _homeworksService.AddHomeworkAsync(courseId, homeworkViewModel); + var mentorId = Request.GetUserIdFromHeader(); + if (mentorId != null) + await _courseFilterService.AddToFilter(courseId, mentorId, new Filter + { + HomeworkIds = new System.Collections.Generic.List { newHomework.Id } + }); + return Ok(newHomework.ToHomeworkViewModel()); } diff --git a/HwProj.CoursesService/HwProj.CoursesService.API/Services/CourseFilterService.cs b/HwProj.CoursesService/HwProj.CoursesService.API/Services/CourseFilterService.cs index 9d06c2d41..e5bfb0956 100644 --- a/HwProj.CoursesService/HwProj.CoursesService.API/Services/CourseFilterService.cs +++ b/HwProj.CoursesService/HwProj.CoursesService.API/Services/CourseFilterService.cs @@ -281,5 +281,42 @@ public async Task UpdateGroupFilters(long courseId, long homeworkId, Group group await AddCourseFilter(newFilter, courseId, filterId); } } + + public async Task AddToFilter(long courseId, string userId, Filter filter) + { + var existingCourseFilter = await _courseFilterRepository.GetAsync(userId, courseId); + if (existingCourseFilter?.Filter is null) + return; + + var targetFilter = existingCourseFilter.Filter.FillEmptyFields(); + filter.FillEmptyFields(); + + var hasChanges = + AddToFilterList(targetFilter.GroupIds, filter.GroupIds) + | AddToFilterList(targetFilter.StudentIds, filter.StudentIds) + | AddToFilterList(targetFilter.HomeworkIds, filter.HomeworkIds) + | AddToFilterList(targetFilter.MentorIds, filter.MentorIds); + + if (hasChanges) + await UpdateAsync(existingCourseFilter.Id, targetFilter); + } + + private static bool AddToFilterList(List target, IEnumerable itemsToAdd) + { + if (!target.Any()) + return false; + + var hasChanges = false; + foreach (var item in itemsToAdd.Distinct()) + { + if (target.Contains(item)) + continue; + + target.Add(item); + hasChanges = true; + } + + return hasChanges; + } } } diff --git a/HwProj.CoursesService/HwProj.CoursesService.API/Services/ICourseFilterService.cs b/HwProj.CoursesService/HwProj.CoursesService.API/Services/ICourseFilterService.cs index 0d49f1805..3d46f3a02 100644 --- a/HwProj.CoursesService/HwProj.CoursesService.API/Services/ICourseFilterService.cs +++ b/HwProj.CoursesService/HwProj.CoursesService.API/Services/ICourseFilterService.cs @@ -16,5 +16,6 @@ public interface ICourseFilterService Task ApplyFilter(CourseDTO courseDto, string userId); Task GetAssignedStudentsIds(long courseId, string[] mentorsIds); Task UpdateGroupFilters(long courseId, long homeworkId, Group group); + Task AddToFilter(long courseId, string filterId, Filter filter); } } \ No newline at end of file From 7f960b2b934d14ba7d5fe40266d3c5a8fe9f1fd8 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?=D0=A0=D0=BE=D1=81=D0=B8=D0=BD=20=D0=A1=D0=B5=D0=BC=D1=91?= =?UTF-8?q?=D0=BD?= Date: Tue, 19 May 2026 12:45:18 +0300 Subject: [PATCH 16/23] refactor: move selected view logic to backend --- .../Controllers/CoursesController.cs | 63 ++++++++++++-- .../src/components/Common/GroupSelector.tsx | 6 -- .../src/components/Courses/CourseFilter.tsx | 10 ++- .../Courses/MentorWorkspaceUtils.ts | 47 ----------- .../Homeworks/CourseHomeworkExperimental.tsx | 83 ++----------------- 5 files changed, 67 insertions(+), 142 deletions(-) delete mode 100644 hwproj.front/src/components/Courses/MentorWorkspaceUtils.ts diff --git a/HwProj.APIGateway/HwProj.APIGateway.API/Controllers/CoursesController.cs b/HwProj.APIGateway/HwProj.APIGateway.API/Controllers/CoursesController.cs index d8c73cfa8..a8f524840 100644 --- a/HwProj.APIGateway/HwProj.APIGateway.API/Controllers/CoursesController.cs +++ b/HwProj.APIGateway/HwProj.APIGateway.API/Controllers/CoursesController.cs @@ -1,5 +1,6 @@ using System.Collections.Generic; using System.Linq; +using System; using System.Net; using System.Threading.Tasks; using AutoMapper; @@ -269,16 +270,64 @@ public async Task GetMentorWorkspace(long courseId, string mentor if (!mentorCourseView.Succeeded) return BadRequest(mentorCourseView.Errors[0]); - var studentIds = mentorCourseView.Value.CourseMates.Select(t => t.StudentId).ToArray(); - var students = await AuthServiceClient.GetAccountsData(studentIds); + var courseResult = await _coursesClient.GetCourseDataRaw(courseId); + if (!courseResult.Succeeded) + return BadRequest(courseResult.Errors[0]); + + var workspace = await CourseToMentorWorkspaceViewModel(courseResult.Value, mentorCourseView.Value); + return Ok(workspace); + } - var workspace = new WorkspaceViewModel + private async Task CourseToMentorWorkspaceViewModel(CourseDTO course, CourseDTO mentorCourseView) + { + var courseGroups = (course.Groups ?? Array.Empty()) + .Where(g => !string.IsNullOrWhiteSpace(g.Name)) + .ToArray(); + + var filteredGroups = (mentorCourseView.Groups ?? Array.Empty()) + .Where(g => !string.IsNullOrWhiteSpace(g.Name)) + .ToArray(); + + var selectedGroups = filteredGroups.Length == courseGroups.Length + ? Array.Empty() + : filteredGroups; + + var selectedGroupsStudentIds = selectedGroups + .SelectMany(g => g.StudentsIds ?? Array.Empty()) + .ToHashSet(); + + var selectedStudentIdsWithoutGroups = (mentorCourseView.CourseMates ?? Array.Empty()) + .Select(t => t.StudentId) + .Where(studentId => !selectedGroupsStudentIds.Contains(studentId)) + .ToArray(); + + var selectedStudentsData = selectedStudentIdsWithoutGroups.Length == (course.CourseMates?.Length ?? 0) + ? Array.Empty() + : await AuthServiceClient.GetAccountsData(selectedStudentIdsWithoutGroups); + + var selectedStudents = selectedStudentsData + .Where(x => x != null) + .OrderBy(x => x.Surname) + .ThenBy(x => x.Name) + .ToArray(); + + var availableHomeworks = selectedGroups.Any() + ? (course.Homeworks ?? Array.Empty()) + .Where(h => h.GroupId == null || selectedGroups.Any(g => g.Id == h.GroupId)) + .ToArray() + : course.Homeworks ?? Array.Empty(); + + var filteredHomeworks = mentorCourseView.Homeworks ?? Array.Empty(); + var selectedHomeworks = filteredHomeworks.Length == availableHomeworks.Length + ? Array.Empty() + : filteredHomeworks; + + return new WorkspaceViewModel { - Homeworks = mentorCourseView.Value.Homeworks, - Students = students.OrderBy(x => x.Surname).ThenBy(x => x.Name).ToArray(), - Groups = mentorCourseView.Value.Groups, + Homeworks = selectedHomeworks, + Students = selectedStudents, + Groups = selectedGroups, }; - return Ok(workspace); } private async Task ToCourseViewModel(CourseDTO course) diff --git a/hwproj.front/src/components/Common/GroupSelector.tsx b/hwproj.front/src/components/Common/GroupSelector.tsx index be46f5c08..9fcdb9ad0 100644 --- a/hwproj.front/src/components/Common/GroupSelector.tsx +++ b/hwproj.front/src/components/Common/GroupSelector.tsx @@ -23,7 +23,6 @@ interface GroupSelectorProps { onGroupsUpdate: () => Promise, selectedGroupId?: number, choiceDisabled?: boolean, - onCreateNewGroup?: (group: GroupViewModel) => Promise, } const GroupSelector: FC = (props) => { @@ -87,11 +86,6 @@ const GroupSelector: FC = (props) => { groupMatesIds: formState.memberIds, courseId: props.courseId, }); - await props.onCreateNewGroup?.({ - id: groupId, - name: formState.name.trim(), - studentsIds: formState.memberIds, - }); await props.onGroupsUpdate(); props.onGroupIdChange(groupId); } diff --git a/hwproj.front/src/components/Courses/CourseFilter.tsx b/hwproj.front/src/components/Courses/CourseFilter.tsx index 110a033d1..9d29eb263 100644 --- a/hwproj.front/src/components/Courses/CourseFilter.tsx +++ b/hwproj.front/src/components/Courses/CourseFilter.tsx @@ -7,7 +7,6 @@ import ApiSingleton from "../../api/ApiSingleton"; import ErrorsHandler from "../Utils/ErrorsHandler"; import {DotLottieReact} from '@lottiefiles/dotlottie-react'; import Button from "@material-ui/core/Button"; -import {getSelectedCourseView} from "./MentorWorkspaceUtils"; interface ICourseFilterProps { courseId: number; @@ -64,11 +63,14 @@ const CourseFilter: FC = (props) => { const mentorWorkspace = await ApiSingleton.coursesApi.coursesGetMentorWorkspace(props.courseId, props.mentorId); - const selectedCourseView = getSelectedCourseView(course, mentorWorkspace); - setState(prevState => ({ ...prevState, - ...selectedCourseView, + courseHomeworks: course.homeworks ?? [], + courseStudents: course.acceptedStudents ?? [], + courseGroups: course.groups?.filter(g => g.name?.trim()) ?? [], + selectedHomeworks: mentorWorkspace.homeworks ?? [], + selectedStudents: mentorWorkspace.students ?? [], + selectedGroups: mentorWorkspace.groups ?? [], mentors: course.mentors!, assignedStudents: assignedStudents.filter(x => x.mentorId !== props.mentorId) })) diff --git a/hwproj.front/src/components/Courses/MentorWorkspaceUtils.ts b/hwproj.front/src/components/Courses/MentorWorkspaceUtils.ts deleted file mode 100644 index 32be3bc71..000000000 --- a/hwproj.front/src/components/Courses/MentorWorkspaceUtils.ts +++ /dev/null @@ -1,47 +0,0 @@ -import {AccountDataDto, CourseViewModel, GroupViewModel, HomeworkViewModel, WorkspaceViewModel} from "@/api"; - -export interface SelectedCourseView { - courseHomeworks: HomeworkViewModel[]; - courseStudents: AccountDataDto[]; - courseGroups: GroupViewModel[]; - selectedHomeworks: HomeworkViewModel[]; - selectedStudents: AccountDataDto[]; - selectedGroups: GroupViewModel[]; -} - -export const getSelectedCourseView = ( - course: CourseViewModel, - mentorWorkspace: WorkspaceViewModel -): SelectedCourseView => { - const courseGroups = course.groups?.filter(g => g.name?.trim()) ?? []; - - const selectedGroups = (mentorWorkspace.groups?.length === courseGroups.length - ? [] - : mentorWorkspace.groups ?? [] - ) - .filter(g => g.name?.trim()); - - const selectedGroupsStudents = selectedGroups.flatMap(g => g.studentsIds ?? []); - const selectedStudentWithoutGroups = mentorWorkspace.students - ?.filter(st => !selectedGroupsStudents.includes(st.userId!)) ?? []; - const allCourseStudentsCount = (course.acceptedStudents?.length ?? 0) + (course.newStudents?.length ?? 0); - const selectedStudents = selectedStudentWithoutGroups.length === allCourseStudentsCount - ? [] - : selectedStudentWithoutGroups; - - const availableHomeworks = selectedGroups.length > 0 - ? course.homeworks?.filter(h => !h.groupId || selectedGroups.some(g => g.id === h.groupId)) - : course.homeworks; - const selectedHomeworks = mentorWorkspace.homeworks?.length === availableHomeworks?.length - ? [] - : mentorWorkspace.homeworks ?? []; - - return { - courseHomeworks: course.homeworks ?? [], - courseStudents: course.acceptedStudents ?? [], - courseGroups, - selectedHomeworks, - selectedStudents: selectedStudents.filter(s => !selectedGroups.some(g => g.studentsIds?.includes(s.userId!))), - selectedGroups, - }; -} diff --git a/hwproj.front/src/components/Homeworks/CourseHomeworkExperimental.tsx b/hwproj.front/src/components/Homeworks/CourseHomeworkExperimental.tsx index fe2981715..23a4a36bd 100644 --- a/hwproj.front/src/components/Homeworks/CourseHomeworkExperimental.tsx +++ b/hwproj.front/src/components/Homeworks/CourseHomeworkExperimental.tsx @@ -22,7 +22,6 @@ import {FC, useEffect, useState} from "react" import Utils from "services/Utils"; import { HomeworkViewModel, ActionOptions, HomeworkTaskViewModel, PostTaskViewModel, AccountDataDto, GroupViewModel, - WorkspaceViewModel, CourseViewModel } from "@/api"; import ApiSingleton from "../../api/ApiSingleton"; import Tags from "../Common/Tags"; @@ -46,7 +45,6 @@ import GroupIcon from '@mui/icons-material/Group'; import AssignmentIcon from '@mui/icons-material/Assignment'; import ErrorsHandler from "@/components/Utils/ErrorsHandler"; import {enqueueSnackbar} from "notistack"; -import {getSelectedCourseView} from "../Courses/MentorWorkspaceUtils"; export interface HomeworkAndFilesInfo { homework: HomeworkViewModel & { isModified?: boolean }, @@ -129,29 +127,19 @@ const CourseHomeworkEditor: FC<{ const [description, setDescription] = useState(loadedHomework.description!) const [selectedGroupId, setSelectedGroupId] = useState(loadedHomework.groupId) const [courseStudents, setCourseStudents] = useState([]) - const [course, setCourse] = useState(undefined) - const [mentorWorkspace, setMentorWorkspace] = useState(undefined) const [page, setPage] = useState<"homework" | "group">("homework") useEffect(() => { - const loadMentorWorkspace = async () => { + const loadCourseStudents = async () => { try { - const [courseData, mentorWorkspace] = await Promise.all([ - ApiSingleton.coursesApi.coursesGetAllCourseData(courseId), - isNewHomework - ? ApiSingleton.coursesApi.coursesGetMentorWorkspace(courseId, props.mentorId) - .catch(() => undefined) - : Promise.resolve(undefined) - ]); + const courseData = await ApiSingleton.coursesApi.coursesGetAllCourseData(courseId); setCourseStudents(courseData.course?.acceptedStudents || []) - setCourse(courseData.course) - setMentorWorkspace(mentorWorkspace) } catch (error) { console.error('Failed to load course data:', error) } } - loadMentorWorkspace() - }, [courseId, props.mentorId, isNewHomework]) + loadCourseStudents() + }, [courseId]) const [hasErrors, setHasErrors] = useState(false) @@ -242,62 +230,6 @@ const CourseHomeworkEditor: FC<{ props.onUpdate({homework: loadedHomework, isDeleted: true}) } - const updateMentorFilter = async (update: { - newGroup?: GroupViewModel, - newHomework?: HomeworkViewModel - }) => { - if (!course || !mentorWorkspace) return; - - const {newGroup, newHomework} = update; - const selectedCourseView = getSelectedCourseView(course, mentorWorkspace); - let updatedGroups = selectedCourseView.selectedGroups; - let updatedHomeworks = selectedCourseView.selectedHomeworks; - let hasChanges = false; - - if (newGroup?.id !== undefined && - selectedCourseView.selectedGroups.length > 0 && - !selectedCourseView.selectedGroups.some(g => g.id === newGroup.id)) { - updatedGroups = [...selectedCourseView.selectedGroups, newGroup]; - hasChanges = true; - } - - if (newHomework?.id !== undefined && - selectedCourseView.selectedHomeworks.length > 0 && - !selectedCourseView.selectedHomeworks.some(h => h.id === newHomework.id)) { - updatedHomeworks = [...selectedCourseView.selectedHomeworks, newHomework]; - hasChanges = true; - } - - setCourse(prev => prev ? ({ - ...prev, - groups: newGroup?.id !== undefined && !prev.groups?.some(g => g.id === newGroup.id) - ? [...(prev.groups ?? []), newGroup] - : prev.groups, - homeworks: newHomework?.id !== undefined && !prev.homeworks?.some(h => h.id === newHomework.id) - ? [...(prev.homeworks ?? []), newHomework] - : prev.homeworks, - }) : prev); - - if (!hasChanges) return; - - await ApiSingleton.coursesApi.coursesEditMentorWorkspace( - courseId, - props.mentorId, - { - homeworkIds: updatedHomeworks.map(h => h.id).filter((id): id is number => id !== undefined), - studentIds: selectedCourseView.selectedStudents.map(s => s.userId).filter((id): id is string => id !== undefined), - groupIds: updatedGroups.map(g => g.id).filter((id): id is number => id !== undefined), - } - ); - - setMentorWorkspace(prev => prev ? ({ - ...prev, - homeworks: updatedHomeworks, - students: selectedCourseView.selectedStudents, - groups: updatedGroups - }) : prev); - } - const getDeleteMessage = (homeworkName: string, filesInfo: IFileInfo[]) => { let message = `Вы точно хотите удалить задание "${homeworkName}"?`; if (filesInfo.length > 0) { @@ -341,10 +273,6 @@ const CourseHomeworkEditor: FC<{ ? await ApiSingleton.homeworksApi.homeworksAddHomework(courseId!, update) : await ApiSingleton.homeworksApi.homeworksUpdateHomework(+homeworkId!, update) - if (isNewHomework && updatedHomework.value) { - await updateMentorFilter({newHomework: updatedHomework.value}); - } - const updatedHomeworkId = updatedHomework.value!.id! await handleFilesChange( courseId, CourseUnitType.Homework, updatedHomeworkId, @@ -516,8 +444,7 @@ const CourseHomeworkEditor: FC<{ selectedGroupId={selectedGroupId} choiceDisabled={!isNewHomework} onGroupsUpdate={props.onGroupsUpdate} - onCreateNewGroup={(newGroup: GroupViewModel) => updateMentorFilter({newGroup})} - groups={mentorWorkspace?.groups ?? props.groups} + groups={props.groups} /> {!isNewHomework && !isPublished && From 90d4c9b21f4a36f5fc714a0eb0728bc6c9466ee4 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?=D0=A0=D0=BE=D1=81=D0=B8=D0=BD=20=D0=A1=D0=B5=D0=BC=D1=91?= =?UTF-8?q?=D0=BD?= Date: Tue, 19 May 2026 13:22:10 +0300 Subject: [PATCH 17/23] fix: groups update after creation --- .../src/components/Courses/Course.tsx | 35 ++++++++----------- .../src/components/Courses/CourseFilter.tsx | 8 ++++- 2 files changed, 22 insertions(+), 21 deletions(-) diff --git a/hwproj.front/src/components/Courses/Course.tsx b/hwproj.front/src/components/Courses/Course.tsx index 91066f8e7..2f04eaa4f 100644 --- a/hwproj.front/src/components/Courses/Course.tsx +++ b/hwproj.front/src/components/Courses/Course.tsx @@ -49,7 +49,6 @@ interface ICourseState { isFound: boolean; course: CourseViewModel; courseHomeworks: HomeworkViewModel[]; - groups: GroupViewModel[]; mentors: AccountDataDto[]; acceptedStudents: AccountDataDto[]; newStudents: AccountDataDto[]; @@ -71,7 +70,6 @@ const Course: React.FC = () => { course: {}, courseHomeworks: [], mentors: [], - groups: [], acceptedStudents: [], newStudents: [], studentSolutions: [], @@ -90,19 +88,8 @@ const Course: React.FC = () => { newStudents, acceptedStudents, courseHomeworks, - groups } = courseState - const loadGroups = async (targetCourseId: number = course.id!) => { - if (!targetCourseId) return; - - const groups = await ApiSingleton.courseGroupsApi.courseGroupsGetAllCourseGroups(targetCourseId) - setCourseState(prevState => ({ - ...prevState, - groups: groups - })) - }; - const userId = ApiSingleton.authService.getUserId() const isLecturer = ApiSingleton.authService.isLecturer() @@ -160,9 +147,17 @@ const Course: React.FC = () => { })) } + const updateCourseGroups = async () => { + const course = await ApiSingleton.coursesApi.coursesGetCourseData(+courseId!) + + setCourseState(prevState => ({ + ...prevState, + course: course, + })) + } + useEffect(() => { setCurrentState() - loadGroups(+courseId!) }, [courseId]) useEffect(() => { @@ -189,9 +184,9 @@ const Course: React.FC = () => { const [lecturerStatsState, setLecturerStatsState] = useState(false); const studentsWithoutGroup = useMemo(() => { - const inGroupIds = new Set(groups.flatMap(g => g.studentsIds)); + const inGroupIds = new Set(course.groups?.flatMap(g => g.studentsIds) || []); return acceptedStudents.filter(s => !inGroupIds.has(s.userId!)); - }, [groups, acceptedStudents]); + }, [course.groups, acceptedStudents]); const CourseMenu: FC = () => { const [anchorEl, setAnchorEl] = React.useState(null); @@ -321,7 +316,7 @@ const Course: React.FC = () => { } - {isCourseMentor && groups.length > 0 && studentsWithoutGroup.length > 0 && + {isCourseMentor && course.groups?.length !== 0 && studentsWithoutGroup.length > 0 && { courseHomeworks: homeworks })) }} - onGroupsUpdate={loadGroups} - groups={groups} + onGroupsUpdate={updateCourseGroups} + groups={course.groups ?? []} /> } {tabValue === "stats" && @@ -418,7 +413,7 @@ const Course: React.FC = () => { isMentor={isCourseMentor} course={courseState.course} solutions={studentSolutions} - groups={groups} + groups={course.groups ?? []} /> } diff --git a/hwproj.front/src/components/Courses/CourseFilter.tsx b/hwproj.front/src/components/Courses/CourseFilter.tsx index 9d29eb263..3e1bf49f8 100644 --- a/hwproj.front/src/components/Courses/CourseFilter.tsx +++ b/hwproj.front/src/components/Courses/CourseFilter.tsx @@ -278,10 +278,16 @@ const CourseFilter: FC = (props) => { } } + const selectedGroups = [...newGroups]; setState((prev) => ({ ...prev, selectedStudents: newStudents, - selectedGroups: [...newGroups], + selectedGroups, + selectedHomeworks: prev.selectedHomeworks + .filter(h => + !h.groupId + || selectedGroups.length === 0 + || selectedGroups.some(g => g.id === h.groupId)), })) }} /> From 6d60ac992c941aa6e220c155505e3c60dd4cf84d Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?=D0=A0=D0=BE=D1=81=D0=B8=D0=BD=20=D0=A1=D0=B5=D0=BC=D1=91?= =?UTF-8?q?=D0=BD?= Date: Tue, 19 May 2026 15:44:08 +0300 Subject: [PATCH 18/23] refactor --- .../Services/CourseFilterService.cs | 17 ++++++++--------- hwproj.front/src/components/Courses/Course.tsx | 2 +- 2 files changed, 9 insertions(+), 10 deletions(-) diff --git a/HwProj.CoursesService/HwProj.CoursesService.API/Services/CourseFilterService.cs b/HwProj.CoursesService/HwProj.CoursesService.API/Services/CourseFilterService.cs index e5bfb0956..9de5a55ad 100644 --- a/HwProj.CoursesService/HwProj.CoursesService.API/Services/CourseFilterService.cs +++ b/HwProj.CoursesService/HwProj.CoursesService.API/Services/CourseFilterService.cs @@ -140,13 +140,12 @@ public async Task GetAssignedStudentsIds(long cou .Distinct() .ToArray(); - var groupToStudents = groupIds.Any() - ? (await _groupsService.GetGroupsAsync(groupIds)) - .ToDictionary( - g => g.Id, - g => g.GroupMates?.Select(gm => gm.StudentId).ToArray() ?? Array.Empty() - ) - : new Dictionary(); + var groups = await _groupsService.GetGroupsAsync(groupIds); + var groupToStudentIds = groups + .ToDictionary( + g => g.Id, + g => g.GroupMates?.Select(gm => gm.StudentId).ToArray() ?? Array.Empty() + ); var result = usersCourseFilters .Select(u => @@ -154,8 +153,8 @@ public async Task GetAssignedStudentsIds(long cou var directStudents = u.CourseFilter.Filter.StudentIds ?? new List() {}; var groupIdsForMentor = u.CourseFilter.Filter.GroupIds ?? Enumerable.Empty(); var studentsFromGroups = groupIdsForMentor - .Where(gid => groupToStudents.ContainsKey(gid)) - .SelectMany(gid => groupToStudents[gid]) + .Where(gid => groupToStudentIds.ContainsKey(gid)) + .SelectMany(gid => groupToStudentIds[gid]) .Distinct() .ToList(); diff --git a/hwproj.front/src/components/Courses/Course.tsx b/hwproj.front/src/components/Courses/Course.tsx index 2f04eaa4f..47d2d8912 100644 --- a/hwproj.front/src/components/Courses/Course.tsx +++ b/hwproj.front/src/components/Courses/Course.tsx @@ -158,7 +158,7 @@ const Course: React.FC = () => { useEffect(() => { setCurrentState() - }, [courseId]) + }, []) useEffect(() => { ApiSingleton.statisticsApi.statisticsGetCourseStatistics(+courseId!) From 75fe26af6fb5a159ce833b9e88f1ea01d5798d97 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?=D0=A0=D0=BE=D1=81=D0=B8=D0=BD=20=D0=A1=D0=B5=D0=BC=D1=91?= =?UTF-8?q?=D0=BD?= Date: Sat, 23 May 2026 17:20:39 +0300 Subject: [PATCH 19/23] fix: return filter logic to frontend --- .../Controllers/CoursesController.cs | 64 ++----------------- .../src/components/Courses/Course.tsx | 2 +- .../src/components/Courses/CourseFilter.tsx | 32 ++++++++-- 3 files changed, 36 insertions(+), 62 deletions(-) diff --git a/HwProj.APIGateway/HwProj.APIGateway.API/Controllers/CoursesController.cs b/HwProj.APIGateway/HwProj.APIGateway.API/Controllers/CoursesController.cs index a8f524840..ef6440f48 100644 --- a/HwProj.APIGateway/HwProj.APIGateway.API/Controllers/CoursesController.cs +++ b/HwProj.APIGateway/HwProj.APIGateway.API/Controllers/CoursesController.cs @@ -1,6 +1,5 @@ using System.Collections.Generic; using System.Linq; -using System; using System.Net; using System.Threading.Tasks; using AutoMapper; @@ -270,64 +269,15 @@ public async Task GetMentorWorkspace(long courseId, string mentor if (!mentorCourseView.Succeeded) return BadRequest(mentorCourseView.Errors[0]); - var courseResult = await _coursesClient.GetCourseDataRaw(courseId); - if (!courseResult.Succeeded) - return BadRequest(courseResult.Errors[0]); + var studentIds = mentorCourseView.Value.CourseMates.Select(t => t.StudentId).ToArray(); + var students = await AuthServiceClient.GetAccountsData(studentIds); - var workspace = await CourseToMentorWorkspaceViewModel(courseResult.Value, mentorCourseView.Value); - return Ok(workspace); - } - - private async Task CourseToMentorWorkspaceViewModel(CourseDTO course, CourseDTO mentorCourseView) - { - var courseGroups = (course.Groups ?? Array.Empty()) - .Where(g => !string.IsNullOrWhiteSpace(g.Name)) - .ToArray(); - - var filteredGroups = (mentorCourseView.Groups ?? Array.Empty()) - .Where(g => !string.IsNullOrWhiteSpace(g.Name)) - .ToArray(); - - var selectedGroups = filteredGroups.Length == courseGroups.Length - ? Array.Empty() - : filteredGroups; - - var selectedGroupsStudentIds = selectedGroups - .SelectMany(g => g.StudentsIds ?? Array.Empty()) - .ToHashSet(); - - var selectedStudentIdsWithoutGroups = (mentorCourseView.CourseMates ?? Array.Empty()) - .Select(t => t.StudentId) - .Where(studentId => !selectedGroupsStudentIds.Contains(studentId)) - .ToArray(); - - var selectedStudentsData = selectedStudentIdsWithoutGroups.Length == (course.CourseMates?.Length ?? 0) - ? Array.Empty() - : await AuthServiceClient.GetAccountsData(selectedStudentIdsWithoutGroups); - - var selectedStudents = selectedStudentsData - .Where(x => x != null) - .OrderBy(x => x.Surname) - .ThenBy(x => x.Name) - .ToArray(); - - var availableHomeworks = selectedGroups.Any() - ? (course.Homeworks ?? Array.Empty()) - .Where(h => h.GroupId == null || selectedGroups.Any(g => g.Id == h.GroupId)) - .ToArray() - : course.Homeworks ?? Array.Empty(); - - var filteredHomeworks = mentorCourseView.Homeworks ?? Array.Empty(); - var selectedHomeworks = filteredHomeworks.Length == availableHomeworks.Length - ? Array.Empty() - : filteredHomeworks; - - return new WorkspaceViewModel + return Ok(new WorkspaceViewModel { - Homeworks = selectedHomeworks, - Students = selectedStudents, - Groups = selectedGroups, - }; + Homeworks = mentorCourseView.Value.Homeworks, + Students = students.OrderBy(x => x.Surname).ThenBy(x => x.Name).ToArray(), + Groups = mentorCourseView.Value.Groups, + }); } private async Task ToCourseViewModel(CourseDTO course) diff --git a/hwproj.front/src/components/Courses/Course.tsx b/hwproj.front/src/components/Courses/Course.tsx index 47d2d8912..076fd7273 100644 --- a/hwproj.front/src/components/Courses/Course.tsx +++ b/hwproj.front/src/components/Courses/Course.tsx @@ -1,7 +1,7 @@ import * as React from "react"; import {FC, useEffect, useState, useMemo} from "react"; import {useNavigate, useParams, useSearchParams} from "react-router-dom"; -import {AccountDataDto, CourseViewModel, GroupViewModel, HomeworkViewModel, StatisticsCourseMatesModel} from "@/api"; +import {AccountDataDto, CourseViewModel, HomeworkViewModel, StatisticsCourseMatesModel} from "@/api"; import StudentStats from "./StudentStats"; import NewCourseStudents from "./NewCourseStudents"; import ApiSingleton from "../../api/ApiSingleton"; diff --git a/hwproj.front/src/components/Courses/CourseFilter.tsx b/hwproj.front/src/components/Courses/CourseFilter.tsx index 3e1bf49f8..6319c600e 100644 --- a/hwproj.front/src/components/Courses/CourseFilter.tsx +++ b/hwproj.front/src/components/Courses/CourseFilter.tsx @@ -63,14 +63,38 @@ const CourseFilter: FC = (props) => { const mentorWorkspace = await ApiSingleton.coursesApi.coursesGetMentorWorkspace(props.courseId, props.mentorId); + const courseGroups = course.groups?.filter(g => g.name?.trim()) ?? []; + const selectedGroups = (mentorWorkspace.groups?.length === courseGroups.length + ? [] + : mentorWorkspace.groups ?? [] + ) + .filter(g => g.name?.trim()); + + const selectedGroupsStudents = selectedGroups.flatMap(g => g.studentsIds ?? []); + const selectedStudentsWithoutGroups = mentorWorkspace.students + ?.filter(st => !selectedGroupsStudents.includes(st.userId!)) ?? []; + const allCourseStudentsCount = (course.acceptedStudents?.length ?? 0) + (course.newStudents?.length ?? 0); + const selectedStudents = selectedStudentsWithoutGroups.length === allCourseStudentsCount + ? [] + : selectedStudentsWithoutGroups; + + const availableHomeworks = course.homeworks + ?.filter(h => + !h.groupId + || selectedGroups.length === 0 + || selectedGroups.some(g => g.id === h.groupId)); + const selectedHomeworks = mentorWorkspace.homeworks?.length === availableHomeworks?.length + ? [] + : mentorWorkspace.homeworks ?? []; + setState(prevState => ({ ...prevState, courseHomeworks: course.homeworks ?? [], courseStudents: course.acceptedStudents ?? [], - courseGroups: course.groups?.filter(g => g.name?.trim()) ?? [], - selectedHomeworks: mentorWorkspace.homeworks ?? [], - selectedStudents: mentorWorkspace.students ?? [], - selectedGroups: mentorWorkspace.groups ?? [], + courseGroups, + selectedHomeworks, + selectedStudents, + selectedGroups, mentors: course.mentors!, assignedStudents: assignedStudents.filter(x => x.mentorId !== props.mentorId) })) From 865a71d8791c6fae92a3c614b23a39f80b727dae Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?=D0=A0=D0=BE=D1=81=D0=B8=D0=BD=20=D0=A1=D0=B5=D0=BC=D1=91?= =?UTF-8?q?=D0=BD?= Date: Sat, 25 Apr 2026 19:49:44 +0300 Subject: [PATCH 20/23] refactor: add grouped students in studentIds --- hwproj.front/src/components/Courses/MentorWorkspaceModal.tsx | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/hwproj.front/src/components/Courses/MentorWorkspaceModal.tsx b/hwproj.front/src/components/Courses/MentorWorkspaceModal.tsx index 5611449f7..a4ec99951 100644 --- a/hwproj.front/src/components/Courses/MentorWorkspaceModal.tsx +++ b/hwproj.front/src/components/Courses/MentorWorkspaceModal.tsx @@ -50,7 +50,8 @@ const MentorWorkspaceModal: FC = (props) => { try { const workspaceViewModel: EditMentorWorkspaceDTO = { homeworkIds: state.selectedHomeworks.map(homeworkViewModel => homeworkViewModel.id!), - studentIds: state.selectedStudents.map(accountData => accountData.userId!), + studentIds: state.selectedStudents.map(accountData => accountData.userId!) + .concat(state.selectedGroups.flatMap(g => g.studentsIds ?? [])), groupIds: state.selectedGroups.map(groupViewModel => groupViewModel.id!) } From 4051731b42fdb318746757216192c06ea99ea50e Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?=D0=A0=D0=BE=D1=81=D0=B8=D0=BD=20=D0=A1=D0=B5=D0=BC=D1=91?= =?UTF-8?q?=D0=BD?= Date: Sat, 2 May 2026 21:51:10 +0300 Subject: [PATCH 21/23] fix: group students counting --- hwproj.front/src/components/Courses/MentorWorkspaceModal.tsx | 3 +-- 1 file changed, 1 insertion(+), 2 deletions(-) diff --git a/hwproj.front/src/components/Courses/MentorWorkspaceModal.tsx b/hwproj.front/src/components/Courses/MentorWorkspaceModal.tsx index a4ec99951..5611449f7 100644 --- a/hwproj.front/src/components/Courses/MentorWorkspaceModal.tsx +++ b/hwproj.front/src/components/Courses/MentorWorkspaceModal.tsx @@ -50,8 +50,7 @@ const MentorWorkspaceModal: FC = (props) => { try { const workspaceViewModel: EditMentorWorkspaceDTO = { homeworkIds: state.selectedHomeworks.map(homeworkViewModel => homeworkViewModel.id!), - studentIds: state.selectedStudents.map(accountData => accountData.userId!) - .concat(state.selectedGroups.flatMap(g => g.studentsIds ?? [])), + studentIds: state.selectedStudents.map(accountData => accountData.userId!), groupIds: state.selectedGroups.map(groupViewModel => groupViewModel.id!) } From 66e97c3ea7e1680c3c680d900a17a94282936699 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?=D0=A0=D0=BE=D1=81=D0=B8=D0=BD=20=D0=A1=D0=B5=D0=BC=D1=91?= =?UTF-8?q?=D0=BD?= Date: Sat, 23 May 2026 17:36:58 +0300 Subject: [PATCH 22/23] refactor --- .../HwProj.APIGateway.API/Controllers/CoursesController.cs | 5 +++-- 1 file changed, 3 insertions(+), 2 deletions(-) diff --git a/HwProj.APIGateway/HwProj.APIGateway.API/Controllers/CoursesController.cs b/HwProj.APIGateway/HwProj.APIGateway.API/Controllers/CoursesController.cs index ef6440f48..d8c73cfa8 100644 --- a/HwProj.APIGateway/HwProj.APIGateway.API/Controllers/CoursesController.cs +++ b/HwProj.APIGateway/HwProj.APIGateway.API/Controllers/CoursesController.cs @@ -272,12 +272,13 @@ public async Task GetMentorWorkspace(long courseId, string mentor var studentIds = mentorCourseView.Value.CourseMates.Select(t => t.StudentId).ToArray(); var students = await AuthServiceClient.GetAccountsData(studentIds); - return Ok(new WorkspaceViewModel + var workspace = new WorkspaceViewModel { Homeworks = mentorCourseView.Value.Homeworks, Students = students.OrderBy(x => x.Surname).ThenBy(x => x.Name).ToArray(), Groups = mentorCourseView.Value.Groups, - }); + }; + return Ok(workspace); } private async Task ToCourseViewModel(CourseDTO course) From dda69dcaa142e84133bda1a2f30afe40ab96db9a Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?=D0=A0=D0=BE=D1=81=D0=B8=D0=BD=20=D0=A1=D0=B5=D0=BC=D1=91?= =?UTF-8?q?=D0=BD?= Date: Thu, 28 May 2026 13:12:19 +0300 Subject: [PATCH 23/23] fix: new method interface --- .../HwProj.CoursesService.API/Services/ICourseFilterService.cs | 3 +-- 1 file changed, 1 insertion(+), 2 deletions(-) diff --git a/HwProj.CoursesService/HwProj.CoursesService.API/Services/ICourseFilterService.cs b/HwProj.CoursesService/HwProj.CoursesService.API/Services/ICourseFilterService.cs index 3d46f3a02..f4b3f1c5b 100644 --- a/HwProj.CoursesService/HwProj.CoursesService.API/Services/ICourseFilterService.cs +++ b/HwProj.CoursesService/HwProj.CoursesService.API/Services/ICourseFilterService.cs @@ -1,4 +1,3 @@ -using System.Collections.Generic; using System.Threading.Tasks; using HwProj.CoursesService.API.Models; using HwProj.Models.CoursesService; @@ -16,6 +15,6 @@ public interface ICourseFilterService Task ApplyFilter(CourseDTO courseDto, string userId); Task GetAssignedStudentsIds(long courseId, string[] mentorsIds); Task UpdateGroupFilters(long courseId, long homeworkId, Group group); - Task AddToFilter(long courseId, string filterId, Filter filter); + Task AddToFilter(long courseId, string userId, Filter filter); } } \ No newline at end of file