diff --git a/SurveyFrontend/src/api/BaseApi.ts b/SurveyFrontend/src/api/BaseApi.ts index f1a537a..4cf0b20 100644 --- a/SurveyFrontend/src/api/BaseApi.ts +++ b/SurveyFrontend/src/api/BaseApi.ts @@ -11,6 +11,10 @@ export const handleUnauthorizedError = (error: unknown) => { window.location.href = '/login'; console.log('Сессия истекла. Перенаправление на страницу входа.'); } + if (error instanceof Error && error.message.includes('Токен отсутствует')) { + window.location.href = '/login'; + console.log('Сессия истекла. Перенаправление на страницу входа.'); + } }; /** diff --git a/SurveyFrontend/src/api/QuestionApi.ts b/SurveyFrontend/src/api/QuestionApi.ts index d16668c..a634d1a 100644 --- a/SurveyFrontend/src/api/QuestionApi.ts +++ b/SurveyFrontend/src/api/QuestionApi.ts @@ -58,7 +58,8 @@ export const updateQuestion = async (id: number, question: Partial questionType: question.questionType, }), }) - return await handleResponse(response) + const result = await handleResponse(response); + return result; } catch(error){ handleUnauthorizedError(error); diff --git a/SurveyFrontend/src/components/Account/Account.tsx b/SurveyFrontend/src/components/Account/Account.tsx index 4728669..a8bfae9 100644 --- a/SurveyFrontend/src/components/Account/Account.tsx +++ b/SurveyFrontend/src/components/Account/Account.tsx @@ -2,6 +2,7 @@ import React, { useEffect, useState } from 'react'; import styles from './Account.module.css'; import AccountImg from '../../assets/account.svg?react'; import { getCurrentUser } from '../../api/AuthApi'; +import {handleUnauthorizedError} from "../../api/BaseApi.ts"; interface AccountProps { href: string; @@ -17,6 +18,7 @@ const Account: React.FC = ({ href }) => { const userData = await getCurrentUser(); setUserName(`${userData.firstName} ${userData.lastName}`); } catch (error) { + handleUnauthorizedError(error) console.error("Ошибка загрузки данных пользователя:", error); localStorage.removeItem("user"); } finally { diff --git a/SurveyFrontend/src/components/QuestionItem/QuestionItem.tsx b/SurveyFrontend/src/components/QuestionItem/QuestionItem.tsx index f07ee42..499c4b6 100644 --- a/SurveyFrontend/src/components/QuestionItem/QuestionItem.tsx +++ b/SurveyFrontend/src/components/QuestionItem/QuestionItem.tsx @@ -53,9 +53,9 @@ const QuestionItem: React.FC = ({ setTextQuestion(valueQuestion); }, [valueQuestion]); - useEffect(() => { - setQuestionType(initialQuestionType); - }, [initialQuestionType]); + // useEffect(() => { + // setQuestionType(initialQuestionType); + // }, [initialQuestionType]); const handleTypeChange = (type: 'SingleAnswerQuestion' | 'MultipleAnswerQuestion') => { setQuestionType(type); @@ -167,18 +167,6 @@ const QuestionItem: React.FC = ({ } }; - // 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 answerText = initialAnswerVariants[index].text; @@ -234,7 +222,13 @@ const QuestionItem: React.FC = ({

{textQuestion || initialTextQuestion}

)} - + + { + handleTypeChange(type); + }} + /> {initialAnswerVariants.map((answer, index) => ( diff --git a/SurveyFrontend/src/components/Results/Results.module.css b/SurveyFrontend/src/components/Results/Results.module.css index 6de347c..19604e9 100644 --- a/SurveyFrontend/src/components/Results/Results.module.css +++ b/SurveyFrontend/src/components/Results/Results.module.css @@ -147,7 +147,6 @@ .exportButtonContainer { padding: 10px 15px; - /*background-color: #4CAF50;*/ background-color: #3788D6; color: white; border: none; @@ -159,5 +158,52 @@ } .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); + } } \ No newline at end of file diff --git a/SurveyFrontend/src/components/Results/Results.tsx b/SurveyFrontend/src/components/Results/Results.tsx index 4a02574..32a3323 100644 --- a/SurveyFrontend/src/components/Results/Results.tsx +++ b/SurveyFrontend/src/components/Results/Results.tsx @@ -10,7 +10,7 @@ import Group from '../../assets/gmail_groups.svg?react'; import Send from '../../assets/send.svg?react'; import {getAllCompletions} from "../../api/CompletionApi.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 {getAnswerVariants, IAnswerVariant} from "../../api/AnswerVariantsApi.ts"; import {getResultsFile} from "../../api/ExportResultApi.ts"; @@ -52,10 +52,18 @@ export const Results = () => { }); const [survey, setLocalSurvey] = useState(initialSurvey); - // const [questions, setQuestions] = useState([]); + + const [showExportPopup, setShowExportPopup] = useState(false); + const exportButtonRef = useRef(null); const handleExportToExcel = async (id: number) => { - await getResultsFile(id) + try { + await getResultsFile(id); + setShowExportPopup(true); + setTimeout(() => setShowExportPopup(false), 2000); + } catch (error) { + console.error('Ошибка при экспорте:', error); + } }; useEffect(() => { @@ -65,7 +73,6 @@ export const Results = () => { setLocalSurvey(surveyData); const questionsList = await getListQuestions(survey.id); - // setQuestions(questionsList); const questionsWithVariants = await Promise.all( questionsList.map(async (question) => { @@ -205,116 +212,134 @@ export const Results = () => { - {surveyStats.questions.map((question, index) => ( -
-
-
-

{question.questionText}

-

Ответов: {question.totalAnswers}

-
+ {surveyStats.totalParticipants === 0 ? ( +

Нет данных о прохождении опроса

+ ) : ( + surveyStats.questions.map((question, index) => ( +
+
+
+

{question.questionText}

+

Ответов: {question.totalAnswers}

+
-
- {question.isMultipleChoice ? ( -
- opt.text), - datasets: [{ - label: '% выбравших', - data: question.options.map(opt => opt.percentage), - backgroundColor: colorsForBar, - borderColor: colorsForBar, - borderWidth: 2, - borderRadius: 8, - borderSkipped: false, - }] - }} - options={{ - responsive: true, - plugins: { - legend: { - display: false - }, - tooltip: { enabled: true }, - datalabels: { display: false }, - annotation: { - annotations: question.options.map((opt, i) => ({ - type: 'label', - xValue: i, - yValue: opt.percentage + 5, - content: `${opt.percentage}%`, - font: { size: 1, weight: 400 }, - color: '#000' - })) - } - }, - scales: { - y: { - beginAtZero: true, - max: 100, - ticks: { callback: (val) => `${val}%` } - }, - x: { - ticks: { - color: '#000000', - font: { - size: 12, - weight: 400 +
+ {question.isMultipleChoice ? ( +
+ opt.text), + datasets: [{ + label: '% выбравших', + data: question.options.map(opt => opt.percentage), + backgroundColor: colorsForBar, + borderColor: colorsForBar, + borderWidth: 2, + borderRadius: 8, + borderSkipped: false, + }] + }} + options={{ + responsive: true, + plugins: { + legend: { + display: false + }, + tooltip: { enabled: true }, + datalabels: { display: false }, + annotation: { + annotations: question.options.map((opt, i) => ({ + type: 'label', + xValue: i, + yValue: opt.percentage + 5, + content: `${opt.percentage}%`, + font: { size: 1, weight: 400 }, + color: '#000' + })) } }, - grid: { display: false } - } - } - }} - /> -
- ) : ( -
- opt.text), - datasets: [{ - data: question.options.map(opt => opt.percentage), - backgroundColor: colorsForPie, - borderColor: '#fff', - borderWidth: 2 - }] - }} - options={{ - responsive: true, - plugins: { - legend: { - position: 'right', - labels: { - color: '#000000', - font: { - size: 12, - weight: 500 + scales: { + y: { + beginAtZero: true, + max: 100, + ticks: { callback: (val) => `${val}%` } + }, + x: { + ticks: { + color: '#000000', + font: { + size: 12, + weight: 400 + } + }, + grid: { display: false } } } - }, - tooltip: { - callbacks: { - label: (ctx) => `${ctx.label}: ${ctx.raw}%` - } - }, - datalabels: { - formatter: (value) => `${value}%`, - color: '#000', - font: { weight: 400, size: 12 } - } - }, - animation: { animateRotate: true } - }} - /> + }} + /> +
+ ) : ( +
+ opt.text), + datasets: [{ + data: question.options.map(opt => opt.percentage), + backgroundColor: colorsForPie, + borderColor: '#fff', + borderWidth: 2 + }] + }} + options={{ + responsive: true, + plugins: { + legend: { + position: 'right', + labels: { + color: '#000000', + font: { + size: 12, + weight: 500 + } + } + }, + tooltip: { + callbacks: { + label: (ctx) => `${ctx.label}: ${ctx.raw}%` + } + }, + datalabels: { + formatter: (value) => `${value}%`, + color: '#000', + font: { weight: 400, size: 12 } + } + }, + animation: { animateRotate: true } + }} + /> +
+ )}
- )} +
-
+ )) + )} + {surveyStats.totalParticipants === 0 ? <> : ( +
+ + {showExportPopup && ( +
+ Файл скачивается +
+ )}
- ))} - - -
+ )} +
); }; \ No newline at end of file diff --git a/SurveyFrontend/src/components/SettingSurvey/SettingSurvey.module.css b/SurveyFrontend/src/components/SettingSurvey/SettingSurvey.module.css index 8fd3d2d..f98fa43 100644 --- a/SurveyFrontend/src/components/SettingSurvey/SettingSurvey.module.css +++ b/SurveyFrontend/src/components/SettingSurvey/SettingSurvey.module.css @@ -26,9 +26,50 @@ 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 { padding: 10px 15px; - /*background-color: #4CAF50;*/ background-color: #3788D6; color: white; border: none; @@ -40,5 +81,5 @@ } .copyButton:hover { - background-color: #45a049; + background-color: rgb(14, 122, 221); } \ No newline at end of file diff --git a/SurveyFrontend/src/components/SettingSurvey/SettingSurvey.tsx b/SurveyFrontend/src/components/SettingSurvey/SettingSurvey.tsx index ad2d5f7..5ab6012 100644 --- a/SurveyFrontend/src/components/SettingSurvey/SettingSurvey.tsx +++ b/SurveyFrontend/src/components/SettingSurvey/SettingSurvey.tsx @@ -1,8 +1,7 @@ -import React from 'react'; +import React, {useRef, useState} from 'react'; import SurveyInfo from "../SurveyInfo/SurveyInfo.tsx"; import styles from "./SettingSurvey.module.css"; import TimeEvent from "../TimeEvent/TimeEvent.tsx"; -import SaveButton from "../SaveButton/SaveButton.tsx"; import {ISurvey} from "../../api/SurveyApi.ts"; import {useLocation, useOutletContext} from "react-router-dom"; import {useSurveyContext} from "../../context/SurveyContext.tsx"; @@ -16,13 +15,17 @@ const SettingSurvey: React.FC = () => { setSurvey: (survey: ISurvey) => void; }>(); const { tempSurvey, setTempSurvey } = useSurveyContext(); + const [showPopup, setShowPopup] = useState(false); + const buttonRef = useRef(null); const handleCopyLink = () => { - if (!survey?.id) - return; + if (!survey?.id) return; const link = `${window.location.origin}/complete-survey/${survey.id}`; navigator.clipboard.writeText(link) - .then(() => console.log('Copied!')) + .then(() => { + setShowPopup(true); + setTimeout(() => setShowPopup(false), 2000); + }) .catch(error => console.error(`Не удалось скопировать ссылку: ${error}`)); } @@ -50,8 +53,22 @@ const SettingSurvey: React.FC = () => {

Параметры видимости

- {}}/> - {!isSettingCreatePage ? : ''} + {!isSettingCreatePage && ( +
+ + {showPopup && ( +
+ Ссылка скопирована +
+ )} +
+ )}
) } diff --git a/SurveyFrontend/src/components/SurveyPage/SurveyPage.tsx b/SurveyFrontend/src/components/SurveyPage/SurveyPage.tsx index 6b5dfbc..02ad868 100644 --- a/SurveyFrontend/src/components/SurveyPage/SurveyPage.tsx +++ b/SurveyFrontend/src/components/SurveyPage/SurveyPage.tsx @@ -235,12 +235,13 @@ export const SurveyPage: React.FC = () => { surveyId: id, id: question.id, title: question.text, - questionType: question.questionType // Убедитесь, что передается новый тип + questionType: question.questionType } as QuestionActionData & { id: number } }); } }); + questions.forEach(question => { question.answerVariants.forEach(answer => { if (!answer.id) {