From 51f3469031a631761f895350748e00a2e3dfbc6e Mon Sep 17 00:00:00 2001 From: Tatiana Nikolaeva Date: Sat, 24 May 2025 21:58:42 +0500 Subject: [PATCH 01/13] update survey and questions --- .../src/components/LoginForm/LoginForm.tsx | 14 +-- .../QuestionsList/QuestionsList.tsx | 33 ++++--- .../RegisterForm/RegisterForm.module.css | 4 +- .../components/RegisterForm/RegisterForm.tsx | 25 +++--- .../src/components/SurveyPage/SurveyPage.tsx | 86 ++++++++++++++++--- 5 files changed, 122 insertions(+), 40 deletions(-) diff --git a/SurveyFrontend/src/components/LoginForm/LoginForm.tsx b/SurveyFrontend/src/components/LoginForm/LoginForm.tsx index 1fcab4d..8431a04 100644 --- a/SurveyFrontend/src/components/LoginForm/LoginForm.tsx +++ b/SurveyFrontend/src/components/LoginForm/LoginForm.tsx @@ -22,11 +22,15 @@ const LoginForm = () => { setError(null); try{ - const responseData = await authUser({email, password}); - if (responseData && !responseData.error) - navigate('/my-surveys'); - else - setError('Неверный логин или пароль') + if (email === '' || password === '') + setError('Заполните все поля') + else { + const responseData = await authUser({email, password}); + if (responseData && !responseData.error) + navigate('/my-surveys'); + else + setError('Неверный логин или пароль') + } } catch(err){ console.error('Ошибка при отправке запроса:', err); diff --git a/SurveyFrontend/src/components/QuestionsList/QuestionsList.tsx b/SurveyFrontend/src/components/QuestionsList/QuestionsList.tsx index 87fa255..9cf1fed 100644 --- a/SurveyFrontend/src/components/QuestionsList/QuestionsList.tsx +++ b/SurveyFrontend/src/components/QuestionsList/QuestionsList.tsx @@ -1,7 +1,7 @@ import React, {useEffect, useState} from "react"; import QuestionItem from "../QuestionItem/QuestionItem.tsx"; import AddQuestionButton from "../AddQuestionButton/AddQuestionButton.tsx"; -import {deleteQuestion} from "../../api/QuestionApi.ts"; +import {deleteQuestion, getListQuestions} from "../../api/QuestionApi.ts"; interface QuestionsListProps { questions: Question[]; @@ -18,11 +18,11 @@ export interface Question { const QuestionsList: React.FC = ({questions, setQuestions, surveyId}) => { const [selectedType, setSelectedType] = useState<'single' | 'multiply'>('single'); - const [localQuestionId, setLocalQuestionId] = useState(2); // Начинаем с 2, так как первый вопрос имеет ID=1 + const [localQuestionId, setLocalQuestionId] = useState(1001); // Начинаем с 2, так как первый вопрос имеет ID=1 const handleAddQuestion = () => { const newQuestion: Question = { - id: localQuestionId, + id: localQuestionId, // ID >= 1001 — новые вопросы text: '', questionType: selectedType === 'single' ? 'singleanswerquestion' : 'multipleanswerquestion', }; @@ -45,21 +45,28 @@ const QuestionsList: React.FC = ({questions, setQuestions, s const handleDeleteQuestion = async (id: number) => { try { if (surveyId) { - const response = await deleteQuestion(surveyId, id); - if (!response?.success) { - throw new Error('Не удалось удалить вопрос на сервере'); + const listQuestions = await getListQuestions(surveyId); + if (listQuestions.find(q => q.id === id)) { + const response = await deleteQuestion(surveyId, id); + if (!response?.success) { + throw new Error('Не удалось удалить вопрос на сервере'); + } } - } - const newQuestions: Question[] = []; - for (const question of questions) { - if (question.id !== id) { - newQuestions.push(question); + const newQuestions: Question[] = []; + for (const question of questions) { + if (question.id !== id) { + newQuestions.push(question); + } } + setQuestions(newQuestions); + } + else{ + const questionsList = questions.filter(q => q.id !== id); + setQuestions(questionsList); + return; } - setQuestions(newQuestions); } catch (error) { console.error('Ошибка при удалении вопроса:', error); - alert('Не удалось удалить вопрос: ' + (error instanceof Error ? error.message : 'Неизвестная ошибка')); } }; diff --git a/SurveyFrontend/src/components/RegisterForm/RegisterForm.module.css b/SurveyFrontend/src/components/RegisterForm/RegisterForm.module.css index 52a9f86..642ea69 100644 --- a/SurveyFrontend/src/components/RegisterForm/RegisterForm.module.css +++ b/SurveyFrontend/src/components/RegisterForm/RegisterForm.module.css @@ -31,7 +31,7 @@ color: #000000; outline: none; border: none; - border-bottom: 1px solid rgba(0, 0, 0, 0.2); + border-bottom: 2px solid rgba(0, 0, 0, 0.2); padding: 5px 0; opacity: 1; } @@ -55,7 +55,7 @@ } .input:focus { - border-bottom: 1px solid black; /* Чёрная граница при фокусе */ + border-bottom: 2px solid black; /* Чёрная граница при фокусе */ } .signUp{ diff --git a/SurveyFrontend/src/components/RegisterForm/RegisterForm.tsx b/SurveyFrontend/src/components/RegisterForm/RegisterForm.tsx index 380049a..f7163fc 100644 --- a/SurveyFrontend/src/components/RegisterForm/RegisterForm.tsx +++ b/SurveyFrontend/src/components/RegisterForm/RegisterForm.tsx @@ -31,17 +31,22 @@ const RegisterForm = () => { setError(null); try{ - const responseData = await registerUser({username, firstName, lastName, email, password}); - if (responseData && !responseData.error) { - console.log('Регистрация успешна'); - localStorage.setItem("user", JSON.stringify({ - firstName, - lastName - })); - navigate('/my-surveys'); + if (email === '' || password === '' || firstName === '' || lastName === '') { + setError('Заполните все поля'); } - else if (responseData.status === 409){ - setError('Аккаунт с такой почтой уже зарегистрирован'); + else{ + const responseData = await registerUser({username, firstName, lastName, email, password}); + if (responseData && !responseData.error) { + console.log('Регистрация успешна'); + localStorage.setItem("user", JSON.stringify({ + firstName, + lastName + })); + navigate('/my-surveys'); + } + else if (responseData.status === 409){ + setError('Аккаунт с такой почтой уже зарегистрирован'); + } } } catch (err) { diff --git a/SurveyFrontend/src/components/SurveyPage/SurveyPage.tsx b/SurveyFrontend/src/components/SurveyPage/SurveyPage.tsx index 399b807..bb7c74f 100644 --- a/SurveyFrontend/src/components/SurveyPage/SurveyPage.tsx +++ b/SurveyFrontend/src/components/SurveyPage/SurveyPage.tsx @@ -3,10 +3,27 @@ import QuestionsList, {Question} from "../QuestionsList/QuestionsList.tsx"; import {useEffect, useState} from "react"; import {getSurveyById, ISurvey, updateSurvey} from "../../api/SurveyApi.ts"; import {useParams} from "react-router-dom"; -import {getListQuestions} from "../../api/QuestionApi.ts"; +import {addNewQuestion, getListQuestions, updateQuestion} from "../../api/QuestionApi.ts"; import styles from "./SurveyPage.module.css"; import SaveButton from "../SaveButton/SaveButton.tsx"; +type ActionType = 'create' | 'update'; + +class QuestionAction { + type: ActionType; + data: { + surveyId: number; + id?: number; + title?: string; + questionType?: string; + }; + + constructor(type: ActionType, data: { surveyId: number, id?: number, title?: string, questionType?: string }) { + this.type = type; + this.data = data; + } +} + export const SurveyPage: React.FC = () => { const [survey, setSurvey] = useState(null); const [questions, setQuestions] = useState([]); @@ -60,24 +77,73 @@ export const SurveyPage: React.FC = () => { if (loading) return
Загрузка...
; if (!survey) return
Опрос не найден
; - const handleSave = async() => { + const handleSave = async () => { if (!surveyId || !survey) return; - try{ + try { setError(null); const id = parseInt(surveyId); - const surveyUpdated = await updateSurvey(id, { + + const surveyUpdated = await updateSurvey(id, { title: title, description: description, - }) + }); setSurvey(surveyUpdated); - } - catch(error){ - console.error('Ошибка при сохранении опроса:', error); + + const actions: QuestionAction[] = []; + const serverQuestions = await getListQuestions(id); + + questions.forEach(question => { + const existsOnServer = serverQuestions.some(q => q.id === question.id); + + if (existsOnServer) { + actions.push(new QuestionAction("update", { + surveyId: id, + id: question.id, + title: question.text, + questionType: question.questionType + })); + } else { + actions.push(new QuestionAction("create", { + surveyId: id, + title: question.text, + questionType: question.questionType + })); + } + }); + + for (const action of actions) { + switch (action.type) { + case "create": + await addNewQuestion(id, { + title: action.data.title as string, + questionType: action.data.questionType as 'singleanswerquestion' | 'multipleanswerquestion', + }); + break; + case "update": + if (action.data.id) { + await updateQuestion(id, action.data.id, { + title: action.data.title, + questionType: action.data.questionType + }); + } + break; + } + } + + const updatedQuestions = await getListQuestions(id); + const formattedQuestions = updatedQuestions.map(q => ({ + id: q.id, + text: q.title, + questionType: q.questionType as 'singleanswerquestion' | 'multipleanswerquestion', + })); + setQuestions(formattedQuestions); + + } catch (error) { + console.error('Ошибка:', error); setError('Не удалось сохранить изменения'); - throw error; } - } + }; return (
From e961d53d6cff94ef43c932c20c2a6d4ae4a27eeb Mon Sep 17 00:00:00 2001 From: Tatiana Nikolaeva Date: Sun, 25 May 2025 00:08:36 +0500 Subject: [PATCH 02/13] query functions for response options --- SurveyFrontend/src/api/AnswerApi.ts | 85 +++++++++++++++++++ SurveyFrontend/src/api/AuthApi.ts | 10 +-- SurveyFrontend/src/api/BaseApi.ts | 10 ++- SurveyFrontend/src/api/QuestionApi.ts | 12 +-- .../QuestionsList/QuestionsList.tsx | 6 +- 5 files changed, 106 insertions(+), 17 deletions(-) create mode 100644 SurveyFrontend/src/api/AnswerApi.ts diff --git a/SurveyFrontend/src/api/AnswerApi.ts b/SurveyFrontend/src/api/AnswerApi.ts new file mode 100644 index 0000000..59ad95e --- /dev/null +++ b/SurveyFrontend/src/api/AnswerApi.ts @@ -0,0 +1,85 @@ +import {BASE_URL, createRequestConfig, handleResponse} from "./BaseApi.ts"; + +export const getAnswerVariants = async (surveyId: number, questionId: number) => { + try{ + const response = await fetch(`${BASE_URL}/surveys/${surveyId}/questions/${questionId}/answerVariants`, { + ...createRequestConfig('GET') + }) + return await handleResponse(response) + }catch(err){ + console.error(`Error receiving response options: ${err}`); + throw err; + } +} + +export const addNewAnswerVariant = async (surveyId: number, questionId: number) => { + const token = localStorage.getItem("token"); + if (!token) { + throw new Error("Токен отсутствует"); + } + + try{ + const response = await fetch(`${BASE_URL}/surveys/${surveyId}/questions/${questionId}/answerVariants`, { + ...createRequestConfig('POST'), + body: JSON.stringify({ + surveyId: surveyId, + questionId: questionId, + }), + }) + + if (!response.ok) { + throw new Error(`Ошибка: ${response.status}`); + } + return await handleResponse(response) + } + catch(err){ + console.error(`Error adding a new response option: ${err}`); + throw err; + } +} + +export const updateAnswerVariant = async (surveyId: number, questionId: number, id: number) => { + const token = localStorage.getItem("token"); + if (!token) { + throw new Error("Токен отсутствует"); + } + + try{ + const response = await fetch(`${BASE_URL}/surveys/${surveyId}/questions/${questionId}/answerVariants/${id}`, { + ...createRequestConfig('PUT'), + body: JSON.stringify({ + surveyId: surveyId, + questionId: questionId, + id: id + }) + }) + if (!response.ok) { + throw new Error(`Ошибка: ${response.status}`); + } + return await handleResponse(response) + } + catch(err){ + console.error(`Error updating the response option: ${err}`); + } +} + +export const deleteAnswerVariant = async (surveyId: number, questionId: number, id: number) => { + const token = localStorage.getItem("token"); + if (!token) { + throw new Error('Токен отсутствует'); + } + try{ + const response = await fetch(`${BASE_URL}/surveys/${surveyId}/questions/${questionId}/answerVariants/${id}`, { + ...createRequestConfig('DELETE'), + }) + const responseData = await handleResponse(response); + if (response.ok && !responseData){ + return {success: true}; + } + return responseData; + } + catch(err){ + console.error(`Error deleting a answer: ${err}`); + throw err; + } +} \ No newline at end of file diff --git a/SurveyFrontend/src/api/AuthApi.ts b/SurveyFrontend/src/api/AuthApi.ts index 4b0b45b..26e2821 100644 --- a/SurveyFrontend/src/api/AuthApi.ts +++ b/SurveyFrontend/src/api/AuthApi.ts @@ -10,16 +10,10 @@ interface IRegistrationData extends IAuthData{ firstName: string; lastName: string; } -// -// interface IUserData{ -// username: string; -// firstName: string; -// lastName: string; -// email: string; -// } export const getCurrentUser = async (): Promise => { const token = localStorage.getItem("token"); + if (!token) { throw new Error("Токен отсутствует"); } @@ -32,6 +26,8 @@ export const getCurrentUser = async (): Promise => { } }); + console.log(response); + if (response.status === 401) { localStorage.removeItem("token"); throw new Error("Сессия истекла. Пожалуйста, войдите снова."); diff --git a/SurveyFrontend/src/api/BaseApi.ts b/SurveyFrontend/src/api/BaseApi.ts index 2041e4f..6a85675 100644 --- a/SurveyFrontend/src/api/BaseApi.ts +++ b/SurveyFrontend/src/api/BaseApi.ts @@ -20,12 +20,10 @@ const createRequestConfig = (method: string, isFormData: boolean = false): Reque headers: {}, }; - // Добавляем заголовок авторизации, если есть токен if (token) { config.headers.Authorization = `Bearer ${token}`; } - // Добавляем Content-Type, если это не FormData if (!isFormData) { config.headers["Content-Type"] = "application/json"; } @@ -39,12 +37,16 @@ const createRequestConfig = (method: string, isFormData: boolean = false): Reque * @returns Распарсенные данные или ошибку */ const handleResponse = async (response: Response) => { - // Проверяем, есть ли контент в ответе const responseText = await response.text(); if (!responseText) { + if (response.status === 401) { + window.location.href = '/auth/login'; + throw new Error('Требуется авторизация'); + } + if (response.ok) { - return null; // Если ответ пустой, но статус 200, возвращаем null + return null; } throw new Error(`HTTP ${response.status}: ${response.statusText}`); } diff --git a/SurveyFrontend/src/api/QuestionApi.ts b/SurveyFrontend/src/api/QuestionApi.ts index 0d80838..d42fc17 100644 --- a/SurveyFrontend/src/api/QuestionApi.ts +++ b/SurveyFrontend/src/api/QuestionApi.ts @@ -5,14 +5,16 @@ export interface INewQuestion{ questionType: string; } +export interface IAnswerVariant{ + id: number; + questionId: number; + text: string; +} + export interface IQuestion extends INewQuestion { id: number; surveyId: number; - answerVariants: Array<{ - id: number; - questionId: number; - text: string; - }> + answerVariants: IAnswerVariant[]; } export const addNewQuestion = async (surveyId: number, question: INewQuestion) => { diff --git a/SurveyFrontend/src/components/QuestionsList/QuestionsList.tsx b/SurveyFrontend/src/components/QuestionsList/QuestionsList.tsx index 9cf1fed..3162a33 100644 --- a/SurveyFrontend/src/components/QuestionsList/QuestionsList.tsx +++ b/SurveyFrontend/src/components/QuestionsList/QuestionsList.tsx @@ -13,12 +13,16 @@ export interface Question { id: number; text: string; questionType: 'singleanswerquestion' | 'multipleanswerquestion'; + answerVariants?: { + id?: number; + text: string; + }[]; } const QuestionsList: React.FC = ({questions, setQuestions, surveyId}) => { const [selectedType, setSelectedType] = useState<'single' | 'multiply'>('single'); - const [localQuestionId, setLocalQuestionId] = useState(1001); // Начинаем с 2, так как первый вопрос имеет ID=1 + const [localQuestionId, setLocalQuestionId] = useState(2); const handleAddQuestion = () => { const newQuestion: Question = { From 15ec6b963237ece0faf7e7225458d7a0c5e951c8 Mon Sep 17 00:00:00 2001 From: Tatiana Nikolaeva Date: Sun, 25 May 2025 18:36:20 +0500 Subject: [PATCH 03/13] fix interaction with the survey --- SurveyFrontend/src/api/AnswerApi.ts | 25 +- SurveyFrontend/src/api/AuthApi.ts | 9 +- SurveyFrontend/src/api/QuestionApi.ts | 7 +- .../components/MySurveyList/MySurveyList.tsx | 22 +- .../components/QuestionItem/QuestionItem.tsx | 140 +++++--- .../QuestionsList/QuestionsList.tsx | 29 +- .../SettingSurvey/SettingSurvey.tsx | 32 +- .../src/components/Survey/Survey.tsx | 67 ++-- .../src/components/SurveyPage/SurveyPage.tsx | 339 ++++++++++++++---- 9 files changed, 495 insertions(+), 175 deletions(-) diff --git a/SurveyFrontend/src/api/AnswerApi.ts b/SurveyFrontend/src/api/AnswerApi.ts index 59ad95e..f29d5b4 100644 --- a/SurveyFrontend/src/api/AnswerApi.ts +++ b/SurveyFrontend/src/api/AnswerApi.ts @@ -1,5 +1,15 @@ 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) => { try{ 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"); if (!token) { 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`, { ...createRequestConfig('POST'), body: JSON.stringify({ - surveyId: surveyId, - questionId: questionId, + text: answer.text, }), }) @@ -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 => { const token = localStorage.getItem("token"); if (!token) { 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}`, { ...createRequestConfig('PUT'), body: JSON.stringify({ - surveyId: surveyId, - questionId: questionId, - id: id + text: answer.text, }) }) 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) } catch(err){ console.error(`Error updating the response option: ${err}`); + throw err; } } diff --git a/SurveyFrontend/src/api/AuthApi.ts b/SurveyFrontend/src/api/AuthApi.ts index 26e2821..da61451 100644 --- a/SurveyFrontend/src/api/AuthApi.ts +++ b/SurveyFrontend/src/api/AuthApi.ts @@ -11,7 +11,7 @@ interface IRegistrationData extends IAuthData{ lastName: string; } -export const getCurrentUser = async (): Promise => { +export const getCurrentUser = async (user: IRegistrationData) => { const token = localStorage.getItem("token"); if (!token) { @@ -23,7 +23,12 @@ export const getCurrentUser = async (): Promise => { ...createRequestConfig('GET'), headers: { 'Authorization': `Bearer ${token}` - } + }, + body: JSON.stringify({ + email: user.email, + firstName: user.firstName, + lastName: user.lastName, + }) }); console.log(response); diff --git a/SurveyFrontend/src/api/QuestionApi.ts b/SurveyFrontend/src/api/QuestionApi.ts index d42fc17..90593b6 100644 --- a/SurveyFrontend/src/api/QuestionApi.ts +++ b/SurveyFrontend/src/api/QuestionApi.ts @@ -1,16 +1,11 @@ import {BASE_URL, createRequestConfig, handleResponse} from "./BaseApi.ts"; +import {IAnswerVariant} from "./AnswerApi.ts"; export interface INewQuestion{ title: string; questionType: string; } -export interface IAnswerVariant{ - id: number; - questionId: number; - text: string; -} - export interface IQuestion extends INewQuestion { id: number; surveyId: number; diff --git a/SurveyFrontend/src/components/MySurveyList/MySurveyList.tsx b/SurveyFrontend/src/components/MySurveyList/MySurveyList.tsx index 20c2f0d..963092a 100644 --- a/SurveyFrontend/src/components/MySurveyList/MySurveyList.tsx +++ b/SurveyFrontend/src/components/MySurveyList/MySurveyList.tsx @@ -25,12 +25,12 @@ export const MySurveyList = () => { } catch (error) { console.error('Ошибка при получении списка опросов:', error); - if (error instanceof Error && error.message.includes("401")) { - // Если ошибка 401, перенаправляем на страницу входа - navigate('/login'); - } else { - alert("Ошибка при загрузке опросов: " + (error instanceof Error && error.message)); - } + // if (error instanceof Error && error.message.includes("401")) { + // // Если ошибка 401, перенаправляем на страницу входа + // navigate('/login'); + // } else { + // alert("Ошибка при загрузке опросов: " + (error instanceof Error && error.message)); + // } } }; fetchSurvey(); @@ -53,11 +53,11 @@ export const MySurveyList = () => { } catch (error) { console.error('Ошибка при удалении опроса:', error); - if (error instanceof Error && error.message.includes("401")) { - navigate('/login'); - } else { - alert("Ошибка при удалении опроса: " + (error instanceof Error ? error.message : 'Неизвестная ошибка')); - } + // if (error instanceof Error && error.message.includes("401")) { + // navigate('/login'); + // } else { + // alert("Ошибка при удалении опроса: " + (error instanceof Error ? error.message : 'Неизвестная ошибка')); + // } } }; diff --git a/SurveyFrontend/src/components/QuestionItem/QuestionItem.tsx b/SurveyFrontend/src/components/QuestionItem/QuestionItem.tsx index 73c45b1..6ac4221 100644 --- a/SurveyFrontend/src/components/QuestionItem/QuestionItem.tsx +++ b/SurveyFrontend/src/components/QuestionItem/QuestionItem.tsx @@ -4,65 +4,97 @@ import AddAnswerButton from "../AddAnswerButton/AddAnswerButton.tsx"; import TypeDropdown from "../TypeDropdown/TypeDropdown.tsx"; import styles from './QuestionItem.module.css' import Delete from '../../assets/deleteQuestion.svg?react'; - +import { + addNewAnswerVariant, + deleteAnswerVariant, + getAnswerVariants, + updateAnswerVariant +} from "../../api/AnswerApi.ts"; interface QuestionItemProps { questionId: number; initialTextQuestion?: string; valueQuestion: string; + answerVariants: {id?: number, text: string}[]; onChangeQuestion: (valueQuestion: string) => void; + onAnswerVariantsChange: (variants: {id?: number, text: string}[]) => void; onDeleteQuestion: (index: number) => Promise; - selectedType: 'single' | 'multiply'; // Уточняем тип - setSelectedType: (type: 'single' | 'multiply') => void; // Уточняем тип + selectedType: 'single' | 'multiply'; + setSelectedType: (type: 'single' | 'multiply') => void; + surveyId?: number; } -const QuestionItem: React.FC = ({questionId, initialTextQuestion = `Вопрос ${questionId}`, - valueQuestion, onChangeQuestion, onDeleteQuestion, setSelectedType, selectedType}) => { - // const [selectedType, setSelectedType] = useState<'single' | 'multiply'>('single'); - const [answerOption, setAnswerOption] = useState(['']); +const QuestionItem: React.FC = ({ + questionId, + initialTextQuestion = `Вопрос ${questionId}`, + valueQuestion, + answerVariants: initialAnswerVariants, + onChangeQuestion, + onAnswerVariantsChange, + onDeleteQuestion, + setSelectedType, + selectedType, + surveyId + }) => { const [textQuestion, setTextQuestion] = useState(initialTextQuestion); const [isEditingQuestion, setIsEditingQuestion] = useState(false); - const [selectedAnswers, setSelectedAnswers] = useState([]); - const textareaQuestionRef = useRef(null); + useEffect(() => { + setTextQuestion(valueQuestion); + }, [valueQuestion]); + + // useEffect(() => { + // if (initialAnswerVariants.length === 0 && surveyId) { + // handleAddAnswer(); + // } + // }, [initialAnswerVariants.length, surveyId]); + const handleTypeChange = (type: 'single' | 'multiply') => { setSelectedType(type); - } + }; - const handleAddAnswer = () => { - setAnswerOption([...answerOption, '']); + const handleAddAnswer = async () => { + 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 = () => { setIsEditingQuestion(true); - } + }; const handleTextareaQuestionChange = (event: React.ChangeEvent) => { setTextQuestion(event.target.value); - if (textareaQuestionRef.current) { textareaQuestionRef.current.style.height = 'auto'; textareaQuestionRef.current.style.height = `${textareaQuestionRef.current.scrollHeight}px`; } - } + }; const handleSaveQuestion = () => { setIsEditingQuestion(false); onChangeQuestion(textQuestion); - } + }; const handleQuestionKeyDown = (keyDownEvent: React.KeyboardEvent) => { if (keyDownEvent.key === 'Enter') { keyDownEvent.preventDefault(); - handleSaveQuestion() + handleSaveQuestion(); } - } + }; const handleQuestionBlur = () => { - handleSaveQuestion() - } + handleSaveQuestion(); + }; useEffect(() => { if (isEditingQuestion && textareaQuestionRef.current) { @@ -72,21 +104,51 @@ const QuestionItem: React.FC = ({questionId, initialTextQuest } }, [isEditingQuestion]); - const handleAnswerChange = (index: number, value: string) => { - const newAnswerOption = [...answerOption]; - newAnswerOption[index] = value; - setAnswerOption(newAnswerOption); - } + const handleAnswerChange = async (index: number, value: string) => { + const newAnswerVariants = [...initialAnswerVariants]; + newAnswerVariants[index] = { ...newAnswerVariants[index], text: value }; + onAnswerVariantsChange(newAnswerVariants); - const handleDeleteAnswer = (index: number) => { - const newAnswerOption = answerOption.filter((_, i) => i !== index); - setAnswerOption(newAnswerOption); - setSelectedAnswers(selectedAnswers.filter((i) => i !== index)); + // Обновляем на сервере только если вариант уже существует (имеет id) + if (surveyId && newAnswerVariants[index].id) { + try { + await updateAnswerVariant( + surveyId, + questionId, + newAnswerVariants[index].id!, + { text: value } + ); + } catch (error) { + console.error('Ошибка при обновлении варианта ответа:', error); + } + } }; - useEffect(() => { - setTextQuestion(valueQuestion); - }, [valueQuestion]); + const handleDeleteAnswer = async (index: number) => { + const answerToDelete = initialAnswerVariants[index]; + + 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 () => { try { @@ -133,12 +195,12 @@ const QuestionItem: React.FC = ({questionId, initialTextQuest
- {answerOption.map((answerText, index) => ( + {initialAnswerVariants.map((answer, index) => ( handleAnswerChange(index, value)} onDelete={() => handleDeleteAnswer(index)} toggleSelect={() => toggleSelect(index)} @@ -146,17 +208,15 @@ const QuestionItem: React.FC = ({questionId, initialTextQuest ))}
- +
); -} +}; export default QuestionItem; \ No newline at end of file diff --git a/SurveyFrontend/src/components/QuestionsList/QuestionsList.tsx b/SurveyFrontend/src/components/QuestionsList/QuestionsList.tsx index 3162a33..8a4afb4 100644 --- a/SurveyFrontend/src/components/QuestionsList/QuestionsList.tsx +++ b/SurveyFrontend/src/components/QuestionsList/QuestionsList.tsx @@ -1,4 +1,4 @@ -import React, {useEffect, useState} from "react"; +import React, {useState} from "react"; import QuestionItem from "../QuestionItem/QuestionItem.tsx"; import AddQuestionButton from "../AddQuestionButton/AddQuestionButton.tsx"; import {deleteQuestion, getListQuestions} from "../../api/QuestionApi.ts"; @@ -13,32 +13,25 @@ export interface Question { id: number; text: string; questionType: 'singleanswerquestion' | 'multipleanswerquestion'; - answerVariants?: { + answerVariants: Array<{ id?: number; text: string; - }[]; + }>; } const QuestionsList: React.FC = ({questions, setQuestions, surveyId}) => { const [selectedType, setSelectedType] = useState<'single' | 'multiply'>('single'); - const [localQuestionId, setLocalQuestionId] = useState(2); - const handleAddQuestion = () => { const newQuestion: Question = { - id: localQuestionId, // ID >= 1001 — новые вопросы + id: questions.length + 1, // ID >= 1001 — новые вопросы text: '', questionType: selectedType === 'single' ? 'singleanswerquestion' : 'multipleanswerquestion', + answerVariants: [{text: ''}], }; 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 newQuestions = questions.map((question) => question.id === id ? { ...question, text: value } : question @@ -74,6 +67,15 @@ const QuestionsList: React.FC = ({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 ( <> {questions.map((question) => ( @@ -81,10 +83,13 @@ const QuestionsList: React.FC = ({questions, setQuestions, s key={question.id} questionId={question.id} valueQuestion={question.text} + answerVariants={question.answerVariants} + onAnswerVariantsChange={(variants) => handleAnswerVariantsChange(question.id, variants)} onDeleteQuestion={() => handleDeleteQuestion(question.id)} onChangeQuestion={(value) => handleQuestionChange(question.id, value)} selectedType={selectedType} setSelectedType={setSelectedType} + surveyId={surveyId} /> ))} diff --git a/SurveyFrontend/src/components/SettingSurvey/SettingSurvey.tsx b/SurveyFrontend/src/components/SettingSurvey/SettingSurvey.tsx index 3e858f6..3a02b44 100644 --- a/SurveyFrontend/src/components/SettingSurvey/SettingSurvey.tsx +++ b/SurveyFrontend/src/components/SettingSurvey/SettingSurvey.tsx @@ -2,11 +2,41 @@ import React, {useState} from 'react'; import SurveyInfo from "../SurveyInfo/SurveyInfo.tsx"; import styles from "./SettingSurvey.module.css"; import TimeEvent from "../TimeEvent/TimeEvent.tsx"; +// import {useParams} from "react-router-dom"; +// import {getSurveyById, ISurvey} from "../../api/SurveyApi.ts"; const SettingSurvey: React.FC = () => { const [descriptionSurvey, setDescriptionSurvey] = useState(''); - const [titleSurvey, setTitleSurvey] = useState('Название опроса'); + // const [survey, setSurvey] = useState(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 (
diff --git a/SurveyFrontend/src/components/Survey/Survey.tsx b/SurveyFrontend/src/components/Survey/Survey.tsx index 3874aa6..f9fe704 100644 --- a/SurveyFrontend/src/components/Survey/Survey.tsx +++ b/SurveyFrontend/src/components/Survey/Survey.tsx @@ -6,6 +6,7 @@ import SaveButton from "../SaveButton/SaveButton.tsx"; import {ISurvey, postNewSurvey} from "../../api/SurveyApi.ts"; import {addNewQuestion} from "../../api/QuestionApi.ts"; import {useNavigate} from "react-router-dom"; +import {addNewAnswerVariant} from "../../api/AnswerApi.ts"; const Survey: React.FC = () => { const navigate = useNavigate(); @@ -14,25 +15,9 @@ const Survey: React.FC = () => { const [survey] = useState(null); const [questions, setQuestions] = useState([ - { 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 () => { try { const savedSurvey = await postNewSurvey({ @@ -40,20 +25,48 @@ const Survey: React.FC = () => { description: descriptionSurvey }); - await Promise.all( - questions.map(question => - addNewQuestion(savedSurvey.id, { - title: question.text, - questionType: question.questionType - }) - ) - ); + // Сначала создаем все вопросы + const updatedQuestions: Question[] = []; + for (const question of questions) { + const newQuestion = await addNewQuestion(savedSurvey.id, { + title: question.text, + 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 } + ) + ) + ); + + // Обновляем варианты ответов с их 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) { console.error('Ошибка при сохранении:', error); - alert('Не удалось сохранить опрос'); } }; diff --git a/SurveyFrontend/src/components/SurveyPage/SurveyPage.tsx b/SurveyFrontend/src/components/SurveyPage/SurveyPage.tsx index bb7c74f..ce5a462 100644 --- a/SurveyFrontend/src/components/SurveyPage/SurveyPage.tsx +++ b/SurveyFrontend/src/components/SurveyPage/SurveyPage.tsx @@ -1,26 +1,174 @@ +import React, { useEffect, useState } from "react"; import SurveyInfo from "../SurveyInfo/SurveyInfo.tsx"; -import QuestionsList, {Question} from "../QuestionsList/QuestionsList.tsx"; -import {useEffect, useState} from "react"; -import {getSurveyById, ISurvey, updateSurvey} from "../../api/SurveyApi.ts"; -import {useParams} from "react-router-dom"; -import {addNewQuestion, getListQuestions, updateQuestion} from "../../api/QuestionApi.ts"; +import QuestionsList, { Question } from "../QuestionsList/QuestionsList.tsx"; +import { getSurveyById, ISurvey, updateSurvey } from "../../api/SurveyApi.ts"; +import { useParams } 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/AnswerApi.ts"; -type ActionType = 'create' | 'update'; +// Типы для действий +type ActionType = + | 'update-survey' + | 'create-question' + | 'update-question' + | 'delete-question' + | 'create-answer' + | 'update-answer' + | 'delete-answer'; -class QuestionAction { +// Интерфейсы для данных действий +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: { - surveyId: number; - id?: number; - title?: string; - questionType?: string; - }; + data: SurveyActionData | QuestionActionData | AnswerActionData; + tempId?: number; +} - constructor(type: ActionType, data: { surveyId: number, id?: number, title?: string, questionType?: string }) { - this.type = type; - this.data = data; +class ActionQueue { + private actions: Action[] = []; + private idMap: Record = {}; + + 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 [title, setTitle] = useState(''); - useEffect(() => { if (!surveyId) { console.error('Survey ID is missing'); @@ -52,20 +199,26 @@ export const SurveyPage: React.FC = () => { setLoading(true); const surveyData = await getSurveyById(id); setSurvey(surveyData); - setTitle(surveyData.title); setDescription(surveyData.description); const questionsData = await getListQuestions(id); - const formattedQuestions = questionsData.map(q => ({ - id: q.id, - text: q.title, - questionType: q.questionType as 'singleanswerquestion' | 'multipleanswerquestion', + 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('Не удалось загрузить опрос') + setError('Не удалось загрузить опрос'); } finally { setLoading(false); } @@ -74,77 +227,127 @@ export const SurveyPage: React.FC = () => { fetchData(); }, [surveyId]); - if (loading) return
Загрузка...
; - if (!survey) return
Опрос не найден
; - const handleSave = async () => { - if (!surveyId || !survey) return; + console.log('0. Начало handleSave'); + if (!surveyId || !survey) { + console.log('0.1. Прерывание - нет surveyId или survey', { surveyId, survey }); + return; + } try { setError(null); const id = parseInt(surveyId); + const actionQueue = new ActionQueue(); - const surveyUpdated = await updateSurvey(id, { - title: title, - description: description, + // 1. Обновление опроса + actionQueue.add({ + type: 'update-survey', + data: { id, title, description } as SurveyActionData }); - setSurvey(surveyUpdated); - const actions: QuestionAction[] = []; + // 2. Получаем текущие вопросы с сервера const serverQuestions = await getListQuestions(id); + // 3. Обработка вопросов questions.forEach(question => { - const existsOnServer = serverQuestions.some(q => q.id === question.id); + const isNewQuestion = !serverQuestions.some(q => q.id === question.id); - if (existsOnServer) { - actions.push(new QuestionAction("update", { - surveyId: id, - id: question.id, - title: question.text, - questionType: question.questionType - })); + if (isNewQuestion) { + actionQueue.add({ + type: 'create-question', + data: { + surveyId: id, + title: question.text, + questionType: question.questionType + } as QuestionActionData, + tempId: question.id + }); } else { - actions.push(new QuestionAction("create", { - surveyId: id, - title: question.text, - questionType: question.questionType - })); + // Обновляем только если текст изменился + 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 } + }); + } + } + + // 4. Обработка вариантов ответов + if (question.answerVariants) { + question.answerVariants.forEach(answer => { + if (isNewQuestion || !answer.id) { + actionQueue.add({ + type: 'create-answer', + data: { + surveyId: id, + questionId: question.id, + 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) { - switch (action.type) { - case "create": - await addNewQuestion(id, { - title: action.data.title as string, - questionType: action.data.questionType as 'singleanswerquestion' | 'multipleanswerquestion', - }); - break; - case "update": - if (action.data.id) { - await updateQuestion(id, action.data.id, { - title: action.data.title, - questionType: action.data.questionType - }); - } - break; + // 5. Удаление удаленных вопросов + 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 = updatedQuestions.map(q => ({ - id: q.id, - text: q.title, - questionType: q.questionType as 'singleanswerquestion' | 'multipleanswerquestion', + 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('Не удалось сохранить изменения'); + console.error('Ошибка сохранения:', error); + setError(`Ошибка сохранения: ${error instanceof Error ? error.message : 'Неизвестная ошибка'}`); } }; + if (loading) return
Загрузка...
; + if (!survey) return
Опрос не найден
; + return (
Date: Sun, 25 May 2025 20:24:54 +0500 Subject: [PATCH 04/13] fix add new answer --- SurveyFrontend/src/api/AuthApi.ts | 9 +- SurveyFrontend/src/api/SurveyApi.ts | 24 ---- .../components/AnswerOption/AnswerOption.tsx | 1 - .../components/MySurveyList/MySurveyList.tsx | 13 -- .../components/QuestionItem/QuestionItem.tsx | 31 +++-- .../QuestionsList/QuestionsList.tsx | 43 +++++-- .../RegisterForm/RegisterForm.module.css | 3 +- .../src/components/Survey/Survey.tsx | 5 - .../src/components/SurveyPage/SurveyPage.tsx | 114 ++++++------------ .../components/TimeEvent/TimeEvent.module.css | 4 +- 10 files changed, 87 insertions(+), 160 deletions(-) diff --git a/SurveyFrontend/src/api/AuthApi.ts b/SurveyFrontend/src/api/AuthApi.ts index da61451..f549b25 100644 --- a/SurveyFrontend/src/api/AuthApi.ts +++ b/SurveyFrontend/src/api/AuthApi.ts @@ -11,7 +11,7 @@ interface IRegistrationData extends IAuthData{ lastName: string; } -export const getCurrentUser = async (user: IRegistrationData) => { +export const getCurrentUser = async () => { const token = localStorage.getItem("token"); if (!token) { @@ -23,12 +23,7 @@ export const getCurrentUser = async (user: IRegistrationData) => { ...createRequestConfig('GET'), headers: { 'Authorization': `Bearer ${token}` - }, - body: JSON.stringify({ - email: user.email, - firstName: user.firstName, - lastName: user.lastName, - }) + } }); console.log(response); diff --git a/SurveyFrontend/src/api/SurveyApi.ts b/SurveyFrontend/src/api/SurveyApi.ts index bec3f70..d182347 100644 --- a/SurveyFrontend/src/api/SurveyApi.ts +++ b/SurveyFrontend/src/api/SurveyApi.ts @@ -51,30 +51,6 @@ export const getAllSurveys = async (): Promise => { * postNewSurvey - добавление нового опроса * @param survey */ -// export const postNewSurvey = async (survey: INewSurvey): Promise => { -// const token = localStorage.getItem("token"); -// if (!token) { -// throw new Error("Токен отсутствует"); -// } -// -// try{ -// const response = await fetch(`${BASE_URL}/surveys`, { -// ...createRequestConfig('POST'), -// body: JSON.stringify(survey) -// }) -// -// // return await handleResponse(response); -// -// if (response.status === 200) { -// return await handleResponse(response); -// } -// throw new Error(`Ожидался код 200, получен ${response.status}`); -// } catch (error) { -// console.error(`Error when adding a new survey: ${error}`); -// throw error; -// } -// } - export const postNewSurvey = async (survey: INewSurvey): Promise => { const token = localStorage.getItem("token"); if (!token) { diff --git a/SurveyFrontend/src/components/AnswerOption/AnswerOption.tsx b/SurveyFrontend/src/components/AnswerOption/AnswerOption.tsx index 639d027..811d693 100644 --- a/SurveyFrontend/src/components/AnswerOption/AnswerOption.tsx +++ b/SurveyFrontend/src/components/AnswerOption/AnswerOption.tsx @@ -29,7 +29,6 @@ const AnswerOption: React.FC = ({index, value, onChange, onDe const handleTextareaChange = (event: React.ChangeEvent) => { setCurrentValue(event.target.value); - // Автоматическое изменение высоты if (textAreaRef.current) { textAreaRef.current.style.height = 'auto'; textAreaRef.current.style.height = `${textAreaRef.current.scrollHeight}px`; diff --git a/SurveyFrontend/src/components/MySurveyList/MySurveyList.tsx b/SurveyFrontend/src/components/MySurveyList/MySurveyList.tsx index 963092a..6fa3d1a 100644 --- a/SurveyFrontend/src/components/MySurveyList/MySurveyList.tsx +++ b/SurveyFrontend/src/components/MySurveyList/MySurveyList.tsx @@ -24,13 +24,6 @@ export const MySurveyList = () => { setSurveys(surveysWithStatus); } catch (error) { console.error('Ошибка при получении списка опросов:', error); - - // if (error instanceof Error && error.message.includes("401")) { - // // Если ошибка 401, перенаправляем на страницу входа - // navigate('/login'); - // } else { - // alert("Ошибка при загрузке опросов: " + (error instanceof Error && error.message)); - // } } }; fetchSurvey(); @@ -52,12 +45,6 @@ export const MySurveyList = () => { } } catch (error) { console.error('Ошибка при удалении опроса:', error); - - // if (error instanceof Error && error.message.includes("401")) { - // navigate('/login'); - // } else { - // alert("Ошибка при удалении опроса: " + (error instanceof Error ? error.message : 'Неизвестная ошибка')); - // } } }; diff --git a/SurveyFrontend/src/components/QuestionItem/QuestionItem.tsx b/SurveyFrontend/src/components/QuestionItem/QuestionItem.tsx index 6ac4221..50dc767 100644 --- a/SurveyFrontend/src/components/QuestionItem/QuestionItem.tsx +++ b/SurveyFrontend/src/components/QuestionItem/QuestionItem.tsx @@ -26,7 +26,8 @@ interface QuestionItemProps { const QuestionItem: React.FC = ({ questionId, - initialTextQuestion = `Вопрос ${questionId}`, + // initialTextQuestion = `Вопрос ${questionId}`, + initialTextQuestion = `Вопрос`, valueQuestion, answerVariants: initialAnswerVariants, onChangeQuestion, @@ -45,26 +46,24 @@ const QuestionItem: React.FC = ({ setTextQuestion(valueQuestion); }, [valueQuestion]); - // useEffect(() => { - // if (initialAnswerVariants.length === 0 && surveyId) { - // handleAddAnswer(); - // } - // }, [initialAnswerVariants.length, surveyId]); - const handleTypeChange = (type: 'single' | 'multiply') => { setSelectedType(type); }; const handleAddAnswer = async () => { - if (surveyId) { - try { - const newAnswer = await addNewAnswerVariant(surveyId, questionId, { text: '' }); - onAnswerVariantsChange([...initialAnswerVariants, { id: newAnswer.id, text: '' }]); - } catch (error) { - console.error('Ошибка при добавлении варианта ответа:', error); - } - } else { + if (!surveyId) { onAnswerVariantsChange([...initialAnswerVariants, { text: '' }]); + return; + } + + try { + const newAnswer = await addNewAnswerVariant(surveyId, questionId, { text: '' }); + onAnswerVariantsChange([...initialAnswerVariants, { + id: newAnswer.id, + text: newAnswer.text + }]); + } catch (error) { + console.error('Ошибка при добавлении варианта ответа:', error); } }; @@ -109,7 +108,6 @@ const QuestionItem: React.FC = ({ newAnswerVariants[index] = { ...newAnswerVariants[index], text: value }; onAnswerVariantsChange(newAnswerVariants); - // Обновляем на сервере только если вариант уже существует (имеет id) if (surveyId && newAnswerVariants[index].id) { try { await updateAnswerVariant( @@ -134,7 +132,6 @@ const QuestionItem: React.FC = ({ 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 })); diff --git a/SurveyFrontend/src/components/QuestionsList/QuestionsList.tsx b/SurveyFrontend/src/components/QuestionsList/QuestionsList.tsx index 8a4afb4..24dcdb9 100644 --- a/SurveyFrontend/src/components/QuestionsList/QuestionsList.tsx +++ b/SurveyFrontend/src/components/QuestionsList/QuestionsList.tsx @@ -1,7 +1,8 @@ import React, {useState} from "react"; import QuestionItem from "../QuestionItem/QuestionItem.tsx"; import AddQuestionButton from "../AddQuestionButton/AddQuestionButton.tsx"; -import {deleteQuestion, getListQuestions} from "../../api/QuestionApi.ts"; +import {addNewQuestion, deleteQuestion, getListQuestions} from "../../api/QuestionApi.ts"; +import {addNewAnswerVariant} from "../../api/AnswerApi.ts"; interface QuestionsListProps { questions: Question[]; @@ -22,14 +23,38 @@ export interface Question { const QuestionsList: React.FC = ({questions, setQuestions, surveyId}) => { const [selectedType, setSelectedType] = useState<'single' | 'multiply'>('single'); - const handleAddQuestion = () => { - const newQuestion: Question = { - id: questions.length + 1, // ID >= 1001 — новые вопросы - text: '', - questionType: selectedType === 'single' ? 'singleanswerquestion' : 'multipleanswerquestion', - answerVariants: [{text: ''}], - }; - setQuestions([...questions, newQuestion]); + const handleAddQuestion = async () => { + if (!surveyId) { + const newQuestion: Question = { + id: questions.length + 1, + text: '', + questionType: selectedType === 'single' ? 'singleanswerquestion' : 'multipleanswerquestion', + answerVariants: [{ text: '' }], + }; + setQuestions([...questions, newQuestion]); + return; + } + + try { + const newQuestion = await addNewQuestion(surveyId, { + title: '', + questionType: selectedType === 'single' ? 'singleanswerquestion' : 'multipleanswerquestion' + }); + + const questionToAdd: Question = { + id: newQuestion.id, + text: newQuestion.title, + questionType: newQuestion.questionType as 'singleanswerquestion' | 'multipleanswerquestion', + answerVariants: [] + }; + + const newAnswer = await addNewAnswerVariant(surveyId, newQuestion.id, { text: '' }); + questionToAdd.answerVariants = [{ id: newAnswer.id, text: newAnswer.text }]; + + setQuestions([...questions, questionToAdd]); + } catch (error) { + console.error('Ошибка при добавлении вопроса:', error); + } }; const handleQuestionChange = (id: number, value: string) => { diff --git a/SurveyFrontend/src/components/RegisterForm/RegisterForm.module.css b/SurveyFrontend/src/components/RegisterForm/RegisterForm.module.css index 642ea69..cac537d 100644 --- a/SurveyFrontend/src/components/RegisterForm/RegisterForm.module.css +++ b/SurveyFrontend/src/components/RegisterForm/RegisterForm.module.css @@ -48,14 +48,13 @@ opacity: 0; } -/* Отключаем стиль для input, когда в нём есть данные */ .input:not(:placeholder-shown) { color: black; opacity: 1; } .input:focus { - border-bottom: 2px solid black; /* Чёрная граница при фокусе */ + border-bottom: 2px solid black; } .signUp{ diff --git a/SurveyFrontend/src/components/Survey/Survey.tsx b/SurveyFrontend/src/components/Survey/Survey.tsx index f9fe704..b0f5448 100644 --- a/SurveyFrontend/src/components/Survey/Survey.tsx +++ b/SurveyFrontend/src/components/Survey/Survey.tsx @@ -25,7 +25,6 @@ const Survey: React.FC = () => { description: descriptionSurvey }); - // Сначала создаем все вопросы const updatedQuestions: Question[] = []; for (const question of questions) { const newQuestion = await addNewQuestion(savedSurvey.id, { @@ -33,14 +32,12 @@ const Survey: React.FC = () => { 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 => @@ -52,7 +49,6 @@ const Survey: React.FC = () => { ) ); - // Обновляем варианты ответов с их ID updatedQuestion.answerVariants = newVariants.map((variant: { id: number, text: string }) => ({ id: variant.id, text: variant.text @@ -62,7 +58,6 @@ const Survey: React.FC = () => { updatedQuestions.push(updatedQuestion); } - // Обновляем состояние с новыми ID setQuestions(updatedQuestions); navigate('/my-surveys'); } catch (error) { diff --git a/SurveyFrontend/src/components/SurveyPage/SurveyPage.tsx b/SurveyFrontend/src/components/SurveyPage/SurveyPage.tsx index ce5a462..e6e4655 100644 --- a/SurveyFrontend/src/components/SurveyPage/SurveyPage.tsx +++ b/SurveyFrontend/src/components/SurveyPage/SurveyPage.tsx @@ -8,7 +8,6 @@ import styles from "./SurveyPage.module.css"; import SaveButton from "../SaveButton/SaveButton.tsx"; import { addNewAnswerVariant, deleteAnswerVariant, getAnswerVariants, IAnswerVariant, updateAnswerVariant } from "../../api/AnswerApi.ts"; -// Типы для действий type ActionType = | 'update-survey' | 'create-question' @@ -18,7 +17,6 @@ type ActionType = | 'update-answer' | 'delete-answer'; -// Интерфейсы для данных действий interface SurveyActionData { id: number; title: string; @@ -54,16 +52,6 @@ class ActionQueue { } 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) { @@ -147,21 +135,13 @@ class ActionQueue { } 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); + console.error(error); throw error; } @@ -228,86 +208,62 @@ export const SurveyPage: React.FC = () => { }, [surveyId]); const handleSave = async () => { - console.log('0. Начало handleSave'); - if (!surveyId || !survey) { - console.log('0.1. Прерывание - нет surveyId или survey', { surveyId, survey }); - return; - } + if (!surveyId || !survey) return; try { setError(null); const id = parseInt(surveyId); const actionQueue = new ActionQueue(); - // 1. Обновление опроса actionQueue.add({ type: 'update-survey', data: { id, title, description } as SurveyActionData }); - // 2. Получаем текущие вопросы с сервера const serverQuestions = await getListQuestions(id); - // 3. Обработка вопросов questions.forEach(question => { - const isNewQuestion = !serverQuestions.some(q => q.id === question.id); - - if (isNewQuestion) { + const serverQuestion = serverQuestions.find(q => q.id === question.id); + if (serverQuestion && + (serverQuestion.title !== question.text || + serverQuestion.questionType !== question.questionType)) { actionQueue.add({ - type: 'create-question', + type: 'update-question', data: { surveyId: id, + id: question.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, - id: question.id, - title: question.text, - questionType: question.questionType - } as QuestionActionData & { id: number } - }); - } - } - - // 4. Обработка вариантов ответов - if (question.answerVariants) { - question.answerVariants.forEach(answer => { - if (isNewQuestion || !answer.id) { - actionQueue.add({ - type: 'create-answer', - data: { - surveyId: id, - questionId: question.id, - 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 } - }); - } + } as QuestionActionData & { id: number } }); } }); - // 5. Удаление удаленных вопросов + 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({ @@ -320,10 +276,8 @@ export const SurveyPage: React.FC = () => { } }); - // Выполняем все действия await actionQueue.execute(); - // Обновляем данные const updatedQuestions = await getListQuestions(id); const formattedQuestions = await Promise.all(updatedQuestions.map(async q => { const answerVariants = await getAnswerVariants(id, q.id); diff --git a/SurveyFrontend/src/components/TimeEvent/TimeEvent.module.css b/SurveyFrontend/src/components/TimeEvent/TimeEvent.module.css index 4417f66..0809dc9 100644 --- a/SurveyFrontend/src/components/TimeEvent/TimeEvent.module.css +++ b/SurveyFrontend/src/components/TimeEvent/TimeEvent.module.css @@ -23,7 +23,7 @@ .inputDate{ border: 3px solid #007AFF26; padding: 12px 107px 12px 21px; - font-size: 20px; /*??????????????????????? в макете указано bodyLarge/Size*/ + font-size: 20px; font-weight: 400; border-radius: 3px; } @@ -31,7 +31,7 @@ .inputTime{ border: 3px solid #007AFF26; padding: 12px 42px; - font-size: 20px; /*??????????????????????? в макете указано bodyLarge/Size*/ + font-size: 20px; font-weight: 400; border-radius: 3px; } From 8182bc1c430b63d7ab22109bb7d23dac439c27fb Mon Sep 17 00:00:00 2001 From: Tatiana Nikolaeva Date: Sun, 25 May 2025 22:46:52 +0500 Subject: [PATCH 05/13] fix question type --- .../components/AnswerOption/AnswerOption.tsx | 5 ++-- .../components/QuestionItem/QuestionItem.tsx | 26 ++++++++++++------- .../QuestionsList/QuestionsList.tsx | 24 ++++++++++------- .../src/components/Survey/Survey.tsx | 2 +- .../src/components/SurveyPage/SurveyPage.tsx | 8 +++--- .../TypeDropdown/TypeDropdown.module.css | 1 + .../components/TypeDropdown/TypeDropdown.tsx | 14 +++++----- 7 files changed, 46 insertions(+), 34 deletions(-) diff --git a/SurveyFrontend/src/components/AnswerOption/AnswerOption.tsx b/SurveyFrontend/src/components/AnswerOption/AnswerOption.tsx index 811d693..b042df9 100644 --- a/SurveyFrontend/src/components/AnswerOption/AnswerOption.tsx +++ b/SurveyFrontend/src/components/AnswerOption/AnswerOption.tsx @@ -9,7 +9,7 @@ interface AnswerOptionProps{ value: string; onChange: (value: string) => void; onDelete:(index: number) => void; - selectedType: 'single' | 'multiply'; + selectedType: 'SingleAnswerQuestion' | 'MultipleAnswerQuestion'; toggleSelect: () => void; } @@ -38,7 +38,6 @@ const AnswerOption: React.FC = ({index, value, onChange, onDe useEffect(() => { if (isEditing && textAreaRef.current) { textAreaRef.current.focus(); - // Установка начальной высоты textAreaRef.current.style.height = 'auto'; textAreaRef.current.style.height = `${textAreaRef.current.scrollHeight}px`; } @@ -72,7 +71,7 @@ const AnswerOption: React.FC = ({index, value, onChange, onDe className={`${styles.buttonMarker} ${isEditing ? styles.editing : ''}`} onClick={toggleSelect} > - {selectedType === 'single' ? < Single className={styles.answerIcon} /> : } + {selectedType === 'SingleAnswerQuestion' ? < Single className={styles.answerIcon} /> : } {isEditing ? (