fixed getting data for completing a survey

This commit is contained in:
Tatiana Nikolaeva 2025-06-02 16:00:19 +05:00
parent 88dcb63232
commit 2ec9354fc1
13 changed files with 128 additions and 55 deletions

View file

@ -9,7 +9,6 @@ import {MySurveyList} from "./components/MySurveyList/MySurveyList.tsx";
import AuthForm from "./pages/AuthForm/AuthForm.tsx";
import {SurveyPage} from "./components/SurveyPage/SurveyPage.tsx";
import CompleteSurvey from "./pages/CompleteSurvey/CompleteSurvey.tsx";
import CompletingSurvey from "./components/CompletingSurvey/CompletingSurvey.tsx";
const App = () => {
return(
@ -33,9 +32,7 @@ const App = () => {
<Route path="results" element={<Results />} />
</Route>
<Route path='/complete-survey' element={<CompleteSurvey/>}>
<Route index element={<CompletingSurvey/>}/>
</Route>
<Route path='/complete-survey/:surveyId' element={<CompleteSurvey/>}/>
<Route path="*" element={<AuthForm />} />
</Routes>

View file

@ -15,8 +15,9 @@
font-size: 18px;
font-weight: 500;
word-break: break-word;
width: 70%;
/*width: 70%;*/
padding: 0;
margin-right: 150px;
line-height: 24px;
cursor: text;
margin-top: 2px;

View file

@ -6,6 +6,7 @@ import Multiple from '../../assets/emptyCheckbox.svg?react';
import SelectedSingle from '../../assets/radio_button_checked.svg?react'
import SelectedMultiple from '../../assets/check_box.svg?react';
import TextareaAutosize from 'react-textarea-autosize';
import {useRouteReadOnly} from "../../hooks/useRouteReadOnly.ts";
interface AnswerOptionProps{
index: number;
@ -15,14 +16,14 @@ interface AnswerOptionProps{
selectedType: 'SingleAnswerQuestion' | 'MultipleAnswerQuestion';
isSelected?: boolean;
toggleSelect?: () => void;
isCompleteSurveyActive?: boolean;
}
const AnswerOption: React.FC<AnswerOptionProps> = ({index, value, onChange, onDelete, selectedType, isSelected, toggleSelect, isCompleteSurveyActive = false}) => {
const AnswerOption: React.FC<AnswerOptionProps> = ({index, value, onChange, onDelete, selectedType, isSelected, toggleSelect}) => {
const [currentValue, setCurrentValue] = useState(value);
const [isEditing, setIsEditing] = useState(false);
const textAreaRef = useRef<HTMLTextAreaElement>(null);
const isReadOnly = useRouteReadOnly();
useEffect(() => {
setCurrentValue(value);
@ -71,7 +72,7 @@ const AnswerOption: React.FC<AnswerOptionProps> = ({index, value, onChange, onDe
}, [isEditing]);
const handleMarkerClick = () => {
if (isCompleteSurveyActive && toggleSelect) {
if (isReadOnly && toggleSelect) {
toggleSelect();
}
};
@ -79,7 +80,7 @@ const AnswerOption: React.FC<AnswerOptionProps> = ({index, value, onChange, onDe
return (
<div className={styles.answer}>
{isCompleteSurveyActive ? (
{isReadOnly ? (
<button
className={`${styles.buttonMarker} ${isSelected ? styles.selected : ''}`}
onClick={handleMarkerClick}
@ -108,7 +109,7 @@ const AnswerOption: React.FC<AnswerOptionProps> = ({index, value, onChange, onDe
</button>
)}
{isCompleteSurveyActive ? (
{isReadOnly ? (
<button className={styles.textAnswer}>
{currentValue || `Ответ ${index}`}
</button>
@ -128,7 +129,7 @@ const AnswerOption: React.FC<AnswerOptionProps> = ({index, value, onChange, onDe
</button>
)}
{!isCompleteSurveyActive && (
{!isReadOnly && (
<button className={styles.deleteButton} onClick={() => onDelete?.(index)}>
<Delete />
</button>

View file

@ -1,25 +1,85 @@
import SurveyInfo from "../SurveyInfo/SurveyInfo.tsx";
import QuestionsList, {Question} from "../QuestionsList/QuestionsList.tsx";
import {useState} from "react";
import {useEffect, useState} from "react";
import styles from './CompletingSurvey.module.css'
import { useParams} from "react-router-dom";
import {getSurveyById, ISurvey} from "../../api/SurveyApi.ts";
import {getListQuestions} from "../../api/QuestionApi.ts";
import {getAnswerVariants, IAnswerVariant} from "../../api/AnswerVariantsApi.ts";
export const CompletingSurvey = () => {
const [titleSurvey, setTitleSurvey] = useState("Название опроса");
const [descriptionSurvey, setDescriptionSurvey] = useState("");
const [questions, setQuestions] = useState<Question[]>([
{ id: 1, text: 'Вопрос 1', questionType: 'SingleAnswerQuestion', answerVariants: [{ id: 1, text: 'Ответ 1' },
{ id: 2, text: 'Ответ 1' }, { id: 3, text: 'Ответ 1' }]},
{ id: 2, text: 'Вопрос 2', questionType: 'MultipleAnswerQuestion', answerVariants: [{ id: 1, text: 'Ответ 1' },
{ id: 2, text: 'Ответ 1' }, { id: 3, text: 'Ответ 1' }]}
]);
// const [titleSurvey, setTitleSurvey] = useState("Название опроса");
// const [descriptionSurvey, setDescriptionSurvey] = useState("");
// const [questions, setQuestions] = useState<Question[]>([
// { id: 1, text: 'Вопрос 1', questionType: 'SingleAnswerQuestion', answerVariants: [{ id: 1, text: 'Ответ 1' },
// { id: 2, text: 'Ответ 1' }, { id: 3, text: 'Ответ 1' }]},
// { id: 2, text: 'Вопрос 2', questionType: 'MultipleAnswerQuestion', answerVariants: [{ id: 1, text: 'Ответ 1' },
// { id: 2, text: 'Ответ 1' }, { id: 3, text: 'Ответ 1' }]}
// ]);
// const [questions, setQuestions] = useState<Question[]>([]);
// const [loading, setLoading] = useState(true);
// const [error, setError] = useState<string | null>(null);
//
// const [description, setDescription] = useState('');
// const [title, setTitle] = useState('');
//
// const { survey, setSurvey } = useOutletContext<{
// survey: ISurvey;
// setSurvey: (survey: ISurvey) => void;
// }>();
const {surveyId} = useParams<{surveyId: string}>();
const [survey, setSurvey] = useState<ISurvey | null>(null);
const [questions, setQuestions] = useState<Question[]>([]);
const [loading, setLoading] = useState(true);
const [error, setError] = useState<string | null>(null);
useEffect(() => {
const fetchSurveyData = async () => {
try {
setLoading(true);
if (!surveyId) return;
const surveyData = await getSurveyById(parseInt(surveyId));
setSurvey(surveyData);
const questionsData = await getListQuestions(parseInt(surveyId));
const formattedQuestions = await Promise.all(questionsData.map(async q => {
const answerVariants = await getAnswerVariants(parseInt(surveyId), q.id);
return {
id: q.id,
text: q.title,
questionType: q.questionType as 'SingleAnswerQuestion' | 'MultipleAnswerQuestion',
answerVariants: answerVariants.map((a: IAnswerVariant) => ({
id: a.id,
text: a.text
}))
};
}));
setQuestions(formattedQuestions);
} catch (error) {
console.error('Ошибка загрузки опроса:', error);
setError('Не удалось загрузить опрос');
} finally {
setLoading(false);
}
};
fetchSurveyData();
}, [surveyId]);
if (loading) return <div>Загрузка...</div>;
if (error) return <div>{error}</div>;
if (!survey) return <div>Опрос не найден</div>;
return (
<div className={styles.survey}>
<SurveyInfo
titleSurvey={titleSurvey}
descriptionSurvey={descriptionSurvey}
setDescriptionSurvey={setDescriptionSurvey}
setTitleSurvey={setTitleSurvey}
titleSurvey={survey.title}
descriptionSurvey={survey.description}
setDescriptionSurvey={(value) => setSurvey({ ...survey, description: value })}
setTitleSurvey={(value) => setSurvey({ ...survey, title: value })}
/>
<QuestionsList
questions={questions}

View file

@ -10,7 +10,6 @@ const Header: React.FC = () => {
const isCreateSurveyActive = location.pathname.startsWith('/survey/create');
const isMySurveysActive = location.pathname === '/my-surveys';
const isCompleteSurveyActive = location.pathname === '/complete-survey';
const isSurveyViewPage = location.pathname.startsWith('/survey/') &&
!location.pathname.startsWith('/survey/create');
@ -37,13 +36,6 @@ const Header: React.FC = () => {
Мои опросы
{(isMySurveysActive || isSurveyViewPage) && <hr className={styles.activeLine}/>}
</Link>
<Link
to='/complete-survey'
className={`${styles.pageLink} ${isCompleteSurveyActive ? styles.active : ''}`}
>
Прохождение опроса
{isCompleteSurveyActive && <hr className={styles.activeLine}/>}
</Link>
</nav>
<Account href={'/profile'} />
</div>

View file

@ -10,8 +10,8 @@ import {
getAnswerVariants,
updateAnswerVariant
} from "../../api/AnswerVariantsApi.ts";
import {useLocation} from "react-router-dom";
import TextareaAutosize from "react-textarea-autosize";
import {useRouteReadOnly} from "../../hooks/useRouteReadOnly.ts";
interface QuestionItemProps {
questionId: number;
@ -23,7 +23,6 @@ interface QuestionItemProps {
onDeleteQuestion: (index: number) => Promise<void>;
initialQuestionType: 'SingleAnswerQuestion' | 'MultipleAnswerQuestion';
onQuestionTypeChange: (type: 'SingleAnswerQuestion' | 'MultipleAnswerQuestion') => void;
surveyId?: number;
}
@ -45,8 +44,7 @@ const QuestionItem: React.FC<QuestionItemProps> = ({
const [questionType, setQuestionType] = useState<'SingleAnswerQuestion' | 'MultipleAnswerQuestion'>(initialQuestionType);
const textareaQuestionRef = useRef<HTMLTextAreaElement>(null);
const location = useLocation();
const isCompleteSurveyActive = location.pathname === '/complete-survey';
const isReadOnly = useRouteReadOnly();
useEffect(() => {
@ -63,6 +61,8 @@ const QuestionItem: React.FC<QuestionItemProps> = ({
};
const handleAddAnswer = async () => {
if (isReadOnly) return
if (!surveyId) {
onAnswerVariantsChange([...initialAnswerVariants, { text: '' }]);
return;
@ -167,10 +167,8 @@ const QuestionItem: React.FC<QuestionItemProps> = ({
const toggleSelect = (index: number) => {
if (initialQuestionType === 'SingleAnswerQuestion') {
// Для одиночного выбора: заменяем массив одним выбранным индексом
setSelectedAnswers([index]);
} else {
// Для множественного выбора: добавляем/удаляем индекс
setSelectedAnswers(prev =>
prev.includes(index)
? prev.filter(i => i !== index)
@ -181,7 +179,7 @@ const QuestionItem: React.FC<QuestionItemProps> = ({
return (
<div className={styles.questionCard}>
{isCompleteSurveyActive ? (
{isReadOnly ? (
<div>
<div className={styles.questionContainer}>
<h2 className={styles.textQuestion}>{textQuestion || initialTextQuestion}</h2>
@ -194,7 +192,6 @@ const QuestionItem: React.FC<QuestionItemProps> = ({
value={answer.text}
isSelected={selectedAnswers.includes(index)}
toggleSelect={() => toggleSelect(index)}
isCompleteSurveyActive={isCompleteSurveyActive}
/>
))}
</div>

View file

@ -3,8 +3,8 @@ import QuestionItem from "../QuestionItem/QuestionItem.tsx";
import AddQuestionButton from "../AddQuestionButton/AddQuestionButton.tsx";
import {addNewQuestion, deleteQuestion, getListQuestions} from "../../api/QuestionApi.ts";
import {addNewAnswerVariant} from "../../api/AnswerVariantsApi.ts";
import {useLocation} from "react-router-dom";
import styles from './QuestionsList.module.css'
import {useRouteReadOnly} from "../../hooks/useRouteReadOnly.ts";
interface QuestionsListProps {
questions: Question[];
@ -23,8 +23,7 @@ export interface Question {
}
const QuestionsList: React.FC<QuestionsListProps> = ({questions, setQuestions, surveyId}) => {
const location = useLocation();
const isCompleteSurveyActive = location.pathname === '/complete-survey';
const isReadOnly = useRouteReadOnly();
const handleAddQuestion = async () => {
if (!surveyId) {
@ -128,7 +127,7 @@ const QuestionsList: React.FC<QuestionsListProps> = ({questions, setQuestions, s
surveyId={surveyId}
/>
))}
{!isCompleteSurveyActive ? <AddQuestionButton onClick={handleAddQuestion} /> : (
{!isReadOnly ? <AddQuestionButton onClick={handleAddQuestion} /> : (
<button className={styles.departur_button}>Отправить</button>
)}

View file

@ -24,4 +24,19 @@
font-size: 24px;
font-weight: 600;
border-radius: 4px;
}
.copyButton {
padding: 10px 15px;
background-color: #4CAF50;
color: white;
border: none;
border-radius: 4px;
cursor: pointer;
font-size: 16px;
margin-bottom: 20px;
}
.copyButton:hover {
background-color: #45a049;
}

View file

@ -18,6 +18,15 @@ const SettingSurvey: React.FC = () => {
const [descriptionSurvey, setDescriptionSurvey] = useState('');
const [titleSurvey, setTitleSurvey] = useState('');
const handleCopyLink = () => {
if (!survey?.id)
return;
const link = `${window.location.origin}/complete-survey/${survey.id}`;
navigator.clipboard.writeText(link)
.then(() => console.log('Copied!'))
.catch(error => console.error(`Не удалось скопировать ссылку: ${error}`));
}
return (
<div className={styles.settingSurvey}>
{isSettingCreatePage ? (
@ -43,6 +52,7 @@ const SettingSurvey: React.FC = () => {
<h2>Параметры видимости</h2>
</div>
<SaveButton onClick={() => {}}/>
<button onClick={handleCopyLink} className={styles.copyButton}>Копировать ссылку</button>
</div>
)
}

View file

@ -3,6 +3,7 @@ import styles from './SurveyInfo.module.css'
import AddDescripImg from '../../assets/add_circle.svg?react';
import TextareaAutosize from 'react-textarea-autosize';
import {useLocation} from "react-router-dom";
import {useRouteReadOnly} from "../../hooks/useRouteReadOnly.ts";
interface SurveyInfoProps {
@ -20,10 +21,11 @@ const SurveyInfo: React.FC<SurveyInfoProps> = ({titleSurvey, setDescriptionSurve
const descriptionTextareaRef = useRef<HTMLTextAreaElement>(null);
const location = useLocation();
const isCompleteSurveyActive = location.pathname === '/complete-survey';
const isSurveyViewPage = location.pathname.startsWith('/survey/') &&
!location.pathname.startsWith('/survey/create');
const isReadOnly = useRouteReadOnly();
const handleDescriptionChange = (descripEvent: React.ChangeEvent<HTMLTextAreaElement>) => {
setDescriptionSurvey?.(descripEvent.target.value);
};
@ -88,7 +90,7 @@ const SurveyInfo: React.FC<SurveyInfoProps> = ({titleSurvey, setDescriptionSurve
}
const renderTitle = () => {
if (isCompleteSurveyActive) {
if (isReadOnly) {
return (
<button className={styles.titleSurvey}>
<h1>{titleSurvey || 'Название опроса'}</h1>
@ -120,7 +122,7 @@ const SurveyInfo: React.FC<SurveyInfoProps> = ({titleSurvey, setDescriptionSurve
};
const renderDescription = () => {
if (isCompleteSurveyActive) {
if (isReadOnly) {
return descriptionSurvey ? (
<p className={styles.desc}>{descriptionSurvey}</p>
) : 'Описание';
@ -167,7 +169,7 @@ const SurveyInfo: React.FC<SurveyInfoProps> = ({titleSurvey, setDescriptionSurve
{renderTitle()}
{renderDescription()}
{(isSurveyViewPage || isCompleteSurveyActive) && createdAt && (
{(isSurveyViewPage || isReadOnly) && createdAt && (
<p className={styles.createdAt}>Дата создания: {addDate()}</p>
)}
</div>

View file

@ -153,11 +153,9 @@ class ActionQueue {
}
export const SurveyPage: React.FC = () => {
// const [survey, setSurvey] = useState<ISurvey | null>(null);
const [questions, setQuestions] = useState<Question[]>([]);
const [loading, setLoading] = useState(true);
const [error, setError] = useState<string | null>(null);
// const { surveyId } = useParams<{ surveyId: string }>();
const [description, setDescription] = useState('');
const [title, setTitle] = useState('');
@ -173,7 +171,6 @@ export const SurveyPage: React.FC = () => {
return;
}
// const id = parseInt(survey.id);
const id = survey.id;
if (isNaN(id)) {
console.error('Invalid survey ID');
@ -183,7 +180,6 @@ export const SurveyPage: React.FC = () => {
const fetchData = async () => {
try {
setLoading(true);
// const surveyData = await getSurveyById(id);
setSurvey(survey);
setTitle(survey.title);
setDescription(survey.description);
@ -218,7 +214,6 @@ export const SurveyPage: React.FC = () => {
try {
setError(null);
// const id = parseInt(survey.id);
const id = survey.id;
const actionQueue = new ActionQueue();

View file

@ -0,0 +1,6 @@
import { useLocation } from 'react-router-dom';
export const useRouteReadOnly = () => {
const location = useLocation();
return location.pathname.includes('/complete-survey/');
};

View file

@ -1,11 +1,9 @@
import Header from "../../components/Header/Header.tsx";
import styles from './CompleteSurvey.module.css'
import CompletingSurvey from "../../components/CompletingSurvey/CompletingSurvey.tsx";
export const CompleteSurvey = () => {
return(
<div className={styles.layout}>
<Header/>
<CompletingSurvey/>
</div>
)