Merge branch 'correction' into 'unstable'

fix results page

See merge request internship-2025/survey-webapp/survey-webapp!30
This commit is contained in:
Tatyana Nikolaeva 2025-06-10 08:43:44 +00:00
commit d93c7ab3c9
9 changed files with 267 additions and 136 deletions

View file

@ -11,6 +11,10 @@ export const handleUnauthorizedError = (error: unknown) => {
window.location.href = '/login'; window.location.href = '/login';
console.log('Сессия истекла. Перенаправление на страницу входа.'); console.log('Сессия истекла. Перенаправление на страницу входа.');
} }
if (error instanceof Error && error.message.includes('Токен отсутствует')) {
window.location.href = '/login';
console.log('Сессия истекла. Перенаправление на страницу входа.');
}
}; };
/** /**

View file

@ -58,7 +58,8 @@ export const updateQuestion = async (id: number, question: Partial<INewQuestion>
questionType: question.questionType, questionType: question.questionType,
}), }),
}) })
return await handleResponse(response) const result = await handleResponse(response);
return result;
} }
catch(error){ catch(error){
handleUnauthorizedError(error); handleUnauthorizedError(error);

View file

@ -2,6 +2,7 @@ import React, { useEffect, useState } from 'react';
import styles from './Account.module.css'; import styles from './Account.module.css';
import AccountImg from '../../assets/account.svg?react'; import AccountImg from '../../assets/account.svg?react';
import { getCurrentUser } from '../../api/AuthApi'; import { getCurrentUser } from '../../api/AuthApi';
import {handleUnauthorizedError} from "../../api/BaseApi.ts";
interface AccountProps { interface AccountProps {
href: string; href: string;
@ -17,6 +18,7 @@ const Account: React.FC<AccountProps> = ({ href }) => {
const userData = await getCurrentUser(); const userData = await getCurrentUser();
setUserName(`${userData.firstName} ${userData.lastName}`); setUserName(`${userData.firstName} ${userData.lastName}`);
} catch (error) { } catch (error) {
handleUnauthorizedError(error)
console.error("Ошибка загрузки данных пользователя:", error); console.error("Ошибка загрузки данных пользователя:", error);
localStorage.removeItem("user"); localStorage.removeItem("user");
} finally { } finally {

View file

@ -53,9 +53,9 @@ const QuestionItem: React.FC<QuestionItemProps> = ({
setTextQuestion(valueQuestion); setTextQuestion(valueQuestion);
}, [valueQuestion]); }, [valueQuestion]);
useEffect(() => { // useEffect(() => {
setQuestionType(initialQuestionType); // setQuestionType(initialQuestionType);
}, [initialQuestionType]); // }, [initialQuestionType]);
const handleTypeChange = (type: 'SingleAnswerQuestion' | 'MultipleAnswerQuestion') => { const handleTypeChange = (type: 'SingleAnswerQuestion' | 'MultipleAnswerQuestion') => {
setQuestionType(type); setQuestionType(type);
@ -167,18 +167,6 @@ 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)
// : [...prev, index]
// );
// }
// };
const toggleSelect = (index: number) => { const toggleSelect = (index: number) => {
const answerText = initialAnswerVariants[index].text; const answerText = initialAnswerVariants[index].text;
@ -234,7 +222,13 @@ const QuestionItem: React.FC<QuestionItemProps> = ({
<h2 className={styles.textQuestion}>{textQuestion || initialTextQuestion}</h2> <h2 className={styles.textQuestion}>{textQuestion || initialTextQuestion}</h2>
</button> </button>
)} )}
<TypeDropdown selectedType={questionType} onTypeChange={handleTypeChange}/>
<TypeDropdown
selectedType={questionType}
onTypeChange={(type) => {
handleTypeChange(type);
}}
/>
</div> </div>
{initialAnswerVariants.map((answer, index) => ( {initialAnswerVariants.map((answer, index) => (

View file

@ -147,7 +147,6 @@
.exportButtonContainer { .exportButtonContainer {
padding: 10px 15px; padding: 10px 15px;
/*background-color: #4CAF50;*/
background-color: #3788D6; background-color: #3788D6;
color: white; color: white;
border: none; border: none;
@ -159,5 +158,52 @@
} }
.exportButtonContainer:hover { .exportButtonContainer:hover {
background-color: #45a049; background-color: rgb(14, 122, 221);
}
.info{
margin-top: 60px;
text-align: center;
}
.exportButtonWrapper {
position: relative;
display: inline-block;
width: 100%;
text-align: center;
}
.exportPopup {
position: absolute;
top: -20px;
left: 50%;
transform: translateX(-50%);
background-color: rgba(255, 255, 255, 0.9);
color: #333;
padding: 4px 8px;
border-radius: 3px;
font-size: 12px;
box-shadow: 0 1px 3px rgba(0, 0, 0, 0.1);
white-space: nowrap;
animation: fadeInOut 2s ease-in-out;
z-index: 10;
}
@keyframes fadeInOut {
0% {
opacity: 0;
transform: translateX(-50%) translateY(5px);
}
20% {
opacity: 1;
transform: translateX(-50%) translateY(0);
}
80% {
opacity: 1;
transform: translateX(-50%) translateY(0);
}
100% {
opacity: 0;
transform: translateX(-50%) translateY(5px);
}
} }

View file

@ -10,7 +10,7 @@ import Group from '../../assets/gmail_groups.svg?react';
import Send from '../../assets/send.svg?react'; import Send from '../../assets/send.svg?react';
import {getAllCompletions} from "../../api/CompletionApi.ts"; import {getAllCompletions} from "../../api/CompletionApi.ts";
import {getAnswer} from "../../api/AnswerApi.ts"; import {getAnswer} from "../../api/AnswerApi.ts";
import {useEffect, useState} from "react"; import {useEffect, useRef, useState} from "react";
import {getListQuestions} from "../../api/QuestionApi.ts"; import {getListQuestions} from "../../api/QuestionApi.ts";
import {getAnswerVariants, IAnswerVariant} from "../../api/AnswerVariantsApi.ts"; import {getAnswerVariants, IAnswerVariant} from "../../api/AnswerVariantsApi.ts";
import {getResultsFile} from "../../api/ExportResultApi.ts"; import {getResultsFile} from "../../api/ExportResultApi.ts";
@ -52,10 +52,18 @@ export const Results = () => {
}); });
const [survey, setLocalSurvey] = useState<ISurvey>(initialSurvey); const [survey, setLocalSurvey] = useState<ISurvey>(initialSurvey);
// const [questions, setQuestions] = useState<IQuestion[]>([]);
const [showExportPopup, setShowExportPopup] = useState(false);
const exportButtonRef = useRef<HTMLButtonElement>(null);
const handleExportToExcel = async (id: number) => { const handleExportToExcel = async (id: number) => {
await getResultsFile(id) try {
await getResultsFile(id);
setShowExportPopup(true);
setTimeout(() => setShowExportPopup(false), 2000);
} catch (error) {
console.error('Ошибка при экспорте:', error);
}
}; };
useEffect(() => { useEffect(() => {
@ -65,7 +73,6 @@ export const Results = () => {
setLocalSurvey(surveyData); setLocalSurvey(surveyData);
const questionsList = await getListQuestions(survey.id); const questionsList = await getListQuestions(survey.id);
// setQuestions(questionsList);
const questionsWithVariants = await Promise.all( const questionsWithVariants = await Promise.all(
questionsList.map(async (question) => { questionsList.map(async (question) => {
@ -205,7 +212,10 @@ export const Results = () => {
</div> </div>
</div> </div>
{surveyStats.questions.map((question, index) => ( {surveyStats.totalParticipants === 0 ? (
<p className={styles.info}>Нет данных о прохождении опроса</p>
) : (
surveyStats.questions.map((question, index) => (
<div key={index} className={styles.questionContainer}> <div key={index} className={styles.questionContainer}>
<div className={styles.questionContent}> <div className={styles.questionContent}>
<div className={styles.textContainer}> <div className={styles.textContainer}>
@ -312,9 +322,24 @@ export const Results = () => {
</div> </div>
</div> </div>
</div> </div>
))} ))
)}
<button className={styles.exportButtonContainer} onClick={() => handleExportToExcel(survey.id)}>Экспорт в excel</button> {surveyStats.totalParticipants === 0 ? <></> : (
<div className={styles.exportButtonWrapper}>
<button
ref={exportButtonRef}
className={styles.exportButtonContainer}
onClick={() => handleExportToExcel(survey.id)}
>
Экспорт в excel
</button>
{showExportPopup && (
<div className={styles.exportPopup}>
Файл скачивается
</div>
)}
</div>
)}
</div> </div>
); );
}; };

View file

@ -26,9 +26,50 @@
border-radius: 4px; border-radius: 4px;
} }
.buttonContainer {
position: relative;
display: inline-block;
width: 100%;
text-align: center;
}
.popup {
position: absolute;
top: -20px;
left: 50%;
transform: translateX(-50%);
background-color: rgba(255, 255, 255, 0.9);
color: #333;
padding: 4px 8px;
border-radius: 3px;
font-size: 12px;
box-shadow: 0 1px 3px rgba(0, 0, 0, 0.1);
white-space: nowrap;
animation: fadeInOut 2s ease-in-out;
z-index: 10;
}
@keyframes fadeInOut {
0% {
opacity: 0;
transform: translateX(-50%) translateY(5px);
}
20% {
opacity: 1;
transform: translateX(-50%) translateY(0);
}
80% {
opacity: 1;
transform: translateX(-50%) translateY(0);
}
100% {
opacity: 0;
transform: translateX(-50%) translateY(5px);
}
}
.copyButton { .copyButton {
padding: 10px 15px; padding: 10px 15px;
/*background-color: #4CAF50;*/
background-color: #3788D6; background-color: #3788D6;
color: white; color: white;
border: none; border: none;
@ -40,5 +81,5 @@
} }
.copyButton:hover { .copyButton:hover {
background-color: #45a049; background-color: rgb(14, 122, 221);
} }

View file

@ -1,8 +1,7 @@
import React from 'react'; import React, {useRef, useState} from 'react';
import SurveyInfo from "../SurveyInfo/SurveyInfo.tsx"; import SurveyInfo from "../SurveyInfo/SurveyInfo.tsx";
import styles from "./SettingSurvey.module.css"; import styles from "./SettingSurvey.module.css";
import TimeEvent from "../TimeEvent/TimeEvent.tsx"; import TimeEvent from "../TimeEvent/TimeEvent.tsx";
import SaveButton from "../SaveButton/SaveButton.tsx";
import {ISurvey} from "../../api/SurveyApi.ts"; import {ISurvey} from "../../api/SurveyApi.ts";
import {useLocation, useOutletContext} from "react-router-dom"; import {useLocation, useOutletContext} from "react-router-dom";
import {useSurveyContext} from "../../context/SurveyContext.tsx"; import {useSurveyContext} from "../../context/SurveyContext.tsx";
@ -16,13 +15,17 @@ const SettingSurvey: React.FC = () => {
setSurvey: (survey: ISurvey) => void; setSurvey: (survey: ISurvey) => void;
}>(); }>();
const { tempSurvey, setTempSurvey } = useSurveyContext(); const { tempSurvey, setTempSurvey } = useSurveyContext();
const [showPopup, setShowPopup] = useState(false);
const buttonRef = useRef<HTMLButtonElement>(null);
const handleCopyLink = () => { const handleCopyLink = () => {
if (!survey?.id) if (!survey?.id) return;
return;
const link = `${window.location.origin}/complete-survey/${survey.id}`; const link = `${window.location.origin}/complete-survey/${survey.id}`;
navigator.clipboard.writeText(link) navigator.clipboard.writeText(link)
.then(() => console.log('Copied!')) .then(() => {
setShowPopup(true);
setTimeout(() => setShowPopup(false), 2000);
})
.catch(error => console.error(`Не удалось скопировать ссылку: ${error}`)); .catch(error => console.error(`Не удалось скопировать ссылку: ${error}`));
} }
@ -50,8 +53,22 @@ const SettingSurvey: React.FC = () => {
<div className={styles.param}> <div className={styles.param}>
<h2>Параметры видимости</h2> <h2>Параметры видимости</h2>
</div> </div>
<SaveButton onClick={() => {}}/> {!isSettingCreatePage && (
{!isSettingCreatePage ? <button onClick={handleCopyLink} className={styles.copyButton}>Копировать ссылку</button> : ''} <div className={styles.buttonContainer}>
<button
ref={buttonRef}
onClick={handleCopyLink}
className={styles.copyButton}
>
Копировать ссылку
</button>
{showPopup && (
<div className={styles.popup}>
Ссылка скопирована
</div>
)}
</div>
)}
</div> </div>
) )
} }

View file

@ -235,12 +235,13 @@ export const SurveyPage: React.FC = () => {
surveyId: id, surveyId: id,
id: question.id, id: question.id,
title: question.text, title: question.text,
questionType: question.questionType // Убедитесь, что передается новый тип questionType: question.questionType
} as QuestionActionData & { id: number } } as QuestionActionData & { id: number }
}); });
} }
}); });
questions.forEach(question => { questions.forEach(question => {
question.answerVariants.forEach(answer => { question.answerVariants.forEach(answer => {
if (!answer.id) { if (!answer.id) {