Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
Show all changes
24 commits
Select commit Hold shift + click to select a range
ac41982
feat: initializing the Redux store
Andrw-404 Dec 30, 2025
75ab88f
feat: add index.ts and hooks.ts
Andrw-404 Dec 30, 2025
fcc3772
feat: upd store
Andrw-404 Dec 31, 2025
37e0b77
feat: upd store
Andrw-404 Dec 31, 2025
21c7785
feat: connect the Redux Provider
Andrw-404 Jan 3, 2026
f4eb53a
refactor: replaced useState with useAppSelector/dispatch in Course.tsx
Andrw-404 Jan 4, 2026
8b6225a
feat: integrate Redux for Course/CourseExperimental
Andrw-404 Jan 4, 2026
e815458
feat: add courseFileSlice.ts and solutionSlice.ts with update index.ts
Andrw-404 Jan 4, 2026
8e6bc54
feat: solutionSlice and courseFileSlice integrate into Course.tsx
Andrw-404 Jan 4, 2026
c895c10
feat: upd slices in store
Andrw-404 Jan 4, 2026
940ddc8
refactor: add-on for Redux integration
Andrw-404 Jan 4, 2026
6990525
feat: upd index.ts
Andrw-404 Jan 4, 2026
3bc1dc6
refactor: integration Course.tsx and StudentStats.tsx
Andrw-404 Jan 4, 2026
7f833b1
feature: upd store
Andrw-404 Jan 7, 2026
7cce748
refactor: integrate MentorsList.tsx with Redux
Andrw-404 Jan 7, 2026
2f93a27
refactor: integrate LecturerStatistics.tsx with Redux
Andrw-404 Jan 7, 2026
5e26fce
refactor: integrate Course.tsx with Redux
Andrw-404 Jan 7, 2026
8d5c801
refactor: integrate CourseExperimental.tsx with Redux
Andrw-404 Jan 7, 2026
d908bef
refactor: integrate NewCourseStudents.tsx with Redux
Andrw-404 Jan 7, 2026
eb6e30b
refactor: integrate StudentStats.tsx with Redux
Andrw-404 Jan 7, 2026
4d07639
refactor: integrate CourseHomeworkExperimental.tsx with Redux
Andrw-404 Jan 7, 2026
7297a21
refactor: integrate CourseTaskExperimental.tsx with Redux
Andrw-404 Jan 7, 2026
0acbad3
Merge remote-tracking branch 'upstream/master' into feature/state-man…
Andrw-404 Jan 7, 2026
4ebee23
refactor: rename key in index.ts
Andrw-404 Jan 7, 2026
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
177 changes: 128 additions & 49 deletions hwproj.front/package-lock.json

Large diffs are not rendered by default.

2 changes: 2 additions & 0 deletions hwproj.front/package.json
Original file line number Diff line number Diff line change
Expand Up @@ -16,6 +16,7 @@
"@mui/lab": "^5.0.0-alpha.99",
"@mui/material": "^5.16.11",
"@mui/x-charts": "^8.2.0",
"@reduxjs/toolkit": "^2.11.2",
"@storybook/addon-knobs": "^6.3.0",
"@types/bluebird": "^3.5.36",
"@types/classnames": "^2.3.1",
Expand Down Expand Up @@ -48,6 +49,7 @@
"react-drag-drop-files": "^3.1.0",
"react-markdown": "^5.0.0",
"react-query": "^3.21.1",
"react-redux": "^9.2.0",
"react-router-dom": "^6.5.0",
"react-social-login-buttons": "^3.5.1",
"react-syntax-highlighter": "^15.5.0",
Expand Down
14 changes: 9 additions & 5 deletions hwproj.front/src/components/Common/MentorsList.tsx
Original file line number Diff line number Diff line change
@@ -1,14 +1,18 @@
import {FC} from "react";
import {AccountDataDto} from "@/api";
import {Typography} from "@material-ui/core";
import * as React from "react";
import {Stack, Tooltip} from "@mui/material";
import { useAppSelector } from "@/store/hooks";
import { AccountDataDto } from "@/api";

const MentorsList: FC<{
mentors: AccountDataDto[]
}> = (props) => {
interface MentorsListProps {
mentors?: AccountDataDto[];
}

const MentorsList: FC<MentorsListProps> = ({ mentors: propMentors }) => {
const reduxMentors = useAppSelector(state => state.course.mentors);
const mentors = propMentors ?? reduxMentors;
const count = 1
const {mentors} = props
const mentorsToShow = mentors.length > count ? mentors.slice(0, count) : mentors
const mentorsToHide = mentors.length > count ? mentors.slice(count) : []
const fontSize = 18
Expand Down
215 changes: 52 additions & 163 deletions hwproj.front/src/components/Courses/Course.tsx

Large diffs are not rendered by default.

81 changes: 47 additions & 34 deletions hwproj.front/src/components/Courses/CourseExperimental.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -4,6 +4,8 @@ import {
HomeworkTaskViewModel,
HomeworkViewModel, Solution, StatisticsCourseMatesModel,
} from "@/api";
import {useAppDispatch, useAppSelector} from "@/store/hooks";
import {updateOrInsertHomework, deleteHomework, updateTask, deleteTask} from "@/store/slices/homeworkSlice";
import {
AlertTitle,
Button,
Expand All @@ -23,7 +25,7 @@ import TimelineContent from '@mui/lab/TimelineContent';
import TimelineDot from '@mui/lab/TimelineDot';
import TimelineOppositeContent from '@mui/lab/TimelineOppositeContent';
import {Alert, Card, CardActions, Chip, Paper, Stack, Tooltip} from "@mui/material";
import {Link} from "react-router-dom";
import {Link, useSearchParams} from "react-router-dom";
import StudentStatsUtils from "../../services/StudentStatsUtils";
import {BonusTag, DefaultTags, getTip, isBonusWork, isTestWork, TestTag} from "../Common/HomeworkTags";
import FileInfoConverter from "components/Utils/FileInfoConverter";
Expand All @@ -37,23 +39,6 @@ import SwitchAccessShortcutIcon from '@mui/icons-material/SwitchAccessShortcut';
import Lodash from "lodash";

interface ICourseExperimentalProps {
homeworks: HomeworkViewModel[]
courseFilesInfo: FileInfoDTO[]
studentSolutions: StatisticsCourseMatesModel[]
courseId: number
isMentor: boolean
isStudentAccepted: boolean
userId: string
selectedHomeworkId: number | undefined
onHomeworkUpdate: (update: { homework: HomeworkViewModel, fileInfos: FileInfoDTO[] | undefined } & {
isDeleted?: boolean
}) => void
onTaskUpdate: (update: { task: HomeworkTaskViewModel, isDeleted?: boolean }) => void,
processingFiles: {
[homeworkId: number]: {
isLoading: boolean;
};
};
onStartProcessing: (homeworkId: number, previouslyExistingFilesCount: number, waitingNewFilesCount: number, deletingFilesIds: number[]) => void;
}

Expand All @@ -66,24 +51,38 @@ interface ICourseExperimentalState {
}

export const CourseExperimental: FC<ICourseExperimentalProps> = (props) => {
const dispatch = useAppDispatch()
const allHomeworks = useAppSelector(state => state.homeworks.homeworks)
const studentSolutions = useAppSelector(state => state.solutions.studentSolutions)
const courseFilesInfo = useAppSelector(state => state.courseFiles.courseFiles)
const mentors = useAppSelector(state => state.course.mentors)
const course = useAppSelector(state => state.course.course)
const acceptedStudents = useAppSelector(state => state.course.acceptedStudents)
const userId = useAppSelector(state => state.auth.userId)

const courseId = course?.id ?? 0
const isAcceptedStudent = acceptedStudents.some(s => s.userId === userId)

const [hideDeferred, setHideDeferred] = useState<boolean>(false)
const [showOnlyGroupedTest, setShowOnlyGroupedTest] = useState<string | undefined>(undefined)
const filterAdded = hideDeferred || showOnlyGroupedTest !== undefined

const isMentor = mentors.some(m => m.userId === userId)
// Определяем разрешение экрана пользователя
const theme = useTheme();
const isMobile = useMediaQuery(theme.breakpoints.down('md'));

// Состояние для кнопки "Наверх"
const [showScrollButton, setShowScrollButton] = useState(false);

const homeworks = props.homeworks.slice().reverse().filter(x => {
const homeworks = allHomeworks.slice().reverse().filter(x => {
if (hideDeferred) return !x.isDeferred
if (showOnlyGroupedTest !== undefined) return x.tags!.includes(TestTag) && x.tags!.includes(showOnlyGroupedTest)
return true
})

const {isMentor, studentSolutions, isStudentAccepted, userId, selectedHomeworkId, courseFilesInfo} = props
const [ searchParams ]= useSearchParams();
const selectedHomeworkId = searchParams.get("homeworkId") ? +searchParams.get("homeworkId")! : undefined

const [state, setState] = useState<ICourseExperimentalState>({
initialEditMode: false,
Expand Down Expand Up @@ -162,7 +161,7 @@ export const CourseExperimental: FC<ICourseExperimentalProps> = (props) => {

const taskSolutionsMap = new Map<number, Solution[]>()

if (!isMentor && isStudentAccepted) {
if (!isMentor && isAcceptedStudent) {
studentSolutions
.filter(t => t.id === userId)
.flatMap(t => t.homeworks!)
Expand Down Expand Up @@ -340,10 +339,28 @@ export const CourseExperimental: FC<ICourseExperimentalProps> = (props) => {

const [newTaskCounter, setNewTaskCounter] = useState<number>(-1)

const handleHomeworkUpdate = (update: { homework: HomeworkViewModel, fileInfos?: FileInfoDTO[], isDeleted?: boolean }) => {
const { homework, isDeleted } = update;
if (isDeleted) {
dispatch(deleteHomework(homework.id!));
} else {
dispatch(updateOrInsertHomework(homework));
}
};

const handleTaskUpdate = (update: { task: HomeworkTaskViewModel, isDeleted?: boolean }) => {
const { task, isDeleted } = update;
if (isDeleted) {
dispatch(deleteTask({homeworkId: task.homeworkId!, taskId: task.id!}));
} else {
dispatch(updateTask(task));
}
}

const addNewHomework = () => {
props.onHomeworkUpdate({
handleHomeworkUpdate({
homework: {
courseId: props.courseId,
courseId: courseId,
title: "Новое задание",
publicationDateNotSet: false,
publicationDate: undefined,
Expand Down Expand Up @@ -398,7 +415,7 @@ export const CourseExperimental: FC<ICourseExperimentalProps> = (props) => {
id
}

props.onTaskUpdate({task})
handleTaskUpdate({task})
setState((prevState) => ({
...prevState,
selectedItem: {
Expand All @@ -418,14 +435,12 @@ export const CourseExperimental: FC<ICourseExperimentalProps> = (props) => {
{isMentor && getDatesAlert(homework, true)}
<CourseHomeworkExperimental
key={homework.id}
getAllHomeworks={() => homeworks}
homeworkAndFilesInfo={{homework, filesInfo}}
isMentor={isMentor}
initialEditMode={initialEditMode || homeworkEditMode}
onMount={onSelectedItemMount}
onAddTask={addNewTask}
onUpdate={update => {
props.onHomeworkUpdate(update)
handleHomeworkUpdate(update)
setState((prevState) => ({
...prevState,
selectedItem: {
Expand All @@ -434,7 +449,6 @@ export const CourseExperimental: FC<ICourseExperimentalProps> = (props) => {
}
}))
}}
isProcessing={props.processingFiles[homework.id!]?.isLoading || false}
onStartProcessing={(homeworkId: number, previouslyExistingFilesCount: number, waitingNewFilesCount: number, deletingFilesIds: number[]) =>
props.onStartProcessing(homeworkId, previouslyExistingFilesCount, waitingNewFilesCount, deletingFilesIds)}
/>
Expand All @@ -450,11 +464,10 @@ export const CourseExperimental: FC<ICourseExperimentalProps> = (props) => {
key={task.id}
task={task}
homework={homework!}
isMentor={isMentor}
initialEditMode={initialEditMode || taskEditMode}
onMount={onSelectedItemMount}
onUpdate={update => {
props.onTaskUpdate(update)
handleTaskUpdate(update)
if (update.isDeleted)
setState((prevState) => ({
...prevState,
Expand All @@ -464,8 +477,8 @@ export const CourseExperimental: FC<ICourseExperimentalProps> = (props) => {
}
}))
}}
toEditHomework={() => toEditHomework(homework!)} getAllHomeworks={() => homeworks}/>
{!props.isMentor && props.isStudentAccepted && < CardActions>
toEditHomework={() => toEditHomework(homework!)}/>
{!isMentor && isAcceptedStudent && < CardActions>
<Link
style={{color: '#212529'}}
to={"/task/" + task.id!.toString()}>
Expand Down Expand Up @@ -516,7 +529,7 @@ export const CourseExperimental: FC<ICourseExperimentalProps> = (props) => {
borderRadius: 10
}
}}>
{props.isMentor && filterAdded && <Stack direction={"column"} alignItems={"center"}>
{isMentor && filterAdded && <Stack direction={"column"} alignItems={"center"}>
<Button
fullWidth
onClick={() => {
Expand All @@ -534,7 +547,7 @@ export const CourseExperimental: FC<ICourseExperimentalProps> = (props) => {
: ""}
</Typography>
</Stack>}
{props.isMentor && !filterAdded && (homeworks[0]?.id || 1) > 0 && <Button
{isMentor && !filterAdded && (homeworks[0]?.id || 1) > 0 && <Button
onClick={addNewHomework}
style={{borderRadius: 8, marginBottom: 10}} variant={"text"} size={"small"}>
+ Добавить задание
Expand Down
27 changes: 12 additions & 15 deletions hwproj.front/src/components/Courses/NewCourseStudents.tsx
Original file line number Diff line number Diff line change
@@ -1,29 +1,26 @@
import * as React from 'react';
import {AccountDataDto, CourseViewModel} from '../../api/';
import ApiSingleton from "../../api/ApiSingleton";
import {FC} from "react";
import {Card, CardContent, CardActions, Grid, Button, Typography, Alert, AlertTitle} from '@mui/material';
import {useAppSelector, useAppDispatch} from "@/store/hooks";
import {fetchCourseData} from '@/store/slices/courseSlice';

interface INewCourseStudentsProps {
course: CourseViewModel,
students: AccountDataDto[],
onUpdate: () => void,
courseId: string,
}

const NewCourseStudents: FC<INewCourseStudentsProps> = (props) => {
const NewCourseStudents: FC = () => {
const course = useAppSelector(state => state.course.course);
const students = useAppSelector(state => state.course.newStudents);
const dispatch = useAppDispatch();

const acceptStudent = async (studentId: string) => {
await ApiSingleton.coursesApi.coursesAcceptStudent(props.course.id!, studentId)
props.onUpdate()
await ApiSingleton.coursesApi.coursesAcceptStudent(course?.id!, studentId)
dispatch(fetchCourseData(course?.id!));
}

const rejectStudent = async (studentId: string) => {
await ApiSingleton.coursesApi.coursesRejectStudent(props.course.id!, studentId)
props.onUpdate()
await ApiSingleton.coursesApi.coursesRejectStudent(course?.id!, studentId)
dispatch(fetchCourseData(course?.id!));
}

const studentsLength = props.students.length
const studentsLength = students.length

if (studentsLength === 0) {
return (
Expand All @@ -36,7 +33,7 @@ const NewCourseStudents: FC<INewCourseStudentsProps> = (props) => {
)
}
return <Grid item container spacing={1} direction={"row"} xs={"auto"}>
{props.students.map((cm, i) => (
{students.map((cm, i) => (
<Grid item>
<Card variant="elevation" style={{backgroundColor: "ghostwhite"}}>
<CardContent>
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -4,18 +4,20 @@ import ApiSingleton from "../../../api/ApiSingleton";
import {Dialog, DialogContent, DialogTitle, Typography, Grid, Tooltip, Chip} from "@mui/material";
import * as React from "react";
import {DotLottieReact} from "@lottiefiles/dotlottie-react";
import { useAppSelector } from "@/store/hooks";

const LecturerStatistics: FC<{
courseId: number
onClose: () => void
}> = (props) => {

const courseId = useAppSelector(state => state.course.course?.id);

const [isLoading, setIsLoading] = useState(true);
const [statistics, setStatistics] = useState<StatisticsLecturersModel[]>([])

const getStatistics = async () => {
const statistics: StatisticsLecturersModel[] =
await ApiSingleton.statisticsApi.statisticsGetLecturersStatistics(props.courseId)
await ApiSingleton.statisticsApi.statisticsGetLecturersStatistics(courseId!);
setStatistics(statistics)
setIsLoading(false);
}
Expand Down
Loading