2025-05-31 22:12:25 +03:00
|
|
|
|
import React, { useState, useEffect } from "react";
|
2025-06-02 17:30:35 +03:00
|
|
|
|
import { useNavigate, useParams } from "react-router-dom";
|
2025-05-31 22:12:25 +03:00
|
|
|
|
import {
|
|
|
|
|
|
Survey,
|
|
|
|
|
|
Question,
|
|
|
|
|
|
QuestionPair,
|
|
|
|
|
|
AgreementQuestion,
|
|
|
|
|
|
QuestionType,
|
2025-06-02 17:30:35 +03:00
|
|
|
|
|
2025-06-02 19:39:54 +03:00
|
|
|
|
} from "../../types/survey.ts";
|
|
|
|
|
|
import { LoadingData } from "../../components/LoadingData.tsx";
|
|
|
|
|
|
import { LoadingList } from "../../components/LoadingList.tsx";
|
|
|
|
|
|
import {useData, useDataPage} from "../../API/hooks.ts";
|
2025-05-31 10:57:27 +03:00
|
|
|
|
|
|
|
|
|
|
export function SurveyPage() {
|
2025-05-31 22:12:25 +03:00
|
|
|
|
const { surveyId } = useParams<{ surveyId: string }>();
|
|
|
|
|
|
const navigate = useNavigate();
|
|
|
|
|
|
|
|
|
|
|
|
// Загрузка теста
|
|
|
|
|
|
const survey = useData<Survey>("surveys", "surveys", surveyId || 0);
|
|
|
|
|
|
|
|
|
|
|
|
// Загрузка вопросов
|
|
|
|
|
|
const [questions, setQuestions] = useState<Question[] | null>(null);
|
|
|
|
|
|
const questionsPairs = useDataPage<QuestionPair>("surveys", "question-pairs", { survey: surveyId || 0 });
|
|
|
|
|
|
const questionsAgreement = useDataPage<AgreementQuestion>("surveys", "agreement-questions", { survey: surveyId || 0 });
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
// Состояние для ответов
|
|
|
|
|
|
const [answers, setAnswers] = useState<Record<string, number>>({});
|
|
|
|
|
|
const [isCompleted, setIsCompleted] = useState(false);
|
|
|
|
|
|
|
|
|
|
|
|
// Объединение вопросов после загрузки
|
|
|
|
|
|
useEffect(() => {
|
|
|
|
|
|
if (
|
|
|
|
|
|
questions === null &&
|
|
|
|
|
|
questionsPairs.items !== null &&
|
|
|
|
|
|
questionsAgreement.items !== null
|
|
|
|
|
|
) {
|
|
|
|
|
|
const mergedQuestions = [
|
|
|
|
|
|
...questionsPairs.items.map((q:Question) => ({ ...q, questionType: QuestionType.QuestionPair })),
|
|
|
|
|
|
...questionsAgreement.items.map((q:Question) => ({ ...q, questionType: QuestionType.AgreementQuestion })),
|
|
|
|
|
|
];
|
|
|
|
|
|
mergedQuestions.sort(() => Math.random() - 0.5); // Перемешивание
|
|
|
|
|
|
setQuestions(mergedQuestions);
|
|
|
|
|
|
}
|
|
|
|
|
|
}, [questions, questionsPairs.items, questionsAgreement.items]);
|
|
|
|
|
|
|
|
|
|
|
|
// Проверка завершения теста
|
|
|
|
|
|
useEffect(() => {
|
|
|
|
|
|
if (questions && Object.keys(answers).length === questions.length) {
|
|
|
|
|
|
setIsCompleted(true);
|
|
|
|
|
|
} else {
|
|
|
|
|
|
setIsCompleted(false);
|
|
|
|
|
|
}
|
|
|
|
|
|
}, [questions, answers]);
|
2025-05-31 10:57:27 +03:00
|
|
|
|
|
2025-05-31 22:12:25 +03:00
|
|
|
|
// Обработка ответов
|
|
|
|
|
|
const handleAnswer = (questionKey: string, answerValue: number) => {
|
|
|
|
|
|
setAnswers((prev) => ({ ...prev, [questionKey]: answerValue }));
|
|
|
|
|
|
};
|
2025-05-31 10:57:27 +03:00
|
|
|
|
|
2025-05-31 22:12:25 +03:00
|
|
|
|
// Вычисление итогового score_variable
|
|
|
|
|
|
const calculateResults = () => {
|
2025-06-01 16:17:09 +03:00
|
|
|
|
if (!questions) return;
|
2025-05-31 22:12:25 +03:00
|
|
|
|
|
|
|
|
|
|
const scoreCounts: Record<string, number> = {};
|
|
|
|
|
|
|
|
|
|
|
|
questions.forEach((question) => {
|
|
|
|
|
|
const questionKey = `${question.questionType}_${question.id}`;
|
|
|
|
|
|
const answer = answers[questionKey];
|
|
|
|
|
|
if (answer !== undefined) {
|
|
|
|
|
|
if (question.questionType === QuestionType.QuestionPair) {
|
|
|
|
|
|
const selectedStatement = (question as QuestionPair).statements.find(
|
|
|
|
|
|
(s) => s.id === answer
|
|
|
|
|
|
);
|
|
|
|
|
|
if (selectedStatement) {
|
|
|
|
|
|
scoreCounts[selectedStatement.score_variable] =
|
|
|
|
|
|
(scoreCounts[selectedStatement.score_variable] || 0) + 1;
|
|
|
|
|
|
}
|
|
|
|
|
|
} else if (question.questionType === QuestionType.AgreementQuestion) {
|
|
|
|
|
|
const scoreVariable = (question as AgreementQuestion).score_variable;
|
|
|
|
|
|
scoreCounts[scoreVariable] = (scoreCounts[scoreVariable] || 0) + answer;
|
|
|
|
|
|
}
|
|
|
|
|
|
}
|
|
|
|
|
|
});
|
|
|
|
|
|
|
|
|
|
|
|
const maxScoreVariable = Object.keys(scoreCounts).reduce((a, b) =>
|
|
|
|
|
|
scoreCounts[a] > scoreCounts[b] ? a : b
|
|
|
|
|
|
);
|
|
|
|
|
|
|
|
|
|
|
|
navigate(`/surveys/results/${maxScoreVariable}`);
|
|
|
|
|
|
};
|
2025-05-31 10:57:27 +03:00
|
|
|
|
|
|
|
|
|
|
return (
|
|
|
|
|
|
<div className="survey-page">
|
|
|
|
|
|
<LoadingData data={survey}>
|
2025-05-31 22:12:25 +03:00
|
|
|
|
<h1>{survey?.title}</h1>
|
|
|
|
|
|
<div>{survey?.description}</div>
|
|
|
|
|
|
|
|
|
|
|
|
<LoadingList data={questions} listElement={(question: Question) => (
|
|
|
|
|
|
<QuestionBlock
|
|
|
|
|
|
key={`${question.questionType}_${question.id}`} // Используем тип вопроса и id для уникальности
|
|
|
|
|
|
question={question}
|
|
|
|
|
|
onAnswer={handleAnswer}
|
|
|
|
|
|
answered={answers[`${question.questionType}_${question.id}`]} // Учитываем тип вопроса в ключе
|
|
|
|
|
|
/>
|
|
|
|
|
|
)} />
|
|
|
|
|
|
|
2025-06-01 20:55:44 +03:00
|
|
|
|
<>
|
|
|
|
|
|
{isCompleted && (
|
|
|
|
|
|
<button onClick={calculateResults}>Завершить тест</button>
|
|
|
|
|
|
)}
|
|
|
|
|
|
</>
|
2025-05-31 10:57:27 +03:00
|
|
|
|
</LoadingData>
|
|
|
|
|
|
</div>
|
|
|
|
|
|
);
|
|
|
|
|
|
}
|
|
|
|
|
|
|
2025-05-31 22:12:25 +03:00
|
|
|
|
function QuestionBlock({
|
|
|
|
|
|
question,
|
|
|
|
|
|
onAnswer,
|
|
|
|
|
|
answered,
|
|
|
|
|
|
}: {
|
|
|
|
|
|
question: Question;
|
|
|
|
|
|
onAnswer: (questionId: string, answerValue: number) => void;
|
|
|
|
|
|
answered?: number;
|
|
|
|
|
|
}) {
|
2025-05-31 18:15:52 +03:00
|
|
|
|
switch (question.questionType) {
|
|
|
|
|
|
case QuestionType.AgreementQuestion:
|
2025-06-01 20:55:44 +03:00
|
|
|
|
{ const agreementQuestion = question as AgreementQuestion;
|
2025-05-31 18:15:52 +03:00
|
|
|
|
return (
|
2025-05-31 22:12:25 +03:00
|
|
|
|
<div className={`question agreement ${answered !== undefined ? "answered" : "unanswered"}`}>
|
|
|
|
|
|
{agreementQuestion.question}
|
|
|
|
|
|
<button
|
2025-06-01 00:52:20 +03:00
|
|
|
|
className={`answer ${answered === 1 ? "selected" : "idle"}`}
|
2025-05-31 22:12:25 +03:00
|
|
|
|
disabled={answered===1}
|
|
|
|
|
|
onClick={() => onAnswer(`${question.questionType}_${question.id}`, 1)}>Согласен</button>
|
|
|
|
|
|
<button
|
2025-06-01 00:52:20 +03:00
|
|
|
|
className={`answer ${answered === 0 ? "selected" : "idle"}`}
|
2025-05-31 22:12:25 +03:00
|
|
|
|
disabled={answered === 0}
|
|
|
|
|
|
onClick={() => onAnswer(`${question.questionType}_${question.id}`, 0)}>Не согласен</button>
|
|
|
|
|
|
|
2025-05-31 18:15:52 +03:00
|
|
|
|
</div>
|
2025-06-01 20:55:44 +03:00
|
|
|
|
); }
|
2025-05-31 22:12:25 +03:00
|
|
|
|
|
2025-05-31 18:15:52 +03:00
|
|
|
|
case QuestionType.QuestionPair:
|
2025-06-01 20:55:44 +03:00
|
|
|
|
{ const pairQuestion = question as QuestionPair;
|
2025-05-31 18:15:52 +03:00
|
|
|
|
return (
|
2025-05-31 22:12:25 +03:00
|
|
|
|
<div className={`question pair ${answered !== undefined ? "answered" : "unanswered"}`}>
|
|
|
|
|
|
{pairQuestion.statements.map((statement) => (
|
|
|
|
|
|
<button
|
2025-06-01 00:52:20 +03:00
|
|
|
|
className={`answer ${answered === statement.id ? "selected" : "idle"}`}
|
2025-05-31 22:12:25 +03:00
|
|
|
|
disabled={answered === statement.id}
|
|
|
|
|
|
key={statement.id}
|
|
|
|
|
|
onClick={() => onAnswer(`${question.questionType}_${question.id}`, statement.id)}
|
|
|
|
|
|
>
|
|
|
|
|
|
{statement.text}
|
|
|
|
|
|
</button>
|
|
|
|
|
|
))}
|
2025-05-31 18:15:52 +03:00
|
|
|
|
</div>
|
2025-06-01 20:55:44 +03:00
|
|
|
|
); }
|
2025-05-31 22:12:25 +03:00
|
|
|
|
|
2025-05-31 18:15:52 +03:00
|
|
|
|
default:
|
2025-05-31 22:12:25 +03:00
|
|
|
|
return null;
|
2025-05-31 18:15:52 +03:00
|
|
|
|
}
|
|
|
|
|
|
}
|
|
|
|
|
|
|