diff --git a/client/src/api/api.ts b/client/src/api/api.ts index e34b2d312c..4c10d74242 100644 --- a/client/src/api/api.ts +++ b/client/src/api/api.ts @@ -1165,7 +1165,9 @@ export const CourseScheduleItemDtoStatusEnum = { Archived: 'archived', Future: 'future', Missed: 'missed', - Review: 'review' + Review: 'review', + Registered: 'registered', + Unavailable: 'unavailable' } as const; export type CourseScheduleItemDtoStatusEnum = typeof CourseScheduleItemDtoStatusEnum[keyof typeof CourseScheduleItemDtoStatusEnum]; @@ -1176,13 +1178,15 @@ export const CourseScheduleItemDtoTagEnum = { Interview: 'interview', CrossCheckSubmit: 'cross-check-submit', CrossCheckReview: 'cross-check-review', - Test: 'test' + Test: 'test', + TeamDistribution: 'team-distribution' } as const; export type CourseScheduleItemDtoTagEnum = typeof CourseScheduleItemDtoTagEnum[keyof typeof CourseScheduleItemDtoTagEnum]; export const CourseScheduleItemDtoTypeEnum = { CourseTask: 'courseTask', - CourseEvent: 'courseEvent' + CourseEvent: 'courseEvent', + CourseTeamDistribution: 'courseTeamDistribution' } as const; export type CourseScheduleItemDtoTypeEnum = typeof CourseScheduleItemDtoTypeEnum[keyof typeof CourseScheduleItemDtoTypeEnum]; diff --git a/client/src/modules/Schedule/components/StatusTabs/renderers.tsx b/client/src/modules/Schedule/components/StatusTabs/renderers.tsx index 18d1c7a5ee..58cce3da53 100644 --- a/client/src/modules/Schedule/components/StatusTabs/renderers.tsx +++ b/client/src/modules/Schedule/components/StatusTabs/renderers.tsx @@ -10,6 +10,8 @@ const tabsOrder = [ CourseScheduleItemDtoStatusEnum.Future, CourseScheduleItemDtoStatusEnum.Missed, CourseScheduleItemDtoStatusEnum.Done, + CourseScheduleItemDtoStatusEnum.Registered, + CourseScheduleItemDtoStatusEnum.Unavailable, CourseScheduleItemDtoStatusEnum.Archived, ]; diff --git a/client/src/modules/Schedule/utils.ts b/client/src/modules/Schedule/utils.ts index 986ccafc81..1e5802daa6 100644 --- a/client/src/modules/Schedule/utils.ts +++ b/client/src/modules/Schedule/utils.ts @@ -26,6 +26,10 @@ export function getTaskStatusColor(value: CourseScheduleItemDtoStatusEnum) { return '#13c2c2'; case CourseScheduleItemDtoStatusEnum.Review: return '#722ed1'; + case CourseScheduleItemDtoStatusEnum.Registered: + return '#73d13d'; + case CourseScheduleItemDtoStatusEnum.Unavailable: + return '#faad14'; default: return '#d9d9d9'; } diff --git a/nestjs/src/courses/course-schedule/course-schedule.service.ts b/nestjs/src/courses/course-schedule/course-schedule.service.ts index bedd106d29..d87f3196a7 100644 --- a/nestjs/src/courses/course-schedule/course-schedule.service.ts +++ b/nestjs/src/courses/course-schedule/course-schedule.service.ts @@ -12,6 +12,8 @@ import { Repository } from 'typeorm'; import { EventType } from '../course-events/dto/course-event.dto'; import { Course } from '@entities/course'; import * as dayjs from 'dayjs'; +import { TeamDistribution } from '@entities/teamDistribution'; +import { TeamDistributionStudent } from '@entities/teamDistributionStudent'; export type CourseScheduleItem = Pick & Partial> & { @@ -29,6 +31,7 @@ export type CourseScheduleItem = Pick & export enum CourseScheduleDataSource { CourseTask = 'courseTask', CourseEvent = 'courseEvent', + CourseTeamDistribution = 'courseTeamDistribution', } export enum CourseScheduleItemTag { @@ -39,6 +42,7 @@ export enum CourseScheduleItemTag { CrossCheckSubmit = 'cross-check-submit', CrossCheckReview = 'cross-check-review', Test = 'test', + TeamDistribution = 'team-distribution', } export enum CourseScheduleItemStatus { @@ -48,6 +52,8 @@ export enum CourseScheduleItemStatus { Future = 'future', Missed = 'missed', Review = 'review', + Registered = 'registered', + Unavailable = 'unavailable', } @Injectable() @@ -69,6 +75,10 @@ export class CourseScheduleService { readonly taskSolutionRepository: Repository, @InjectRepository(TaskChecker) readonly taskCheckerRepository: Repository, + @InjectRepository(TeamDistribution) + readonly courseTeamDistributionRepository: Repository, + @InjectRepository(TeamDistributionStudent) + private teamDistributionStudentRepository: Repository, ) {} private scheduleSort(a: CourseScheduleItem, b: CourseScheduleItem) { @@ -89,18 +99,86 @@ export class CourseScheduleService { return aTagPriority - bTagPriority; } + private async getCourseTeamDistributions(courseId: number, studentId?: number) { + return this.courseTeamDistributionRepository.find({ + where: { courseId }, + cache: studentId ? 90 * 1000 : undefined, + }); + } + + private async getTeamDistributionStudents(courseId: number, studentId?: number) { + if (!studentId) { + return []; + } + + const teamDistributionStudents = await this.teamDistributionStudentRepository.find({ + where: { courseId, studentId }, + relations: { student: true }, + }); + return teamDistributionStudents; + } + + private getTeamDistributionStatus( + teamDistribution: TeamDistribution, + teamDistributionStudents: TeamDistributionStudent[], + ) { + const currTimestampUTC = dayjs(); + const distributionStartDate = dayjs(teamDistribution.startDate); + const teamDistributionStudent = teamDistributionStudents.find(el => el.teamDistributionId === teamDistribution.id); + + if (currTimestampUTC < distributionStartDate) { + return CourseScheduleItemStatus.Future; + } + + if (teamDistributionStudent?.distributed) { + return CourseScheduleItemStatus.Done; + } + + if (teamDistributionStudent?.active) { + return CourseScheduleItemStatus.Registered; + } + + if ( + teamDistributionStudent?.student == null || + teamDistributionStudent.student.isExpelled || + teamDistribution.minTotalScore > teamDistributionStudent.student.totalScore + ) { + return CourseScheduleItemStatus.Unavailable; + } + + const distributionEndDate = dayjs(teamDistribution.endDate); + if (currTimestampUTC <= distributionEndDate && currTimestampUTC >= distributionStartDate) { + return CourseScheduleItemStatus.Available; + } + + if (currTimestampUTC > distributionEndDate) { + return CourseScheduleItemStatus.Missed; + } + + return CourseScheduleItemStatus.Unavailable; + } + public async getAll(courseId: number, studentId?: number): Promise { - const [courseTasks, courseEvents] = await Promise.all([ + const [courseTasks, courseEvents, teamDistribution] = await Promise.all([ this.getActiveCourseTasks(courseId, studentId), this.getCourseEvents(courseId, studentId), + this.getCourseTeamDistributions(courseId, studentId), ]); - const [taskResults, interviewResults, technicalScreeningResults, taskSolutions, taskCheckers] = await Promise.all([ + const [ + taskResults, + interviewResults, + technicalScreeningResults, + taskSolutions, + taskCheckers, + teamDistributionStudents, + ] = await Promise.all([ this.getTaskResults(studentId), this.getInterviewResults(studentId), this.getPrescreeningResults(studentId), this.getTaskSolutions(studentId), this.getTaskCheckers(studentId), + this.getTeamDistributionStudents(courseId, studentId), ]); const schedule = courseTasks @@ -164,6 +242,22 @@ export class CourseScheduleService { } as CourseScheduleItem; }), ) + .concat( + teamDistribution.map(teamDistribution => { + teamDistribution.description; + return { + id: teamDistribution.id, + name: teamDistribution.name, + courseId, + startDate: teamDistribution.startDate, + endDate: teamDistribution.endDate, + status: this.getTeamDistributionStatus(teamDistribution, teamDistributionStudents), + type: CourseScheduleDataSource.CourseTeamDistribution, + tag: CourseScheduleItemTag.TeamDistribution, + descriptionUrl: teamDistribution.descriptionUrl, + }; + }), + ) .sort(this.scheduleSort); return schedule; diff --git a/nestjs/src/spec.json b/nestjs/src/spec.json index 58b8473595..fc5eb7da7b 100644 --- a/nestjs/src/spec.json +++ b/nestjs/src/spec.json @@ -3682,7 +3682,10 @@ "score": { "type": "number", "nullable": true }, "name": { "type": "string" }, "id": { "type": "number" }, - "status": { "type": "string", "enum": ["done", "available", "archived", "future", "missed", "review"] }, + "status": { + "type": "string", + "enum": ["done", "available", "archived", "future", "missed", "review", "registered", "unavailable"] + }, "startDate": { "type": "string" }, "endDate": { "type": "string" }, "crossCheckEndDate": { "type": "string" }, @@ -3692,9 +3695,18 @@ "descriptionUrl": { "type": "string", "nullable": true }, "tag": { "type": "string", - "enum": ["lecture", "coding", "self-study", "interview", "cross-check-submit", "cross-check-review", "test"] + "enum": [ + "lecture", + "coding", + "self-study", + "interview", + "cross-check-submit", + "cross-check-review", + "test", + "team-distribution" + ] }, - "type": { "type": "string", "enum": ["courseTask", "courseEvent"] } + "type": { "type": "string", "enum": ["courseTask", "courseEvent", "courseTeamDistribution"] } }, "required": [ "score",