survey-webapp/SurveyFrontend/src/components/SurveyPage/SurveyPage.tsx
2025-06-02 16:00:19 +05:00

324 lines
No EOL
12 KiB
TypeScript
Raw Blame History

This file contains ambiguous Unicode characters

This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.

import React, { useEffect, useState } from "react";
import SurveyInfo from "../SurveyInfo/SurveyInfo.tsx";
import QuestionsList, { Question } from "../QuestionsList/QuestionsList.tsx";
import { ISurvey, updateSurvey } from "../../api/SurveyApi.ts";
import {useOutletContext} from "react-router-dom";
import { addNewQuestion, getListQuestions, updateQuestion, deleteQuestion } from "../../api/QuestionApi.ts";
import styles from "./SurveyPage.module.css";
import SaveButton from "../SaveButton/SaveButton.tsx";
import { addNewAnswerVariant, deleteAnswerVariant, getAnswerVariants, IAnswerVariant, updateAnswerVariant } from "../../api/AnswerVariantsApi.ts";
type ActionType =
| 'update-survey'
| 'create-question'
| 'update-question'
| 'delete-question'
| 'create-answer'
| 'update-answer'
| 'delete-answer';
interface SurveyActionData {
id: number;
title: string;
description: string;
}
interface QuestionActionData {
surveyId: number;
id?: number;
title: string;
questionType: 'SingleAnswerQuestion' | 'MultipleAnswerQuestion';
}
interface AnswerActionData {
surveyId: number;
questionId: number;
id?: number;
text: string;
}
interface Action {
type: ActionType;
data: SurveyActionData | QuestionActionData | AnswerActionData;
tempId?: number;
}
class ActionQueue {
private actions: Action[] = [];
private idMap: Record<number, number> = {};
add(action: Action) {
this.actions.push(action);
}
async execute() {
for (const action of this.actions) {
try {
switch (action.type) {
case 'update-survey':
await this.handleUpdateSurvey(action.data as SurveyActionData);
break;
case 'create-question': {
const createdQuestion = await this.handleCreateQuestion(action.data as QuestionActionData);
if (action.tempId) {
this.idMap[action.tempId] = createdQuestion.id;
}
break;
}
case 'update-question':
await this.handleUpdateQuestion(action.data as QuestionActionData & { id: number });
break;
case 'delete-question':
await this.handleDeleteQuestion(action.data as QuestionActionData & { id: number });
break;
case 'create-answer': {
const answerData = action.data as AnswerActionData;
await this.handleCreateAnswer({
...answerData,
questionId: this.idMap[answerData.questionId] || answerData.questionId
});
break;
}
case 'update-answer': {
const updateAnswerData = action.data as AnswerActionData & { id: number };
await this.handleUpdateAnswer({
...updateAnswerData,
questionId: this.idMap[updateAnswerData.questionId] || updateAnswerData.questionId
});
break;
}
case 'delete-answer': {
const deleteAnswerData = action.data as AnswerActionData & { id: number };
await this.handleDeleteAnswer({
...deleteAnswerData,
questionId: this.idMap[deleteAnswerData.questionId] || deleteAnswerData.questionId
});
break;
}
}
} catch (error) {
console.error(`Failed to execute action ${action.type}:`, error);
throw error;
}
}
}
private async handleUpdateSurvey(data: SurveyActionData) {
return await updateSurvey(data.id, {
title: data.title,
description: data.description
});
}
private async handleCreateQuestion(data: QuestionActionData) {
return await addNewQuestion(data.surveyId, {
title: data.title,
questionType: data.questionType
});
}
private async handleUpdateQuestion(data: QuestionActionData & { id: number }) {
return await updateQuestion(data.id, {
title: data.title,
questionType: data.questionType
});
}
private async handleDeleteQuestion(data: QuestionActionData & { id: number }) {
return await deleteQuestion(data.id);
}
private async handleCreateAnswer(data: AnswerActionData) {
return await addNewAnswerVariant(data.surveyId, data.questionId, {
text: data.text
});
}
private async handleUpdateAnswer(data: AnswerActionData & { id: number }) {
try {
const result = await updateAnswerVariant(data.id, {
text: data.text
});
return result;
} catch (error) {
console.error(error);
throw error;
}
}
private async handleDeleteAnswer(data: AnswerActionData & { id: number }) {
return await deleteAnswerVariant(data.id);
}
}
export const SurveyPage: React.FC = () => {
const [questions, setQuestions] = useState<Question[]>([]);
const [loading, setLoading] = useState(true);
const [error, setError] = useState<string | null>(null);
const [description, setDescription] = useState('');
const [title, setTitle] = useState('');
const { survey, setSurvey } = useOutletContext<{
survey: ISurvey;
setSurvey: (survey: ISurvey) => void;
}>();
useEffect(() => {
if (!survey.id) {
console.error('Survey ID is missing');
return;
}
const id = survey.id;
if (isNaN(id)) {
console.error('Invalid survey ID');
return;
}
const fetchData = async () => {
try {
setLoading(true);
setSurvey(survey);
setTitle(survey.title);
setDescription(survey.description);
const questionsData = await getListQuestions(id);
const formattedQuestions = await Promise.all(questionsData.map(async q => {
const answerVariants = await getAnswerVariants(id, q.id);
return {
id: q.id,
text: q.title,
questionType: q.questionType as 'SingleAnswerQuestion' | 'MultipleAnswerQuestion',
answerVariants: answerVariants.map((a: IAnswerVariant) => ({
id: a.id,
text: a.text
}))
};
}));
setQuestions(formattedQuestions);
} catch (error) {
console.error('Ошибка:', error);
setError('Не удалось загрузить опрос');
} finally {
setLoading(false);
}
};
fetchData();
}, [survey.id]);
const handleSave = async () => {
if (!survey.id || !survey) return;
try {
setError(null);
const id = survey.id;
const actionQueue = new ActionQueue();
actionQueue.add({
type: 'update-survey',
data: { id, title, description } as SurveyActionData
});
const serverQuestions = await getListQuestions(id);
questions.forEach(question => {
const serverQuestion = serverQuestions.find(q => q.id === question.id);
if (serverQuestion &&
(serverQuestion.title !== question.text ||
serverQuestion.questionType !== question.questionType)) {
actionQueue.add({
type: 'update-question',
data: {
surveyId: id,
id: question.id,
title: question.text,
questionType: question.questionType // Убедитесь, что передается новый тип
} as QuestionActionData & { id: number }
});
}
});
questions.forEach(question => {
question.answerVariants.forEach(answer => {
if (!answer.id) {
actionQueue.add({
type: 'create-answer',
data: {
surveyId: id,
questionId: question.id,
text: answer.text
} as AnswerActionData
});
} else {
actionQueue.add({
type: 'update-answer',
data: {
surveyId: id,
questionId: question.id,
id: answer.id,
text: answer.text
} as AnswerActionData & { id: number }
});
}
});
});
serverQuestions.forEach(serverQuestion => {
if (!questions.some(q => q.id === serverQuestion.id)) {
actionQueue.add({
type: 'delete-question',
data: {
surveyId: id,
id: serverQuestion.id
} as QuestionActionData & { id: number }
});
}
});
await actionQueue.execute();
const updatedQuestions = await getListQuestions(id);
const formattedQuestions = await Promise.all(updatedQuestions.map(async q => {
const answerVariants = await getAnswerVariants(id, q.id);
return {
id: q.id,
text: q.title,
questionType: q.questionType as 'SingleAnswerQuestion' | 'MultipleAnswerQuestion',
answerVariants: answerVariants.map((a: IAnswerVariant) => ({
id: a.id,
text: a.text
}))
};
}));
setQuestions(formattedQuestions);
} catch (error) {
console.error('Ошибка сохранения:', error);
setError(`Ошибка сохранения: ${error instanceof Error ? error.message : 'Неизвестная ошибка'}`);
}
};
if (loading) return <div>Загрузка...</div>;
if (!survey) return <div>Опрос не найден</div>;
return (
<div className={styles.survey_page}>
<SurveyInfo
titleSurvey={title}
descriptionSurvey={description}
setDescriptionSurvey={setDescription}
setTitleSurvey={setTitle}
/>
<QuestionsList
questions={questions}
setQuestions={setQuestions}
surveyId={survey.id}
/>
{error && <div className={styles.error}>{error}</div>}
<SaveButton onClick={handleSave}/>
</div>
);
};