fix interaction with the survey

This commit is contained in:
Tatiana Nikolaeva 2025-05-25 18:36:20 +05:00
parent e961d53d6c
commit 15ec6b9632
9 changed files with 495 additions and 175 deletions

View file

@ -1,5 +1,15 @@
import {BASE_URL, createRequestConfig, handleResponse} from "./BaseApi.ts"; import {BASE_URL, createRequestConfig, handleResponse} from "./BaseApi.ts";
export interface INewAnswer{
text: string;
}
export interface IAnswerVariant extends INewAnswer{
surveyId: number;
id: number;
questionId: number;
}
export const getAnswerVariants = async (surveyId: number, questionId: number) => { export const getAnswerVariants = async (surveyId: number, questionId: number) => {
try{ try{
const response = await fetch(`${BASE_URL}/surveys/${surveyId}/questions/${questionId}/answerVariants`, { const response = await fetch(`${BASE_URL}/surveys/${surveyId}/questions/${questionId}/answerVariants`, {
@ -12,7 +22,7 @@ export const getAnswerVariants = async (surveyId: number, questionId: number) =>
} }
} }
export const addNewAnswerVariant = async (surveyId: number, questionId: number) => { export const addNewAnswerVariant = async (surveyId: number, questionId: number, answer: INewAnswer) => {
const token = localStorage.getItem("token"); const token = localStorage.getItem("token");
if (!token) { if (!token) {
throw new Error("Токен отсутствует"); throw new Error("Токен отсутствует");
@ -22,8 +32,7 @@ export const addNewAnswerVariant = async (surveyId: number, questionId: number)
const response = await fetch(`${BASE_URL}/surveys/${surveyId}/questions/${questionId}/answerVariants`, { const response = await fetch(`${BASE_URL}/surveys/${surveyId}/questions/${questionId}/answerVariants`, {
...createRequestConfig('POST'), ...createRequestConfig('POST'),
body: JSON.stringify({ body: JSON.stringify({
surveyId: surveyId, text: answer.text,
questionId: questionId,
}), }),
}) })
@ -38,7 +47,7 @@ export const addNewAnswerVariant = async (surveyId: number, questionId: number)
} }
} }
export const updateAnswerVariant = async (surveyId: number, questionId: number, id: number) => { export const updateAnswerVariant = async (surveyId: number, questionId: number, id: number, answer: INewAnswer): Promise<INewAnswer> => {
const token = localStorage.getItem("token"); const token = localStorage.getItem("token");
if (!token) { if (!token) {
throw new Error("Токен отсутствует"); throw new Error("Токен отсутствует");
@ -48,18 +57,18 @@ export const updateAnswerVariant = async (surveyId: number, questionId: number,
const response = await fetch(`${BASE_URL}/surveys/${surveyId}/questions/${questionId}/answerVariants/${id}`, { const response = await fetch(`${BASE_URL}/surveys/${surveyId}/questions/${questionId}/answerVariants/${id}`, {
...createRequestConfig('PUT'), ...createRequestConfig('PUT'),
body: JSON.stringify({ body: JSON.stringify({
surveyId: surveyId, text: answer.text,
questionId: questionId,
id: id
}) })
}) })
if (!response.ok) { if (!response.ok) {
throw new Error(`Ошибка: ${response.status}`); const errorData = await response.json().catch(() => null);
throw new Error(`Ошибка ${response.status}: ${errorData?.message || 'Неизвестная ошибка'}`);
} }
return await handleResponse(response) return await handleResponse(response)
} }
catch(err){ catch(err){
console.error(`Error updating the response option: ${err}`); console.error(`Error updating the response option: ${err}`);
throw err;
} }
} }

View file

@ -11,7 +11,7 @@ interface IRegistrationData extends IAuthData{
lastName: string; lastName: string;
} }
export const getCurrentUser = async (): Promise<IRegistrationData> => { export const getCurrentUser = async (user: IRegistrationData) => {
const token = localStorage.getItem("token"); const token = localStorage.getItem("token");
if (!token) { if (!token) {
@ -23,7 +23,12 @@ export const getCurrentUser = async (): Promise<IRegistrationData> => {
...createRequestConfig('GET'), ...createRequestConfig('GET'),
headers: { headers: {
'Authorization': `Bearer ${token}` 'Authorization': `Bearer ${token}`
} },
body: JSON.stringify({
email: user.email,
firstName: user.firstName,
lastName: user.lastName,
})
}); });
console.log(response); console.log(response);

View file

@ -1,16 +1,11 @@
import {BASE_URL, createRequestConfig, handleResponse} from "./BaseApi.ts"; import {BASE_URL, createRequestConfig, handleResponse} from "./BaseApi.ts";
import {IAnswerVariant} from "./AnswerApi.ts";
export interface INewQuestion{ export interface INewQuestion{
title: string; title: string;
questionType: string; questionType: string;
} }
export interface IAnswerVariant{
id: number;
questionId: number;
text: string;
}
export interface IQuestion extends INewQuestion { export interface IQuestion extends INewQuestion {
id: number; id: number;
surveyId: number; surveyId: number;

View file

@ -25,12 +25,12 @@ export const MySurveyList = () => {
} catch (error) { } catch (error) {
console.error('Ошибка при получении списка опросов:', error); console.error('Ошибка при получении списка опросов:', error);
if (error instanceof Error && error.message.includes("401")) { // if (error instanceof Error && error.message.includes("401")) {
// Если ошибка 401, перенаправляем на страницу входа // // Если ошибка 401, перенаправляем на страницу входа
navigate('/login'); // navigate('/login');
} else { // } else {
alert("Ошибка при загрузке опросов: " + (error instanceof Error && error.message)); // alert("Ошибка при загрузке опросов: " + (error instanceof Error && error.message));
} // }
} }
}; };
fetchSurvey(); fetchSurvey();
@ -53,11 +53,11 @@ export const MySurveyList = () => {
} catch (error) { } catch (error) {
console.error('Ошибка при удалении опроса:', error); console.error('Ошибка при удалении опроса:', error);
if (error instanceof Error && error.message.includes("401")) { // if (error instanceof Error && error.message.includes("401")) {
navigate('/login'); // navigate('/login');
} else { // } else {
alert("Ошибка при удалении опроса: " + (error instanceof Error ? error.message : 'Неизвестная ошибка')); // alert("Ошибка при удалении опроса: " + (error instanceof Error ? error.message : 'Неизвестная ошибка'));
} // }
} }
}; };

View file

@ -4,65 +4,97 @@ import AddAnswerButton from "../AddAnswerButton/AddAnswerButton.tsx";
import TypeDropdown from "../TypeDropdown/TypeDropdown.tsx"; import TypeDropdown from "../TypeDropdown/TypeDropdown.tsx";
import styles from './QuestionItem.module.css' import styles from './QuestionItem.module.css'
import Delete from '../../assets/deleteQuestion.svg?react'; import Delete from '../../assets/deleteQuestion.svg?react';
import {
addNewAnswerVariant,
deleteAnswerVariant,
getAnswerVariants,
updateAnswerVariant
} from "../../api/AnswerApi.ts";
interface QuestionItemProps { interface QuestionItemProps {
questionId: number; questionId: number;
initialTextQuestion?: string; initialTextQuestion?: string;
valueQuestion: string; valueQuestion: string;
answerVariants: {id?: number, text: string}[];
onChangeQuestion: (valueQuestion: string) => void; onChangeQuestion: (valueQuestion: string) => void;
onAnswerVariantsChange: (variants: {id?: number, text: string}[]) => void;
onDeleteQuestion: (index: number) => Promise<void>; onDeleteQuestion: (index: number) => Promise<void>;
selectedType: 'single' | 'multiply'; // Уточняем тип selectedType: 'single' | 'multiply';
setSelectedType: (type: 'single' | 'multiply') => void; // Уточняем тип setSelectedType: (type: 'single' | 'multiply') => void;
surveyId?: number;
} }
const QuestionItem: React.FC<QuestionItemProps> = ({questionId, initialTextQuestion = `Вопрос ${questionId}`, const QuestionItem: React.FC<QuestionItemProps> = ({
valueQuestion, onChangeQuestion, onDeleteQuestion, setSelectedType, selectedType}) => { questionId,
// const [selectedType, setSelectedType] = useState<'single' | 'multiply'>('single'); initialTextQuestion = `Вопрос ${questionId}`,
const [answerOption, setAnswerOption] = useState(['']); valueQuestion,
answerVariants: initialAnswerVariants,
onChangeQuestion,
onAnswerVariantsChange,
onDeleteQuestion,
setSelectedType,
selectedType,
surveyId
}) => {
const [textQuestion, setTextQuestion] = useState(initialTextQuestion); const [textQuestion, setTextQuestion] = useState(initialTextQuestion);
const [isEditingQuestion, setIsEditingQuestion] = useState(false); const [isEditingQuestion, setIsEditingQuestion] = useState(false);
const [selectedAnswers, setSelectedAnswers] = useState<number[]>([]); const [selectedAnswers, setSelectedAnswers] = useState<number[]>([]);
const textareaQuestionRef = useRef<HTMLTextAreaElement>(null); const textareaQuestionRef = useRef<HTMLTextAreaElement>(null);
useEffect(() => {
setTextQuestion(valueQuestion);
}, [valueQuestion]);
// useEffect(() => {
// if (initialAnswerVariants.length === 0 && surveyId) {
// handleAddAnswer();
// }
// }, [initialAnswerVariants.length, surveyId]);
const handleTypeChange = (type: 'single' | 'multiply') => { const handleTypeChange = (type: 'single' | 'multiply') => {
setSelectedType(type); setSelectedType(type);
} };
const handleAddAnswer = () => { const handleAddAnswer = async () => {
setAnswerOption([...answerOption, '']); if (surveyId) {
try {
const newAnswer = await addNewAnswerVariant(surveyId, questionId, { text: '' });
onAnswerVariantsChange([...initialAnswerVariants, { id: newAnswer.id, text: '' }]);
} catch (error) {
console.error('Ошибка при добавлении варианта ответа:', error);
}
} else {
onAnswerVariantsChange([...initialAnswerVariants, { text: '' }]);
}
}; };
const handleQuestionClick = () => { const handleQuestionClick = () => {
setIsEditingQuestion(true); setIsEditingQuestion(true);
} };
const handleTextareaQuestionChange = (event: React.ChangeEvent<HTMLTextAreaElement>) => { const handleTextareaQuestionChange = (event: React.ChangeEvent<HTMLTextAreaElement>) => {
setTextQuestion(event.target.value); setTextQuestion(event.target.value);
if (textareaQuestionRef.current) { if (textareaQuestionRef.current) {
textareaQuestionRef.current.style.height = 'auto'; textareaQuestionRef.current.style.height = 'auto';
textareaQuestionRef.current.style.height = `${textareaQuestionRef.current.scrollHeight}px`; textareaQuestionRef.current.style.height = `${textareaQuestionRef.current.scrollHeight}px`;
} }
} };
const handleSaveQuestion = () => { const handleSaveQuestion = () => {
setIsEditingQuestion(false); setIsEditingQuestion(false);
onChangeQuestion(textQuestion); onChangeQuestion(textQuestion);
} };
const handleQuestionKeyDown = (keyDownEvent: React.KeyboardEvent<HTMLTextAreaElement>) => { const handleQuestionKeyDown = (keyDownEvent: React.KeyboardEvent<HTMLTextAreaElement>) => {
if (keyDownEvent.key === 'Enter') { if (keyDownEvent.key === 'Enter') {
keyDownEvent.preventDefault(); keyDownEvent.preventDefault();
handleSaveQuestion() handleSaveQuestion();
}
} }
};
const handleQuestionBlur = () => { const handleQuestionBlur = () => {
handleSaveQuestion() handleSaveQuestion();
} };
useEffect(() => { useEffect(() => {
if (isEditingQuestion && textareaQuestionRef.current) { if (isEditingQuestion && textareaQuestionRef.current) {
@ -72,21 +104,51 @@ const QuestionItem: React.FC<QuestionItemProps> = ({questionId, initialTextQuest
} }
}, [isEditingQuestion]); }, [isEditingQuestion]);
const handleAnswerChange = (index: number, value: string) => { const handleAnswerChange = async (index: number, value: string) => {
const newAnswerOption = [...answerOption]; const newAnswerVariants = [...initialAnswerVariants];
newAnswerOption[index] = value; newAnswerVariants[index] = { ...newAnswerVariants[index], text: value };
setAnswerOption(newAnswerOption); onAnswerVariantsChange(newAnswerVariants);
}
const handleDeleteAnswer = (index: number) => { // Обновляем на сервере только если вариант уже существует (имеет id)
const newAnswerOption = answerOption.filter((_, i) => i !== index); if (surveyId && newAnswerVariants[index].id) {
setAnswerOption(newAnswerOption); try {
setSelectedAnswers(selectedAnswers.filter((i) => i !== index)); await updateAnswerVariant(
surveyId,
questionId,
newAnswerVariants[index].id!,
{ text: value }
);
} catch (error) {
console.error('Ошибка при обновлении варианта ответа:', error);
}
}
}; };
useEffect(() => { const handleDeleteAnswer = async (index: number) => {
setTextQuestion(valueQuestion); const answerToDelete = initialAnswerVariants[index];
}, [valueQuestion]);
if (surveyId && answerToDelete.id) {
try {
await deleteAnswerVariant(surveyId, questionId, answerToDelete.id);
const newAnswerVariants = initialAnswerVariants.filter((_, i) => i !== index);
onAnswerVariantsChange(newAnswerVariants);
setSelectedAnswers(selectedAnswers.filter((i) => i !== index));
// Обновляем список после удаления
if (surveyId) {
const variants = await getAnswerVariants(surveyId, questionId);
const answers = variants.map((v: { id: number, text: string }) => ({ id: v.id, text: v.text }));
onAnswerVariantsChange(answers);
}
} catch (error) {
console.error('Ошибка при удалении варианта ответа:', error);
}
} else {
const newAnswerVariants = initialAnswerVariants.filter((_, i) => i !== index);
onAnswerVariantsChange(newAnswerVariants);
setSelectedAnswers(selectedAnswers.filter((i) => i !== index));
}
};
const handleDeleteQuestion = async () => { const handleDeleteQuestion = async () => {
try { try {
@ -133,12 +195,12 @@ const QuestionItem: React.FC<QuestionItemProps> = ({questionId, initialTextQuest
<TypeDropdown selectedType={selectedType} onTypeChange={handleTypeChange}/> <TypeDropdown selectedType={selectedType} onTypeChange={handleTypeChange}/>
</div> </div>
{answerOption.map((answerText, index) => ( {initialAnswerVariants.map((answer, index) => (
<AnswerOption <AnswerOption
key={index} key={answer.id || index}
selectedType={selectedType} selectedType={selectedType}
index={index + 1} index={index + 1}
value={answerText} value={answer.text}
onChange={(value) => handleAnswerChange(index, value)} onChange={(value) => handleAnswerChange(index, value)}
onDelete={() => handleDeleteAnswer(index)} onDelete={() => handleDeleteAnswer(index)}
toggleSelect={() => toggleSelect(index)} toggleSelect={() => toggleSelect(index)}
@ -146,17 +208,15 @@ const QuestionItem: React.FC<QuestionItemProps> = ({questionId, initialTextQuest
))} ))}
<div className={styles.questionActions}> <div className={styles.questionActions}>
<AddAnswerButton <AddAnswerButton onClick={handleAddAnswer} />
onClick={handleAddAnswer}
/>
<button className={styles.deleteQuestionButton} onClick={handleDeleteQuestion}> <button className={styles.deleteQuestionButton} onClick={handleDeleteQuestion}>
Удалить{/**/} Удалить
<Delete className={styles.basketImg}/> <Delete className={styles.basketImg}/>
</button> </button>
</div> </div>
</div> </div>
</div> </div>
); );
} };
export default QuestionItem; export default QuestionItem;

View file

@ -1,4 +1,4 @@
import React, {useEffect, useState} from "react"; import React, {useState} from "react";
import QuestionItem from "../QuestionItem/QuestionItem.tsx"; import QuestionItem from "../QuestionItem/QuestionItem.tsx";
import AddQuestionButton from "../AddQuestionButton/AddQuestionButton.tsx"; import AddQuestionButton from "../AddQuestionButton/AddQuestionButton.tsx";
import {deleteQuestion, getListQuestions} from "../../api/QuestionApi.ts"; import {deleteQuestion, getListQuestions} from "../../api/QuestionApi.ts";
@ -13,32 +13,25 @@ export interface Question {
id: number; id: number;
text: string; text: string;
questionType: 'singleanswerquestion' | 'multipleanswerquestion'; questionType: 'singleanswerquestion' | 'multipleanswerquestion';
answerVariants?: { answerVariants: Array<{
id?: number; id?: number;
text: string; text: string;
}[]; }>;
} }
const QuestionsList: React.FC<QuestionsListProps> = ({questions, setQuestions, surveyId}) => { const QuestionsList: React.FC<QuestionsListProps> = ({questions, setQuestions, surveyId}) => {
const [selectedType, setSelectedType] = useState<'single' | 'multiply'>('single'); const [selectedType, setSelectedType] = useState<'single' | 'multiply'>('single');
const [localQuestionId, setLocalQuestionId] = useState(2);
const handleAddQuestion = () => { const handleAddQuestion = () => {
const newQuestion: Question = { const newQuestion: Question = {
id: localQuestionId, // ID >= 1001 — новые вопросы id: questions.length + 1, // ID >= 1001 — новые вопросы
text: '', text: '',
questionType: selectedType === 'single' ? 'singleanswerquestion' : 'multipleanswerquestion', questionType: selectedType === 'single' ? 'singleanswerquestion' : 'multipleanswerquestion',
answerVariants: [{text: ''}],
}; };
setQuestions([...questions, newQuestion]); setQuestions([...questions, newQuestion]);
setLocalQuestionId(localQuestionId + 1);
}; };
useEffect(() => {
setLocalQuestionId(questions.length > 0 ?
Math.max(...questions.map(q => q.id)) + 1 : 1);
}, [questions]);
const handleQuestionChange = (id: number, value: string) => { const handleQuestionChange = (id: number, value: string) => {
const newQuestions = questions.map((question) => const newQuestions = questions.map((question) =>
question.id === id ? { ...question, text: value } : question question.id === id ? { ...question, text: value } : question
@ -74,6 +67,15 @@ const QuestionsList: React.FC<QuestionsListProps> = ({questions, setQuestions, s
} }
}; };
const handleAnswerVariantsChange = (questionId: number, newAnswerVariants: {id?: number, text: string}[]) => {
const newQuestions = questions.map(question =>
question.id === questionId
? {...question, answerVariants: newAnswerVariants}
: question
);
setQuestions(newQuestions);
};
return ( return (
<> <>
{questions.map((question) => ( {questions.map((question) => (
@ -81,10 +83,13 @@ const QuestionsList: React.FC<QuestionsListProps> = ({questions, setQuestions, s
key={question.id} key={question.id}
questionId={question.id} questionId={question.id}
valueQuestion={question.text} valueQuestion={question.text}
answerVariants={question.answerVariants}
onAnswerVariantsChange={(variants) => handleAnswerVariantsChange(question.id, variants)}
onDeleteQuestion={() => handleDeleteQuestion(question.id)} onDeleteQuestion={() => handleDeleteQuestion(question.id)}
onChangeQuestion={(value) => handleQuestionChange(question.id, value)} onChangeQuestion={(value) => handleQuestionChange(question.id, value)}
selectedType={selectedType} selectedType={selectedType}
setSelectedType={setSelectedType} setSelectedType={setSelectedType}
surveyId={surveyId}
/> />
))} ))}
<AddQuestionButton onClick={handleAddQuestion} /> <AddQuestionButton onClick={handleAddQuestion} />

View file

@ -2,11 +2,41 @@ import React, {useState} from 'react';
import SurveyInfo from "../SurveyInfo/SurveyInfo.tsx"; import SurveyInfo from "../SurveyInfo/SurveyInfo.tsx";
import styles from "./SettingSurvey.module.css"; import styles from "./SettingSurvey.module.css";
import TimeEvent from "../TimeEvent/TimeEvent.tsx"; import TimeEvent from "../TimeEvent/TimeEvent.tsx";
// import {useParams} from "react-router-dom";
// import {getSurveyById, ISurvey} from "../../api/SurveyApi.ts";
const SettingSurvey: React.FC = () => { const SettingSurvey: React.FC = () => {
const [descriptionSurvey, setDescriptionSurvey] = useState(''); const [descriptionSurvey, setDescriptionSurvey] = useState('');
const [titleSurvey, setTitleSurvey] = useState('Название опроса'); // const [survey, setSurvey] = useState<ISurvey | null>(null);
const [titleSurvey, setTitleSurvey] = useState('');
// const { surveyId } = useParams<{ surveyId: string }>();
// useEffect(() => {
// if (!surveyId) {
// console.error('Survey ID is missing');
// return;
// }
// const id = parseInt(surveyId);
// if (isNaN(id)) {
// console.error('Invalid survey ID');
// return;
// }
//
// const fetchData = async () => {
// try {
// const surveyData = await getSurveyById(id);
// setSurvey(surveyData);
// setTitleSurvey(surveyData.title);
// setDescriptionSurvey(surveyData.description);
// } catch (error) {
// console.error('Ошибка:', error);
// }
// };
//
// fetchData();
//
// }, [surveyId]);
return ( return (
<div className={styles.settingSurvey}> <div className={styles.settingSurvey}>

View file

@ -6,6 +6,7 @@ import SaveButton from "../SaveButton/SaveButton.tsx";
import {ISurvey, postNewSurvey} from "../../api/SurveyApi.ts"; import {ISurvey, postNewSurvey} from "../../api/SurveyApi.ts";
import {addNewQuestion} from "../../api/QuestionApi.ts"; import {addNewQuestion} from "../../api/QuestionApi.ts";
import {useNavigate} from "react-router-dom"; import {useNavigate} from "react-router-dom";
import {addNewAnswerVariant} from "../../api/AnswerApi.ts";
const Survey: React.FC = () => { const Survey: React.FC = () => {
const navigate = useNavigate(); const navigate = useNavigate();
@ -14,25 +15,9 @@ const Survey: React.FC = () => {
const [survey] = useState<ISurvey | null>(null); const [survey] = useState<ISurvey | null>(null);
const [questions, setQuestions] = useState<Question[]>([ const [questions, setQuestions] = useState<Question[]>([
{ id: 1, text: '', questionType: 'singleanswerquestion'}, { id: 1, text: '', questionType: 'singleanswerquestion', answerVariants: [{ text: '' }]},
]); ]);
// const handleSave = async () => {
// const savedSurvey = await postNewSurvey({title: titleSurvey, description: descriptionSurvey});
// setSurvey(savedSurvey);
// Promise.all(
// questions
// .map((question) => addNewQuestion( savedSurvey.id, {title: question.text, questionType: question.questionType })),
// )
// .then(() => {
// alert('Все удачно сохранилось');
// })
// .catch(() => {
// alert('Пиздец');
// });
// };
const handleSave = async () => { const handleSave = async () => {
try { try {
const savedSurvey = await postNewSurvey({ const savedSurvey = await postNewSurvey({
@ -40,20 +25,48 @@ const Survey: React.FC = () => {
description: descriptionSurvey description: descriptionSurvey
}); });
await Promise.all( // Сначала создаем все вопросы
questions.map(question => const updatedQuestions: Question[] = [];
addNewQuestion(savedSurvey.id, { for (const question of questions) {
const newQuestion = await addNewQuestion(savedSurvey.id, {
title: question.text, title: question.text,
questionType: question.questionType questionType: question.questionType
}) });
// Создаем копию вопроса с новым ID
const updatedQuestion: Question = {
...question,
id: newQuestion.id,
answerVariants: []
};
// Затем создаем варианты ответов для каждого вопроса
if (question.answerVariants && question.answerVariants.length > 0) {
const newVariants = await Promise.all(
question.answerVariants.map(answer =>
addNewAnswerVariant(
savedSurvey.id,
newQuestion.id,
{ text: answer.text }
)
) )
); );
navigate('/my-surveys'); // Обновляем варианты ответов с их ID
updatedQuestion.answerVariants = newVariants.map((variant: { id: number, text: string }) => ({
id: variant.id,
text: variant.text
}));
}
updatedQuestions.push(updatedQuestion);
}
// Обновляем состояние с новыми ID
setQuestions(updatedQuestions);
navigate('/my-surveys');
} catch (error) { } catch (error) {
console.error('Ошибка при сохранении:', error); console.error('Ошибка при сохранении:', error);
alert('Не удалось сохранить опрос');
} }
}; };

View file

@ -1,26 +1,174 @@
import React, { useEffect, useState } from "react";
import SurveyInfo from "../SurveyInfo/SurveyInfo.tsx"; import SurveyInfo from "../SurveyInfo/SurveyInfo.tsx";
import QuestionsList, {Question} from "../QuestionsList/QuestionsList.tsx"; import QuestionsList, { Question } from "../QuestionsList/QuestionsList.tsx";
import {useEffect, useState} from "react"; import { getSurveyById, ISurvey, updateSurvey } from "../../api/SurveyApi.ts";
import {getSurveyById, ISurvey, updateSurvey} from "../../api/SurveyApi.ts"; import { useParams } from "react-router-dom";
import {useParams} from "react-router-dom"; import { addNewQuestion, getListQuestions, updateQuestion, deleteQuestion } from "../../api/QuestionApi.ts";
import {addNewQuestion, getListQuestions, updateQuestion} from "../../api/QuestionApi.ts";
import styles from "./SurveyPage.module.css"; import styles from "./SurveyPage.module.css";
import SaveButton from "../SaveButton/SaveButton.tsx"; import SaveButton from "../SaveButton/SaveButton.tsx";
import { addNewAnswerVariant, deleteAnswerVariant, getAnswerVariants, IAnswerVariant, updateAnswerVariant } from "../../api/AnswerApi.ts";
type ActionType = 'create' | 'update'; // Типы для действий
type ActionType =
| 'update-survey'
| 'create-question'
| 'update-question'
| 'delete-question'
| 'create-answer'
| 'update-answer'
| 'delete-answer';
class QuestionAction { // Интерфейсы для данных действий
type: ActionType; interface SurveyActionData {
data: { id: number;
title: string;
description: string;
}
interface QuestionActionData {
surveyId: number; surveyId: number;
id?: number; id?: number;
title?: string; title: string;
questionType?: string; questionType: 'singleanswerquestion' | 'multipleanswerquestion';
}; }
constructor(type: ActionType, data: { surveyId: number, id?: number, title?: string, questionType?: string }) { interface AnswerActionData {
this.type = type; surveyId: number;
this.data = data; 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() {
console.log(`Выполнение очереди с ${this.actions.length} действиями`);
for (const [index, action] of this.actions.entries()) {
console.log(`Обработка действия ${index + 1}/${this.actions.length}:`, action);
try {
// ... существующий код
} catch (error) {
console.error(`Ошибка в действии ${index + 1}:`, error);
throw error;
}
}
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.surveyId, data.id, {
title: data.title,
questionType: data.questionType
});
}
private async handleDeleteQuestion(data: QuestionActionData & { id: number }) {
return await deleteQuestion(data.surveyId, data.id);
}
private async handleCreateAnswer(data: AnswerActionData) {
return await addNewAnswerVariant(data.surveyId, data.questionId, {
text: data.text
});
}
private async handleUpdateAnswer(data: AnswerActionData & { id: number }) {
console.log('1. Начало handleUpdateAnswer', data);
try {
console.log('2. Перед вызовом updateAnswerVariant', {
surveyId: data.surveyId,
questionId: data.questionId,
id: data.id,
text: data.text
});
const result = await updateAnswerVariant(data.surveyId, data.questionId, data.id, {
text: data.text
});
console.log('3. После вызова updateAnswerVariant', result);
return result;
} catch (error) {
console.error('4. Ошибка в handleUpdateAnswer:', error);
throw error;
}
}
private async handleDeleteAnswer(data: AnswerActionData & { id: number }) {
return await deleteAnswerVariant(data.surveyId, data.questionId, data.id);
} }
} }
@ -34,7 +182,6 @@ export const SurveyPage: React.FC = () => {
const [description, setDescription] = useState(''); const [description, setDescription] = useState('');
const [title, setTitle] = useState(''); const [title, setTitle] = useState('');
useEffect(() => { useEffect(() => {
if (!surveyId) { if (!surveyId) {
console.error('Survey ID is missing'); console.error('Survey ID is missing');
@ -52,20 +199,26 @@ export const SurveyPage: React.FC = () => {
setLoading(true); setLoading(true);
const surveyData = await getSurveyById(id); const surveyData = await getSurveyById(id);
setSurvey(surveyData); setSurvey(surveyData);
setTitle(surveyData.title); setTitle(surveyData.title);
setDescription(surveyData.description); setDescription(surveyData.description);
const questionsData = await getListQuestions(id); const questionsData = await getListQuestions(id);
const formattedQuestions = questionsData.map(q => ({ const formattedQuestions = await Promise.all(questionsData.map(async q => {
const answerVariants = await getAnswerVariants(id, q.id);
return {
id: q.id, id: q.id,
text: q.title, text: q.title,
questionType: q.questionType as 'singleanswerquestion' | 'multipleanswerquestion', questionType: q.questionType as 'singleanswerquestion' | 'multipleanswerquestion',
answerVariants: answerVariants.map((a: IAnswerVariant) => ({
id: a.id,
text: a.text
}))
};
})); }));
setQuestions(formattedQuestions); setQuestions(formattedQuestions);
} catch (error) { } catch (error) {
console.error('Ошибка:', error); console.error('Ошибка:', error);
setError('Не удалось загрузить опрос') setError('Не удалось загрузить опрос');
} finally { } finally {
setLoading(false); setLoading(false);
} }
@ -74,77 +227,127 @@ export const SurveyPage: React.FC = () => {
fetchData(); fetchData();
}, [surveyId]); }, [surveyId]);
if (loading) return <div>Загрузка...</div>;
if (!survey) return <div>Опрос не найден</div>;
const handleSave = async () => { const handleSave = async () => {
if (!surveyId || !survey) return; console.log('0. Начало handleSave');
if (!surveyId || !survey) {
console.log('0.1. Прерывание - нет surveyId или survey', { surveyId, survey });
return;
}
try { try {
setError(null); setError(null);
const id = parseInt(surveyId); const id = parseInt(surveyId);
const actionQueue = new ActionQueue();
const surveyUpdated = await updateSurvey(id, { // 1. Обновление опроса
title: title, actionQueue.add({
description: description, type: 'update-survey',
data: { id, title, description } as SurveyActionData
}); });
setSurvey(surveyUpdated);
const actions: QuestionAction[] = []; // 2. Получаем текущие вопросы с сервера
const serverQuestions = await getListQuestions(id); const serverQuestions = await getListQuestions(id);
// 3. Обработка вопросов
questions.forEach(question => { questions.forEach(question => {
const existsOnServer = serverQuestions.some(q => q.id === question.id); const isNewQuestion = !serverQuestions.some(q => q.id === question.id);
if (existsOnServer) { if (isNewQuestion) {
actions.push(new QuestionAction("update", { actionQueue.add({
type: 'create-question',
data: {
surveyId: id,
title: question.text,
questionType: question.questionType
} as QuestionActionData,
tempId: question.id
});
} else {
// Обновляем только если текст изменился
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, surveyId: id,
id: question.id, id: question.id,
title: question.text, title: question.text,
questionType: question.questionType questionType: question.questionType
})); } as QuestionActionData & { id: number }
} else { });
actions.push(new QuestionAction("create", { }
}
// 4. Обработка вариантов ответов
if (question.answerVariants) {
question.answerVariants.forEach(answer => {
if (isNewQuestion || !answer.id) {
actionQueue.add({
type: 'create-answer',
data: {
surveyId: id, surveyId: id,
title: question.text, questionId: question.id,
questionType: question.questionType text: answer.text
})); } as AnswerActionData,
tempId: question.id
});
} else {
// Обновляем только если текст изменился
actionQueue.add({
type: 'update-answer',
data: {
surveyId: id,
questionId: question.id,
id: answer.id,
text: answer.text
} as AnswerActionData & { id: number }
});
}
});
} }
}); });
for (const action of actions) { // 5. Удаление удаленных вопросов
switch (action.type) { serverQuestions.forEach(serverQuestion => {
case "create": if (!questions.some(q => q.id === serverQuestion.id)) {
await addNewQuestion(id, { actionQueue.add({
title: action.data.title as string, type: 'delete-question',
questionType: action.data.questionType as 'singleanswerquestion' | 'multipleanswerquestion', data: {
}); surveyId: id,
break; id: serverQuestion.id
case "update": } as QuestionActionData & { id: number }
if (action.data.id) {
await updateQuestion(id, action.data.id, {
title: action.data.title,
questionType: action.data.questionType
}); });
} }
break; });
}
}
// Выполняем все действия
await actionQueue.execute();
// Обновляем данные
const updatedQuestions = await getListQuestions(id); const updatedQuestions = await getListQuestions(id);
const formattedQuestions = updatedQuestions.map(q => ({ const formattedQuestions = await Promise.all(updatedQuestions.map(async q => {
const answerVariants = await getAnswerVariants(id, q.id);
return {
id: q.id, id: q.id,
text: q.title, text: q.title,
questionType: q.questionType as 'singleanswerquestion' | 'multipleanswerquestion', questionType: q.questionType as 'singleanswerquestion' | 'multipleanswerquestion',
answerVariants: answerVariants.map((a: IAnswerVariant) => ({
id: a.id,
text: a.text
}))
};
})); }));
setQuestions(formattedQuestions); setQuestions(formattedQuestions);
} catch (error) { } catch (error) {
console.error('Ошибка:', error); console.error('Ошибка сохранения:', error);
setError('Не удалось сохранить изменения'); setError(`Ошибка сохранения: ${error instanceof Error ? error.message : 'Неизвестная ошибка'}`);
} }
}; };
if (loading) return <div>Загрузка...</div>;
if (!survey) return <div>Опрос не найден</div>;
return ( return (
<div className={styles.survey_page}> <div className={styles.survey_page}>
<SurveyInfo <SurveyInfo