Merge branch 'unstable' into 'main'
Million fixes See merge request internship-2025/survey-webapp/survey-webapp!31
This commit is contained in:
commit
70f287a0b6
12 changed files with 289 additions and 138 deletions
|
|
@ -71,7 +71,8 @@ public class QuestionController : ControllerBase
|
||||||
var question = QuestionMapper.QuestionUpdateToModel(dto, id);
|
var question = QuestionMapper.QuestionUpdateToModel(dto, id);
|
||||||
await _questionService.UpdateQuestionAsync(question);
|
await _questionService.UpdateQuestionAsync(question);
|
||||||
var updatedQuestion = await _questionService.GetQuestionByIdAsync(id);
|
var updatedQuestion = await _questionService.GetQuestionByIdAsync(id);
|
||||||
return Ok(updatedQuestion);
|
var result = QuestionMapper.ModelToQuestionDto(updatedQuestion);
|
||||||
|
return Ok(result);
|
||||||
}
|
}
|
||||||
|
|
||||||
/// <summary>
|
/// <summary>
|
||||||
|
|
|
||||||
|
|
@ -69,16 +69,22 @@ public static class QuestionMapper
|
||||||
{
|
{
|
||||||
Id = questionId,
|
Id = questionId,
|
||||||
Title = dto.Title,
|
Title = dto.Title,
|
||||||
|
Discriminator = "TextQuestion",
|
||||||
|
AnswerVariants = []
|
||||||
},
|
},
|
||||||
"singleanswerquestion" => new SingleAnswerQuestion
|
"singleanswerquestion" => new SingleAnswerQuestion
|
||||||
{
|
{
|
||||||
Id = questionId,
|
Id = questionId,
|
||||||
Title = dto.Title,
|
Title = dto.Title,
|
||||||
|
Discriminator = "SingleAnswerQuestion",
|
||||||
|
AnswerVariants = []
|
||||||
},
|
},
|
||||||
"multipleanswerquestion" => new MultipleAnswerQuestion
|
"multipleanswerquestion" => new MultipleAnswerQuestion
|
||||||
{
|
{
|
||||||
Id = questionId,
|
Id = questionId,
|
||||||
Title = dto.Title,
|
Title = dto.Title,
|
||||||
|
Discriminator = "MultipleAnswerQuestion",
|
||||||
|
AnswerVariants = []
|
||||||
},
|
},
|
||||||
_ => throw new BadRequestException("Unknown question type")
|
_ => throw new BadRequestException("Unknown question type")
|
||||||
};
|
};
|
||||||
|
|
|
||||||
|
|
@ -41,8 +41,21 @@ public class QuestionService : IQuestionService
|
||||||
|
|
||||||
question.SurveyId = questionBase.SurveyId;
|
question.SurveyId = questionBase.SurveyId;
|
||||||
|
|
||||||
|
// Если изменился тип вопроса (Discriminator), используем новый тип
|
||||||
|
if (questionBase.Discriminator != question.Discriminator)
|
||||||
|
{
|
||||||
|
// Удаляем старый вопрос
|
||||||
|
await _questionRepository.DeleteAsync(questionBase.Id);
|
||||||
|
|
||||||
|
// Добавляем новый с тем же ID
|
||||||
|
await _questionRepository.AddAsync(question);
|
||||||
|
}
|
||||||
|
else
|
||||||
|
{
|
||||||
|
// Если тип не меняется, просто обновляем
|
||||||
await _questionRepository.UpdateAsync(question);
|
await _questionRepository.UpdateAsync(question);
|
||||||
}
|
}
|
||||||
|
}
|
||||||
|
|
||||||
public async Task DeleteQuestionAsync(int id)
|
public async Task DeleteQuestionAsync(int id)
|
||||||
{
|
{
|
||||||
|
|
|
||||||
|
|
@ -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('Сессия истекла. Перенаправление на страницу входа.');
|
||||||
|
}
|
||||||
};
|
};
|
||||||
|
|
||||||
/**
|
/**
|
||||||
|
|
|
||||||
|
|
@ -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);
|
||||||
|
|
|
||||||
|
|
@ -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 {
|
||||||
|
|
|
||||||
|
|
@ -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) => (
|
||||||
|
|
|
||||||
|
|
@ -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);
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
@ -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>
|
||||||
);
|
);
|
||||||
};
|
};
|
||||||
|
|
@ -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);
|
||||||
}
|
}
|
||||||
|
|
@ -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>
|
||||||
)
|
)
|
||||||
}
|
}
|
||||||
|
|
|
||||||
|
|
@ -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) {
|
||||||
|
|
|
||||||
Loading…
Add table
Add a link
Reference in a new issue