fixed getting data for completing a survey
This commit is contained in:
parent
88dcb63232
commit
2ec9354fc1
13 changed files with 128 additions and 55 deletions
|
|
@ -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>
|
||||
|
|
|
|||
|
|
@ -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;
|
||||
|
|
|
|||
|
|
@ -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>
|
||||
|
|
|
|||
|
|
@ -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}
|
||||
|
|
|
|||
|
|
@ -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>
|
||||
|
|
|
|||
|
|
@ -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>
|
||||
|
|
|
|||
|
|
@ -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>
|
||||
)}
|
||||
|
||||
|
|
|
|||
|
|
@ -25,3 +25,18 @@
|
|||
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;
|
||||
}
|
||||
|
|
@ -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>
|
||||
)
|
||||
}
|
||||
|
|
|
|||
|
|
@ -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>
|
||||
|
|
|
|||
|
|
@ -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();
|
||||
|
||||
|
|
|
|||
6
SurveyFrontend/src/hooks/useRouteReadOnly.ts
Normal file
6
SurveyFrontend/src/hooks/useRouteReadOnly.ts
Normal file
|
|
@ -0,0 +1,6 @@
|
|||
import { useLocation } from 'react-router-dom';
|
||||
|
||||
export const useRouteReadOnly = () => {
|
||||
const location = useLocation();
|
||||
return location.pathname.includes('/complete-survey/');
|
||||
};
|
||||
|
|
@ -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>
|
||||
)
|
||||
|
|
|
|||
Loading…
Add table
Add a link
Reference in a new issue