Skip to content

Commit 3800d22

Browse files
committed
QAGDEV-723 - [FE] Прикрепления тестов к заданиям v3
1 parent 869df43 commit 3800d22

File tree

14 files changed

+358
-10
lines changed

14 files changed

+358
-10
lines changed

src/api/graphql/lecture/lecture.graphql

Lines changed: 13 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -14,6 +14,19 @@ query lecture($id: ID!) {
1414
subject
1515
description
1616
content
17+
testGroup {
18+
id
19+
testName
20+
successThreshold
21+
testQuestions {
22+
id
23+
text
24+
testAnswers {
25+
id
26+
text
27+
}
28+
}
29+
}
1730
files {
1831
id
1932
homeWork

src/api/graphql/lecture/update-lecture.graphql

Lines changed: 5 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -5,6 +5,11 @@ mutation updateLecture($input: LectureInput!) {
55
description
66
content
77
contentHomeWork
8+
testGroup {
9+
id
10+
testName
11+
successThreshold
12+
}
813
speakers {
914
id
1015
firstName

src/features/edit-training/containers/index.ts

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -7,3 +7,4 @@ export { default as DeleteLecture } from "./delete-lecture";
77
export { default as CreateLecture } from "./create-lecture";
88
export { default as SelectLecture } from "./select-lecture";
99
export { default as AddLecture } from "./add-lecture";
10+
export { default as SelectTests } from "./select-tests";
Lines changed: 102 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,102 @@
1+
import { FC } from "react";
2+
import { Control , Controller } from "react-hook-form";
3+
import {
4+
FormControl,
5+
InputLabel,
6+
Select,
7+
MenuItem,
8+
FormHelperText,
9+
Box,
10+
Typography,
11+
Chip,
12+
} from "@mui/material";
13+
14+
import { useTestTestGroupsQuery } from "api/graphql/generated/graphql";
15+
import { AppSpinner } from "shared/components/spinners";
16+
17+
interface SelectTestsProps {
18+
name: string;
19+
control: Control<any>;
20+
label?: string;
21+
helperText?: string;
22+
}
23+
24+
const SelectTests: FC<SelectTestsProps> = ({
25+
name,
26+
control,
27+
label = "Тест для лекции",
28+
helperText,
29+
}) => {
30+
const { data: testsData, loading, error } = useTestTestGroupsQuery();
31+
32+
const tests = testsData?.testTestGroups || [];
33+
34+
if (loading) {
35+
return (
36+
<Box sx={{ display: "flex", alignItems: "center", gap: 2, py: 2 }}>
37+
<AppSpinner />
38+
<Typography variant="body2" color="text.secondary">
39+
Загрузка тестов...
40+
</Typography>
41+
</Box>
42+
);
43+
}
44+
45+
if (error) {
46+
return (
47+
<Typography variant="body2" color="error">
48+
Ошибка загрузки тестов
49+
</Typography>
50+
);
51+
}
52+
53+
return (
54+
<Controller
55+
name={name}
56+
control={control}
57+
render={({ field, fieldState: { error: fieldError } }) => (
58+
<FormControl fullWidth error={!!fieldError}>
59+
<InputLabel id={`${name}-label`}>{label}</InputLabel>
60+
<Select
61+
labelId={`${name}-label`}
62+
label={label}
63+
value={field.value || ""}
64+
onChange={(e) => field.onChange(e.target.value)}
65+
displayEmpty
66+
>
67+
<MenuItem value="">
68+
<em>Без теста</em>
69+
</MenuItem>
70+
{tests.map((test) => (
71+
<MenuItem key={test?.id} value={test?.id || ""}>
72+
<Box
73+
sx={{
74+
display: "flex",
75+
alignItems: "center",
76+
gap: 1,
77+
width: "100%",
78+
}}
79+
>
80+
<Typography variant="body1" sx={{ flexGrow: 1 }}>
81+
{test?.testName}
82+
</Typography>
83+
<Chip
84+
label={`${test?.successThreshold}%`}
85+
size="small"
86+
color="primary"
87+
variant="outlined"
88+
/>
89+
</Box>
90+
</MenuItem>
91+
))}
92+
</Select>
93+
{(fieldError || helperText) && (
94+
<FormHelperText>{fieldError?.message || helperText}</FormHelperText>
95+
)}
96+
</FormControl>
97+
)}
98+
/>
99+
);
100+
};
101+
102+
export default SelectTests;

src/features/edit-training/views/edit-lecture/edit-lecture.tsx

Lines changed: 25 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -24,7 +24,7 @@ import {
2424
useLectureHomeworkFileUpload,
2525
} from "shared/hooks";
2626

27-
import { SelectLectors } from "../../containers";
27+
import { SelectLectors, SelectTests } from "../../containers";
2828
import {
2929
StyledButtonsStack,
3030
StyledContinueButton,
@@ -42,7 +42,13 @@ const EditLecture: FC<IEditLecture> = ({
4242
updateLecture,
4343
dataLectureHomework,
4444
}) => {
45-
const { id: lectureId, subject, speakers, content } = dataLecture.lecture!;
45+
const {
46+
id: lectureId,
47+
subject,
48+
speakers,
49+
content,
50+
testGroup,
51+
} = dataLecture.lecture!;
4652
const contentHomework = dataLectureHomework.lectureHomeWork;
4753
const { enqueueSnackbar } = useSnackbar();
4854
const navigate = useNavigate();
@@ -66,7 +72,13 @@ const EditLecture: FC<IEditLecture> = ({
6672
const rteRefContentHomeWork = useRef<RichTextEditorRef>(null);
6773

6874
const { handleSubmit, control } = useForm({
69-
defaultValues: { id: lectureId, subject, description, speakers },
75+
defaultValues: {
76+
id: lectureId,
77+
subject,
78+
description,
79+
speakers,
80+
testGroupId: testGroup?.id || "",
81+
},
7082
});
7183

7284
const onSubmit: SubmitHandler<LectureInput> = async (data) => {
@@ -248,6 +260,16 @@ const EditLecture: FC<IEditLecture> = ({
248260
/>
249261
</StyledInfoStack>
250262
</StyledPaper>
263+
<StyledPaper>
264+
<StyledInfoStack>
265+
<Typography variant="h3">Тест для лекции</Typography>
266+
<SelectTests
267+
name="testGroupId"
268+
control={control}
269+
helperText="Выберите тест, который будет доступен студентам после изучения материалов лекции"
270+
/>
271+
</StyledInfoStack>
272+
</StyledPaper>
251273
<StyledPaper>
252274
<StyledInfoStack>
253275
<Typography variant="h3">Материалы урока</Typography>

src/features/edit-training/views/edit-lecture/edit-lecture.types.ts

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -23,4 +23,5 @@ export type LectureInput = {
2323
id?: InputMaybe<Scalars["ID"]>;
2424
speakers?: InputMaybe<Array<InputMaybe<FieldValues>>>;
2525
subject?: InputMaybe<Scalars["String"]>;
26+
testGroupId?: InputMaybe<Scalars["ID"]>;
2627
};

src/features/lecture-detail/views/lecture-detail/lecture-detail.tsx

Lines changed: 14 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -11,6 +11,7 @@ import LectureContent from "../lecture-content";
1111
import { HomeworksFormProvider } from "../../context/homeworks-other-students-form-context";
1212
import StepperButtons from "../stepper-buttons";
1313
import HomeworkSection from "../homework-section";
14+
import LectureTestSection from "../lecture-test-section";
1415

1516
const LectureDetail: FC<ILectureDetail> = (props) => {
1617
const {
@@ -20,10 +21,12 @@ const LectureDetail: FC<ILectureDetail> = (props) => {
2021
tariffHomework,
2122
trainingId,
2223
} = props;
23-
const { subject, description, speakers, content } = dataLecture.lecture || {};
24+
const { subject, description, speakers, content, testGroup } =
25+
dataLecture.lecture || {};
2426
const lectureHomeWork = dataLectureHomework?.lectureHomeWork;
2527

2628
const hasHomework = !!lectureHomeWork;
29+
const hasTest = !!testGroup;
2730

2831
const [view, setView] = useState("kanban");
2932

@@ -41,13 +44,23 @@ const LectureDetail: FC<ILectureDetail> = (props) => {
4144
/>
4245
);
4346

47+
const renderTest = () =>
48+
hasTest && (
49+
<LectureTestSection
50+
testGroup={testGroup}
51+
trainingId={trainingId}
52+
lectureId={dataLecture.lecture?.id}
53+
/>
54+
);
55+
4456
return (
4557
<HomeworksFormProvider>
4658
<Container>
4759
<LectureTitle title={subject} />
4860
<LectureDescription description={description} />
4961
<LectureSpeakers speakers={speakers} />
5062
<LectureContent content={content} />
63+
{renderTest()}
5164
{!tariffHomework ? <BlurredHomework /> : renderHomework()}
5265
<StepperButtons
5366
dataTrainingLectures={dataTrainingLectures}
Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1 @@
1+
export { default } from "./lecture-test-section";
Lines changed: 110 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,110 @@
1+
import { FC } from "react";
2+
import { useNavigate } from "react-router-dom";
3+
import {
4+
Box,
5+
Typography,
6+
Card,
7+
CardContent,
8+
Button,
9+
Chip,
10+
Stack,
11+
Alert,
12+
} from "@mui/material";
13+
import {
14+
Quiz as QuizIcon,
15+
PlayArrow as PlayIcon,
16+
EmojiEvents as TrophyIcon,
17+
} from "@mui/icons-material";
18+
19+
import { TestGroupDto } from "api/graphql/generated/graphql";
20+
21+
interface LectureTestSectionProps {
22+
testGroup: TestGroupDto;
23+
trainingId?: string;
24+
lectureId?: string;
25+
}
26+
27+
const LectureTestSection: FC<LectureTestSectionProps> = ({
28+
testGroup,
29+
trainingId,
30+
lectureId,
31+
}) => {
32+
const navigate = useNavigate();
33+
34+
const handleStartTest = () => {
35+
// Проверяем наличие всех необходимых параметров
36+
if (!testGroup?.id || !trainingId || !lectureId) {
37+
console.error("Missing required parameters for test navigation:", {
38+
testId: testGroup?.id,
39+
trainingId,
40+
lectureId,
41+
});
42+
return;
43+
}
44+
45+
// Переходим на страницу теста с параметрами
46+
navigate(`/test/${testGroup.id}/${trainingId}/${lectureId}`);
47+
};
48+
49+
return (
50+
<Box sx={{ my: 4 }}>
51+
<Typography
52+
variant="h4"
53+
gutterBottom
54+
sx={{ display: "flex", alignItems: "center", gap: 1 }}
55+
>
56+
<QuizIcon color="primary" />
57+
Тест по лекции
58+
</Typography>
59+
60+
<Card elevation={3}>
61+
<CardContent>
62+
<Box sx={{ mb: 3 }}>
63+
<Typography variant="h5" gutterBottom>
64+
{testGroup.testName}
65+
</Typography>
66+
67+
<Stack direction="row" spacing={2} sx={{ mb: 2 }}>
68+
<Chip
69+
icon={<TrophyIcon />}
70+
label={`Проходной балл: ${testGroup.successThreshold}%`}
71+
color="primary"
72+
variant="outlined"
73+
/>
74+
<Chip
75+
label={`Вопросов: ${testGroup.testQuestions?.length || 0}`}
76+
variant="outlined"
77+
/>
78+
</Stack>
79+
</Box>
80+
81+
<Alert severity="info" sx={{ mb: 3 }}>
82+
<Typography variant="body2">
83+
Пройдите тест, чтобы закрепить изученный материал. Для успешного
84+
прохождения необходимо набрать минимум{" "}
85+
{testGroup.successThreshold}% правильных ответов.
86+
</Typography>
87+
</Alert>
88+
89+
<Box sx={{ display: "flex", justifyContent: "center" }}>
90+
<Button
91+
variant="contained"
92+
size="large"
93+
startIcon={<PlayIcon />}
94+
onClick={handleStartTest}
95+
sx={{
96+
py: 1.5,
97+
px: 4,
98+
fontSize: "1.1rem",
99+
}}
100+
>
101+
Начать тестирование
102+
</Button>
103+
</Box>
104+
</CardContent>
105+
</Card>
106+
</Box>
107+
);
108+
};
109+
110+
export default LectureTestSection;

src/features/test/containers/test-container.tsx

Lines changed: 21 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -28,9 +28,25 @@ interface UserAnswer {
2828
answerId: string;
2929
}
3030

31-
const TestContainer: FC = () => {
32-
// Для тестирования используем захардкоженный ID
33-
const testId = "5";
31+
interface TestContainerProps {
32+
testId: string;
33+
trainingId: string;
34+
lectureId: string;
35+
}
36+
37+
const TestContainer: FC<TestContainerProps> = ({
38+
testId,
39+
trainingId,
40+
lectureId,
41+
}) => {
42+
// Отладочная информация
43+
console.log("🔍 TestContainer Debug Info:", {
44+
testId,
45+
trainingId,
46+
lectureId,
47+
});
48+
49+
// Убираем захардкоженный testId
3450
const [userAnswers, setUserAnswers] = useState<UserAnswer[]>([]);
3551
const [isCompleted, setIsCompleted] = useState(false);
3652
const [score, setScore] = useState(0);
@@ -166,6 +182,8 @@ const TestContainer: FC = () => {
166182
currentQuestionIndex={currentQuestionIndex}
167183
totalQuestions={testQuestions.length}
168184
isCurrentQuestionAnswered={isCurrentQuestionAnswered}
185+
trainingId={trainingId}
186+
lectureId={lectureId}
169187
onAnswerSelect={handleAnswerSelect}
170188
onNextQuestion={handleNextQuestion}
171189
onSubmitTest={handleSubmitTest}

0 commit comments

Comments
 (0)