324 lines
No EOL
12 KiB
TypeScript
324 lines
No EOL
12 KiB
TypeScript
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>
|
||
);
|
||
}; |