Merge remote-tracking branch 'origin/unstable' into unstable
This commit is contained in:
commit
47e7173422
50 changed files with 940 additions and 472 deletions
|
|
@ -9,7 +9,7 @@ import {MySurveyList} from "./components/MySurveyList/MySurveyList.tsx";
|
||||||
import AuthForm from "./pages/AuthForm/AuthForm.tsx";
|
import AuthForm from "./pages/AuthForm/AuthForm.tsx";
|
||||||
import {SurveyPage} from "./components/SurveyPage/SurveyPage.tsx";
|
import {SurveyPage} from "./components/SurveyPage/SurveyPage.tsx";
|
||||||
import CompleteSurvey from "./pages/CompleteSurvey/CompleteSurvey.tsx";
|
import CompleteSurvey from "./pages/CompleteSurvey/CompleteSurvey.tsx";
|
||||||
import CompletingSurvey from "./components/CompletingSurvey/CompletingSurvey.tsx";
|
import {SurveyProvider} from './context/SurveyContext.tsx';
|
||||||
|
|
||||||
const App = () => {
|
const App = () => {
|
||||||
return(
|
return(
|
||||||
|
|
@ -19,8 +19,8 @@ const App = () => {
|
||||||
<Route path="/register" element={<AuthForm />} />
|
<Route path="/register" element={<AuthForm />} />
|
||||||
|
|
||||||
<Route path="survey/create" element={<SurveyCreateAndEditingPage />}>
|
<Route path="survey/create" element={<SurveyCreateAndEditingPage />}>
|
||||||
<Route path="questions" element={<Survey />} />
|
<Route path="questions" element={<SurveyProvider><Survey /></SurveyProvider>} />
|
||||||
<Route path="settings" element={<SettingSurvey />} />
|
<Route path="settings" element={<SurveyProvider><SettingSurvey /></SurveyProvider>} />
|
||||||
</Route>
|
</Route>
|
||||||
|
|
||||||
<Route path="my-surveys" element={<MySurveysPage />}>
|
<Route path="my-surveys" element={<MySurveysPage />}>
|
||||||
|
|
@ -29,13 +29,11 @@ const App = () => {
|
||||||
|
|
||||||
<Route path='survey/:surveyId' element={<SurveyCreateAndEditingPage />}>
|
<Route path='survey/:surveyId' element={<SurveyCreateAndEditingPage />}>
|
||||||
<Route path="questions" element={<SurveyPage />} />
|
<Route path="questions" element={<SurveyPage />} />
|
||||||
<Route path="settings" element={<SettingSurvey />} />
|
<Route path="settings" element={<SurveyProvider><SettingSurvey /></SurveyProvider>} />
|
||||||
<Route path="results" element={<Results />} />
|
<Route path="results" element={<Results />} />
|
||||||
</Route>
|
</Route>
|
||||||
|
|
||||||
<Route path='/complete-survey' element={<CompleteSurvey/>}>
|
<Route path='/complete-survey/:surveyId' element={<CompleteSurvey/>}/>
|
||||||
<Route index element={<CompletingSurvey/>}/>
|
|
||||||
</Route>
|
|
||||||
|
|
||||||
<Route path="*" element={<AuthForm />} />
|
<Route path="*" element={<AuthForm />} />
|
||||||
</Routes>
|
</Routes>
|
||||||
|
|
|
||||||
|
|
@ -1,94 +1,40 @@
|
||||||
import {BASE_URL, createRequestConfig, handleResponse} from "./BaseApi.ts";
|
import {BASE_URL, createRequestConfig, handleResponse, handleUnauthorizedError} from "./BaseApi.ts";
|
||||||
|
|
||||||
export interface INewAnswer{
|
export const getAnswer = async (id: number) => {
|
||||||
text: string;
|
|
||||||
}
|
|
||||||
|
|
||||||
export interface IAnswerVariant extends INewAnswer{
|
|
||||||
surveyId: number;
|
|
||||||
id: number;
|
|
||||||
questionId: number;
|
|
||||||
}
|
|
||||||
|
|
||||||
export const getAnswerVariants = async (surveyId: number, questionId: number) => {
|
|
||||||
try{
|
|
||||||
const response = await fetch(`${BASE_URL}/surveys/${surveyId}/questions/${questionId}/answerVariants`, {
|
|
||||||
...createRequestConfig('GET')
|
|
||||||
})
|
|
||||||
return await handleResponse(response)
|
|
||||||
}catch(err){
|
|
||||||
console.error(`Error receiving response options: ${err}`);
|
|
||||||
throw err;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
export const addNewAnswerVariant = async (surveyId: number, questionId: number, answer: INewAnswer) => {
|
|
||||||
const token = localStorage.getItem("token");
|
|
||||||
if (!token) {
|
|
||||||
throw new Error("Токен отсутствует");
|
|
||||||
}
|
|
||||||
|
|
||||||
try{
|
|
||||||
const response = await fetch(`${BASE_URL}/surveys/${surveyId}/questions/${questionId}/answerVariants`, {
|
|
||||||
...createRequestConfig('POST'),
|
|
||||||
body: JSON.stringify({
|
|
||||||
text: answer.text,
|
|
||||||
}),
|
|
||||||
})
|
|
||||||
|
|
||||||
if (!response.ok) {
|
|
||||||
throw new Error(`Ошибка: ${response.status}`);
|
|
||||||
}
|
|
||||||
return await handleResponse(response)
|
|
||||||
}
|
|
||||||
catch(err){
|
|
||||||
console.error(`Error adding a new response option: ${err}`);
|
|
||||||
throw err;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
export const updateAnswerVariant = async (surveyId: number, questionId: number, id: number, answer: INewAnswer): Promise<INewAnswer> => {
|
|
||||||
const token = localStorage.getItem("token");
|
|
||||||
if (!token) {
|
|
||||||
throw new Error("Токен отсутствует");
|
|
||||||
}
|
|
||||||
|
|
||||||
try{
|
|
||||||
const response = await fetch(`${BASE_URL}/surveys/${surveyId}/questions/${questionId}/answerVariants/${id}`, {
|
|
||||||
...createRequestConfig('PUT'),
|
|
||||||
body: JSON.stringify({
|
|
||||||
text: answer.text,
|
|
||||||
})
|
|
||||||
})
|
|
||||||
if (!response.ok) {
|
|
||||||
const errorData = await response.json().catch(() => null);
|
|
||||||
throw new Error(`Ошибка ${response.status}: ${errorData?.message || 'Неизвестная ошибка'}`);
|
|
||||||
}
|
|
||||||
return await handleResponse(response)
|
|
||||||
}
|
|
||||||
catch(err){
|
|
||||||
console.error(`Error updating the response option: ${err}`);
|
|
||||||
throw err;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
export const deleteAnswerVariant = async (surveyId: number, questionId: number, id: number) => {
|
|
||||||
const token = localStorage.getItem("token");
|
const token = localStorage.getItem("token");
|
||||||
if (!token) {
|
if (!token) {
|
||||||
throw new Error('Токен отсутствует');
|
throw new Error('Токен отсутствует');
|
||||||
}
|
}
|
||||||
|
|
||||||
try{
|
try{
|
||||||
const response = await fetch(`${BASE_URL}/surveys/${surveyId}/questions/${questionId}/answerVariants/${id}`, {
|
const response = await fetch(`${BASE_URL}/questions/${id}/answers`, {
|
||||||
...createRequestConfig('DELETE'),
|
...createRequestConfig('GET'),
|
||||||
})
|
})
|
||||||
const responseData = await handleResponse(response);
|
return await handleResponse(response)
|
||||||
if (response.ok && !responseData){
|
|
||||||
return {success: true};
|
|
||||||
}
|
|
||||||
return responseData;
|
|
||||||
}
|
}
|
||||||
catch(err){
|
catch (error) {
|
||||||
console.error(`Error deleting a answer: ${err}`);
|
handleUnauthorizedError(error);
|
||||||
throw err;
|
console.error(`error when receiving the response: ${error}`);
|
||||||
|
throw error;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
export const getCompletionsAnswer = async (id: number) => {
|
||||||
|
const token = localStorage.getItem("token");
|
||||||
|
if (!token) {
|
||||||
|
throw new Error('Токен отсутствует');
|
||||||
|
}
|
||||||
|
|
||||||
|
try{
|
||||||
|
const response = await fetch(`${BASE_URL}/completions/${id}/answers`, {
|
||||||
|
...createRequestConfig('GET'),
|
||||||
|
})
|
||||||
|
|
||||||
|
return await handleResponse(response)
|
||||||
|
}
|
||||||
|
catch (error) {
|
||||||
|
handleUnauthorizedError(error);
|
||||||
|
console.error(`error when receiving the selected response: ${error}`);
|
||||||
|
throw error;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
97
SurveyFrontend/src/api/AnswerVariantsApi.ts
Normal file
97
SurveyFrontend/src/api/AnswerVariantsApi.ts
Normal file
|
|
@ -0,0 +1,97 @@
|
||||||
|
import {BASE_URL, createRequestConfig, handleResponse, handleUnauthorizedError} from "./BaseApi.ts";
|
||||||
|
|
||||||
|
export interface INewAnswer{
|
||||||
|
text: string;
|
||||||
|
}
|
||||||
|
|
||||||
|
export interface IAnswerVariant extends INewAnswer{
|
||||||
|
surveyId: number;
|
||||||
|
id: number;
|
||||||
|
questionId: number;
|
||||||
|
}
|
||||||
|
|
||||||
|
export const getAnswerVariants = async (surveyId: number, questionId: number) => {
|
||||||
|
try{
|
||||||
|
const response = await fetch(`${BASE_URL}/surveys/${surveyId}/questions/${questionId}/answerVariants`, {
|
||||||
|
...createRequestConfig('GET')
|
||||||
|
})
|
||||||
|
return await handleResponse(response)
|
||||||
|
}catch(err){
|
||||||
|
console.error(`Error receiving response options: ${err}`);
|
||||||
|
throw err;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
export const addNewAnswerVariant = async (surveyId: number, questionId: number, answer: INewAnswer) => {
|
||||||
|
const token = localStorage.getItem("token");
|
||||||
|
if (!token) {
|
||||||
|
throw new Error("Токен отсутствует");
|
||||||
|
}
|
||||||
|
|
||||||
|
try{
|
||||||
|
const response = await fetch(`${BASE_URL}/surveys/${surveyId}/questions/${questionId}/answerVariants`, {
|
||||||
|
...createRequestConfig('POST'),
|
||||||
|
body: JSON.stringify({
|
||||||
|
text: answer.text,
|
||||||
|
}),
|
||||||
|
})
|
||||||
|
|
||||||
|
if (!response.ok) {
|
||||||
|
throw new Error(`Ошибка: ${response.status}`);
|
||||||
|
}
|
||||||
|
return await handleResponse(response)
|
||||||
|
}
|
||||||
|
catch(err){
|
||||||
|
handleUnauthorizedError(err);
|
||||||
|
console.error(`Error adding a new response option: ${err}`);
|
||||||
|
throw err;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
export const updateAnswerVariant = async (id: number, answer: INewAnswer): Promise<INewAnswer> => {
|
||||||
|
const token = localStorage.getItem("token");
|
||||||
|
if (!token) {
|
||||||
|
throw new Error("Токен отсутствует");
|
||||||
|
}
|
||||||
|
|
||||||
|
try{
|
||||||
|
const response = await fetch(`${BASE_URL}/answerVariants/${id}`, {
|
||||||
|
...createRequestConfig('PUT'),
|
||||||
|
body: JSON.stringify({
|
||||||
|
text: answer.text,
|
||||||
|
})
|
||||||
|
})
|
||||||
|
if (!response.ok) {
|
||||||
|
const errorData = await response.json().catch(() => null);
|
||||||
|
throw new Error(`Ошибка ${response.status}: ${errorData?.message || 'Неизвестная ошибка'}`);
|
||||||
|
}
|
||||||
|
return await handleResponse(response)
|
||||||
|
}
|
||||||
|
catch(err){
|
||||||
|
handleUnauthorizedError(err);
|
||||||
|
console.error(`Error updating the response option: ${err}`);
|
||||||
|
throw err;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
export const deleteAnswerVariant = async (id: number) => {
|
||||||
|
const token = localStorage.getItem("token");
|
||||||
|
if (!token) {
|
||||||
|
throw new Error('Токен отсутствует');
|
||||||
|
}
|
||||||
|
try{
|
||||||
|
const response = await fetch(`${BASE_URL}/answerVariants/${id}`, {
|
||||||
|
...createRequestConfig('DELETE'),
|
||||||
|
})
|
||||||
|
const responseData = await handleResponse(response);
|
||||||
|
if (response.ok && !responseData){
|
||||||
|
return {success: true};
|
||||||
|
}
|
||||||
|
return responseData;
|
||||||
|
}
|
||||||
|
catch(err){
|
||||||
|
handleUnauthorizedError(err);
|
||||||
|
console.error(`Error deleting a answer: ${err}`);
|
||||||
|
throw err;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
@ -1,4 +1,4 @@
|
||||||
import {BASE_URL, createRequestConfig, handleResponse} from "./BaseApi.ts";
|
import {BASE_URL, createRequestConfig, handleResponse, handleUnauthorizedError} from "./BaseApi.ts";
|
||||||
|
|
||||||
interface IAuthData{
|
interface IAuthData{
|
||||||
email: string;
|
email: string;
|
||||||
|
|
@ -26,14 +26,6 @@ export const getCurrentUser = async () => {
|
||||||
}
|
}
|
||||||
});
|
});
|
||||||
|
|
||||||
console.log(response);
|
|
||||||
|
|
||||||
if (response.status === 401) {
|
|
||||||
localStorage.removeItem("token");
|
|
||||||
localStorage.removeItem("user");
|
|
||||||
throw new Error("Сессия истекла. Пожалуйста, войдите снова.");
|
|
||||||
}
|
|
||||||
|
|
||||||
if (!response.ok) {
|
if (!response.ok) {
|
||||||
throw new Error(`Ошибка сервера: ${response.status}`);
|
throw new Error(`Ошибка сервера: ${response.status}`);
|
||||||
}
|
}
|
||||||
|
|
@ -42,33 +34,38 @@ export const getCurrentUser = async () => {
|
||||||
localStorage.setItem("user", JSON.stringify(userData));
|
localStorage.setItem("user", JSON.stringify(userData));
|
||||||
return userData;
|
return userData;
|
||||||
} catch (error) {
|
} catch (error) {
|
||||||
|
handleUnauthorizedError(error);
|
||||||
console.error("Ошибка при получении данных пользователя:", error);
|
console.error("Ошибка при получении данных пользователя:", error);
|
||||||
throw error;
|
throw error;
|
||||||
}
|
}
|
||||||
};
|
};
|
||||||
|
|
||||||
export const registerUser = async (data: IRegistrationData) => {
|
export const registerUser = async (data: IRegistrationData) => {
|
||||||
try{
|
try {
|
||||||
const response = await fetch(`${BASE_URL}/auth/register`, {
|
const response = await fetch(`${BASE_URL}/auth/register`, {
|
||||||
...createRequestConfig('POST'), body: JSON.stringify(data),
|
...createRequestConfig('POST'),
|
||||||
})
|
body: JSON.stringify(data),
|
||||||
const responseData = await handleResponse(response);
|
});
|
||||||
|
|
||||||
if (responseData.accessToken) {
|
if (!response.ok) {
|
||||||
localStorage.setItem("token", responseData.accessToken);
|
const errorData = await response.json();
|
||||||
localStorage.setItem("user", JSON.stringify({
|
throw new Error(errorData.message || `Ошибка: ${response.status}`);
|
||||||
firstName: data.firstName,
|
}
|
||||||
lastName: data.lastName,
|
|
||||||
email: data.email
|
const responseData = await handleResponse(response);
|
||||||
}));
|
if (responseData.accessToken || responseData.token) {
|
||||||
|
localStorage.setItem("token", responseData.accessToken || responseData.token);
|
||||||
|
if (responseData.user) {
|
||||||
|
localStorage.setItem("user", JSON.stringify(responseData.user));
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
return responseData;
|
return responseData;
|
||||||
} catch (error){
|
} catch (error) {
|
||||||
console.error("Registration error:", error);
|
console.error("Registration error:", error);
|
||||||
throw error;
|
throw error;
|
||||||
}
|
}
|
||||||
}
|
};
|
||||||
|
|
||||||
export const authUser = async (data: IAuthData) => {
|
export const authUser = async (data: IAuthData) => {
|
||||||
try {
|
try {
|
||||||
|
|
@ -76,6 +73,7 @@ export const authUser = async (data: IAuthData) => {
|
||||||
...createRequestConfig('POST'),
|
...createRequestConfig('POST'),
|
||||||
body: JSON.stringify(data),
|
body: JSON.stringify(data),
|
||||||
});
|
});
|
||||||
|
|
||||||
const responseData = await handleResponse(response);
|
const responseData = await handleResponse(response);
|
||||||
|
|
||||||
const token = responseData.accessToken || responseData.token;
|
const token = responseData.accessToken || responseData.token;
|
||||||
|
|
@ -91,6 +89,7 @@ export const authUser = async (data: IAuthData) => {
|
||||||
|
|
||||||
return responseData;
|
return responseData;
|
||||||
} catch (error) {
|
} catch (error) {
|
||||||
|
handleUnauthorizedError(error);
|
||||||
console.error("Login error:", error);
|
console.error("Login error:", error);
|
||||||
throw error;
|
throw error;
|
||||||
}
|
}
|
||||||
|
|
|
||||||
|
|
@ -6,6 +6,13 @@ interface RequestConfig {
|
||||||
body?: BodyInit | null;
|
body?: BodyInit | null;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
export const handleUnauthorizedError = (error: unknown) => {
|
||||||
|
if (error instanceof Error && error.message.includes('401')) {
|
||||||
|
window.location.href = '/login';
|
||||||
|
console.log('Сессия истекла. Перенаправление на страницу входа.');
|
||||||
|
}
|
||||||
|
};
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Создаёт конфигурацию для fetch-запроса
|
* Создаёт конфигурацию для fetch-запроса
|
||||||
* @param method HTTP-метод (GET, POST, PUT, DELETE)
|
* @param method HTTP-метод (GET, POST, PUT, DELETE)
|
||||||
|
|
@ -37,14 +44,15 @@ const createRequestConfig = (method: string, isFormData: boolean = false): Reque
|
||||||
* @returns Распарсенные данные или ошибку
|
* @returns Распарсенные данные или ошибку
|
||||||
*/
|
*/
|
||||||
const handleResponse = async (response: Response) => {
|
const handleResponse = async (response: Response) => {
|
||||||
|
if (response.status === 401) {
|
||||||
|
localStorage.removeItem("token");
|
||||||
|
localStorage.removeItem("user");
|
||||||
|
throw new Error("401: Unauthorized");
|
||||||
|
}
|
||||||
|
|
||||||
const responseText = await response.text();
|
const responseText = await response.text();
|
||||||
|
|
||||||
if (!responseText) {
|
if (!responseText) {
|
||||||
if (response.status === 401) {
|
|
||||||
window.location.href = '/auth/login';
|
|
||||||
throw new Error('Требуется авторизация');
|
|
||||||
}
|
|
||||||
|
|
||||||
if (response.ok) {
|
if (response.ok) {
|
||||||
return null;
|
return null;
|
||||||
}
|
}
|
||||||
|
|
|
||||||
62
SurveyFrontend/src/api/CompletionApi.ts
Normal file
62
SurveyFrontend/src/api/CompletionApi.ts
Normal file
|
|
@ -0,0 +1,62 @@
|
||||||
|
import {BASE_URL, createRequestConfig, handleResponse, handleUnauthorizedError} from "./BaseApi.ts";
|
||||||
|
export interface ICompletionRequest {
|
||||||
|
answers: Array<{
|
||||||
|
questionId: number;
|
||||||
|
answerText: string;
|
||||||
|
}>;
|
||||||
|
}
|
||||||
|
|
||||||
|
export const getAllCompletions = async (surveyId: number) => {
|
||||||
|
const token = localStorage.getItem("token");
|
||||||
|
if (!token) {
|
||||||
|
throw new Error('Токен отсутствует');
|
||||||
|
}
|
||||||
|
try{
|
||||||
|
const response = await fetch(`${BASE_URL}/surveys/${surveyId}/completions`, {
|
||||||
|
...createRequestConfig('GET'),
|
||||||
|
})
|
||||||
|
return await handleResponse(response);
|
||||||
|
}
|
||||||
|
catch (error) {
|
||||||
|
handleUnauthorizedError(error);
|
||||||
|
console.error(`Error when receiving all selected responses: ${error}`);
|
||||||
|
throw error;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
export const addNewCompletion = async (surveyId: number, data: ICompletionRequest) => {
|
||||||
|
try{
|
||||||
|
const response = await fetch(`${BASE_URL}/surveys/${surveyId}/completions`, {
|
||||||
|
...createRequestConfig('POST'),
|
||||||
|
body: JSON.stringify(data)
|
||||||
|
})
|
||||||
|
if (!response.ok) {
|
||||||
|
throw new Error(`Ошибка: ${response.status}`);
|
||||||
|
}
|
||||||
|
return await handleResponse(response)
|
||||||
|
}
|
||||||
|
catch (error) {
|
||||||
|
handleUnauthorizedError(error);
|
||||||
|
console.error(`Error when adding a new survey passage: ${error}`);
|
||||||
|
throw error;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
export const getCompletionById = async (id: number) => {
|
||||||
|
const token = localStorage.getItem("token");
|
||||||
|
if (!token) {
|
||||||
|
throw new Error('Токен отсутствует');
|
||||||
|
}
|
||||||
|
|
||||||
|
try{
|
||||||
|
const response = await fetch(`${BASE_URL}/completions/${id}`, {
|
||||||
|
...createRequestConfig('GET'),
|
||||||
|
})
|
||||||
|
return await handleResponse(response);
|
||||||
|
}
|
||||||
|
catch (error) {
|
||||||
|
handleUnauthorizedError(error);
|
||||||
|
console.error(`Error when receiving a completed survey by id: ${error}`);
|
||||||
|
throw error;
|
||||||
|
}
|
||||||
|
}
|
||||||
42
SurveyFrontend/src/api/ExportResultApi.ts
Normal file
42
SurveyFrontend/src/api/ExportResultApi.ts
Normal file
|
|
@ -0,0 +1,42 @@
|
||||||
|
import {BASE_URL, createRequestConfig, handleUnauthorizedError} from "./BaseApi.ts";
|
||||||
|
|
||||||
|
export const getResultsFile = async (surveyId: number) => {
|
||||||
|
try {
|
||||||
|
const response = await fetch(`${BASE_URL}/export/excel/${surveyId}`, {
|
||||||
|
...createRequestConfig('GET'),
|
||||||
|
});
|
||||||
|
|
||||||
|
if (!response.ok) {
|
||||||
|
throw new Error('Ошибка при получении файла');
|
||||||
|
}
|
||||||
|
|
||||||
|
const blob = await response.blob();
|
||||||
|
|
||||||
|
const url = window.URL.createObjectURL(blob);
|
||||||
|
|
||||||
|
const a = document.createElement('a');
|
||||||
|
a.href = url;
|
||||||
|
|
||||||
|
const contentDisposition = response.headers.get('content-disposition');
|
||||||
|
let fileName = `survey_results_${surveyId}.xlsx`;
|
||||||
|
|
||||||
|
if (contentDisposition) {
|
||||||
|
const fileNameMatch = contentDisposition.match(/filename="?(.+)"?/);
|
||||||
|
if (fileNameMatch && fileNameMatch[1]) {
|
||||||
|
fileName = fileNameMatch[1];
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
a.download = fileName;
|
||||||
|
document.body.appendChild(a);
|
||||||
|
a.click();
|
||||||
|
|
||||||
|
window.URL.revokeObjectURL(url);
|
||||||
|
document.body.removeChild(a);
|
||||||
|
}
|
||||||
|
catch (error) {
|
||||||
|
handleUnauthorizedError(error);
|
||||||
|
console.error(`Error when receiving survey results: ${error}`);
|
||||||
|
throw error;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
@ -1,5 +1,5 @@
|
||||||
import {BASE_URL, createRequestConfig, handleResponse} from "./BaseApi.ts";
|
import {BASE_URL, createRequestConfig, handleResponse, handleUnauthorizedError} from "./BaseApi.ts";
|
||||||
import {IAnswerVariant} from "./AnswerApi.ts";
|
import {IAnswerVariant} from "./AnswerVariantsApi.ts";
|
||||||
|
|
||||||
export interface INewQuestion{
|
export interface INewQuestion{
|
||||||
title: string;
|
title: string;
|
||||||
|
|
@ -25,6 +25,7 @@ export const addNewQuestion = async (surveyId: number, question: INewQuestion) =
|
||||||
})
|
})
|
||||||
return await handleResponse(response)
|
return await handleResponse(response)
|
||||||
} catch (error){
|
} catch (error){
|
||||||
|
handleUnauthorizedError(error);
|
||||||
throw new Error(`Error when adding a new question: ${error}`);
|
throw new Error(`Error when adding a new question: ${error}`);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
@ -34,50 +35,46 @@ export const getListQuestions = async (surveyId: number): Promise<IQuestion[]> =
|
||||||
const response = await fetch(`${BASE_URL}/surveys/${surveyId}/questions`, {
|
const response = await fetch(`${BASE_URL}/surveys/${surveyId}/questions`, {
|
||||||
...createRequestConfig('GET'),
|
...createRequestConfig('GET'),
|
||||||
})
|
})
|
||||||
if (response.status === 200) {
|
return await handleResponse(response);
|
||||||
return await handleResponse(response);
|
|
||||||
}
|
|
||||||
throw new Error(`Ожидался код 200, получен ${response.status}`);
|
|
||||||
}
|
}
|
||||||
catch(error){
|
catch(error){
|
||||||
|
handleUnauthorizedError(error);
|
||||||
console.error(`Error when receiving the list of questions: ${error}`);
|
console.error(`Error when receiving the list of questions: ${error}`);
|
||||||
throw error;
|
throw error;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
export const updateQuestion = async (surveyId: number, id: number, question: Partial<INewQuestion>): Promise<INewQuestion> => {
|
export const updateQuestion = async (id: number, question: Partial<INewQuestion>): Promise<INewQuestion> => {
|
||||||
const token = localStorage.getItem("token");
|
const token = localStorage.getItem("token");
|
||||||
if (!token) {
|
if (!token) {
|
||||||
throw new Error("Токен отсутствует");
|
throw new Error("Токен отсутствует");
|
||||||
}
|
}
|
||||||
|
|
||||||
try{
|
try{
|
||||||
const response = await fetch(`${BASE_URL}/surveys/${surveyId}/questions/${id}`, {
|
const response = await fetch(`${BASE_URL}/questions/${id}`, {
|
||||||
...createRequestConfig('PUT'),
|
...createRequestConfig('PUT'),
|
||||||
body: JSON.stringify({
|
body: JSON.stringify({
|
||||||
title: question.title,
|
title: question.title,
|
||||||
questionType: question.questionType,
|
questionType: question.questionType,
|
||||||
}),
|
}),
|
||||||
})
|
})
|
||||||
if (response.status === 200) {
|
return await handleResponse(response)
|
||||||
return await handleResponse(response)
|
|
||||||
}
|
|
||||||
throw new Error(`Ожидался код 200, получен ${response.status}`)
|
|
||||||
}
|
}
|
||||||
catch(error){
|
catch(error){
|
||||||
|
handleUnauthorizedError(error);
|
||||||
console.error(`Error when updating question: ${error}`);
|
console.error(`Error when updating question: ${error}`);
|
||||||
throw error;
|
throw error;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
export const deleteQuestion = async (surveyId: number, id: number) => {
|
export const deleteQuestion = async (id: number) => {
|
||||||
const token = localStorage.getItem("token");
|
const token = localStorage.getItem("token");
|
||||||
if (!token) {
|
if (!token) {
|
||||||
throw new Error("Токен отсутствует");
|
throw new Error("Токен отсутствует");
|
||||||
}
|
}
|
||||||
|
|
||||||
try{
|
try{
|
||||||
const response = await fetch(`${BASE_URL}/surveys/${surveyId}/questions/${id}`, {
|
const response = await fetch(`${BASE_URL}/questions/${id}`, {
|
||||||
...createRequestConfig('DELETE'),
|
...createRequestConfig('DELETE'),
|
||||||
})
|
})
|
||||||
const responseData = await handleResponse(response);
|
const responseData = await handleResponse(response);
|
||||||
|
|
@ -86,6 +83,7 @@ export const deleteQuestion = async (surveyId: number, id: number) => {
|
||||||
}
|
}
|
||||||
return responseData;
|
return responseData;
|
||||||
} catch (error){
|
} catch (error){
|
||||||
|
handleUnauthorizedError(error);
|
||||||
console.error(`Error deleting a question: ${error}`);
|
console.error(`Error deleting a question: ${error}`);
|
||||||
throw error;
|
throw error;
|
||||||
}
|
}
|
||||||
|
|
|
||||||
|
|
@ -1,4 +1,4 @@
|
||||||
import {BASE_URL, createRequestConfig, handleResponse} from "./BaseApi.ts";
|
import {BASE_URL, createRequestConfig, handleResponse, handleUnauthorizedError} from "./BaseApi.ts";
|
||||||
|
|
||||||
export interface ISurvey {
|
export interface ISurvey {
|
||||||
id: number;
|
id: number;
|
||||||
|
|
@ -28,6 +28,7 @@ export const getMySurveys = async (): Promise<ISurvey[]> => {
|
||||||
});
|
});
|
||||||
return await handleResponse(response);
|
return await handleResponse(response);
|
||||||
} catch (error) {
|
} catch (error) {
|
||||||
|
handleUnauthorizedError(error);
|
||||||
console.error("Error receiving surveys:", error);
|
console.error("Error receiving surveys:", error);
|
||||||
throw error;
|
throw error;
|
||||||
}
|
}
|
||||||
|
|
@ -43,6 +44,7 @@ export const getAllSurveys = async (): Promise<ISurvey[]> => {
|
||||||
})
|
})
|
||||||
return await handleResponse(response);
|
return await handleResponse(response);
|
||||||
} catch (error) {
|
} catch (error) {
|
||||||
|
handleUnauthorizedError(error);
|
||||||
console.error("Error receiving surveys:", error);
|
console.error("Error receiving surveys:", error);
|
||||||
throw error;
|
throw error;
|
||||||
}
|
}
|
||||||
|
|
@ -68,12 +70,13 @@ export const postNewSurvey = async (survey: INewSurvey): Promise<ISurvey> => {
|
||||||
throw new Error(`Ошибка: ${response.status}`);
|
throw new Error(`Ошибка: ${response.status}`);
|
||||||
}
|
}
|
||||||
|
|
||||||
const data = await response.json();
|
const data = await handleResponse(response);
|
||||||
if (!data.id) {
|
if (!data.id) {
|
||||||
throw new Error("Сервер не вернул ID опроса");
|
throw new Error("Сервер не вернул ID опроса");
|
||||||
}
|
}
|
||||||
return data;
|
return data;
|
||||||
} catch (error) {
|
} catch (error) {
|
||||||
|
handleUnauthorizedError(error);
|
||||||
console.error(`Error when adding a new survey: ${error}`);
|
console.error(`Error when adding a new survey: ${error}`);
|
||||||
throw error;
|
throw error;
|
||||||
}
|
}
|
||||||
|
|
@ -90,6 +93,7 @@ export const getSurveyById = async (surveyId: number): Promise<ISurvey> => {
|
||||||
})
|
})
|
||||||
return await handleResponse(response);
|
return await handleResponse(response);
|
||||||
} catch (error){
|
} catch (error){
|
||||||
|
handleUnauthorizedError(error);
|
||||||
console.error(`Error finding the survey by id: ${error}`);
|
console.error(`Error finding the survey by id: ${error}`);
|
||||||
throw error;
|
throw error;
|
||||||
}
|
}
|
||||||
|
|
@ -115,6 +119,7 @@ export const deleteSurvey = async (surveyId: number) => {
|
||||||
}
|
}
|
||||||
return responseData;
|
return responseData;
|
||||||
} catch (error){
|
} catch (error){
|
||||||
|
handleUnauthorizedError(error);
|
||||||
console.error(`Error deleting a survey: ${error}`);
|
console.error(`Error deleting a survey: ${error}`);
|
||||||
throw error;
|
throw error;
|
||||||
}
|
}
|
||||||
|
|
@ -138,12 +143,10 @@ export const updateSurvey = async (surveyId: number, survey: Partial<INewSurvey>
|
||||||
description: survey.description,
|
description: survey.description,
|
||||||
})
|
})
|
||||||
})
|
})
|
||||||
if (response.status === 200) {
|
return await handleResponse(response);
|
||||||
return await handleResponse(response);
|
|
||||||
}
|
|
||||||
throw new Error(`Ожидался код 200, получен ${response.status}`);
|
|
||||||
}
|
}
|
||||||
catch (error){
|
catch (error){
|
||||||
|
handleUnauthorizedError(error);
|
||||||
console.error(`Error updating survey: ${error}`);
|
console.error(`Error updating survey: ${error}`);
|
||||||
throw error;
|
throw error;
|
||||||
}
|
}
|
||||||
|
|
|
||||||
|
|
@ -4,8 +4,8 @@
|
||||||
background-color: #F3F3F3;
|
background-color: #F3F3F3;
|
||||||
border-radius: 40px;
|
border-radius: 40px;
|
||||||
align-items: center;
|
align-items: center;
|
||||||
padding: 4.58px 13px 4.58px 4.58px;
|
padding: 2px 10px 2px 4.5px;
|
||||||
margin: 26px 33px 27px 0;
|
margin: 15px 33px 15px 0;
|
||||||
margin-left: auto;
|
margin-left: auto;
|
||||||
display: inline-flex;
|
display: inline-flex;
|
||||||
max-width: 100%;
|
max-width: 100%;
|
||||||
|
|
@ -18,7 +18,7 @@
|
||||||
display: flex;
|
display: flex;
|
||||||
align-items: center;
|
align-items: center;
|
||||||
justify-content: space-around;
|
justify-content: space-around;
|
||||||
font-size: 24px;
|
font-size: 18px;
|
||||||
font-weight: 600;
|
font-weight: 600;
|
||||||
color: black;
|
color: black;
|
||||||
text-decoration: none;
|
text-decoration: none;
|
||||||
|
|
@ -27,7 +27,6 @@
|
||||||
|
|
||||||
.accountImg{
|
.accountImg{
|
||||||
vertical-align: middle;
|
vertical-align: middle;
|
||||||
width: 55px;
|
width: 40px;
|
||||||
margin-right: 9px;
|
|
||||||
flex-shrink: 0;
|
flex-shrink: 0;
|
||||||
}
|
}
|
||||||
|
|
|
||||||
|
|
@ -11,28 +11,6 @@ const Account: React.FC<AccountProps> = ({ href }) => {
|
||||||
const [userName, setUserName] = useState<string>();
|
const [userName, setUserName] = useState<string>();
|
||||||
const [isLoading, setIsLoading] = useState(true);
|
const [isLoading, setIsLoading] = useState(true);
|
||||||
|
|
||||||
// useEffect(() => {
|
|
||||||
// const fetchUserData = async () => {
|
|
||||||
// try {
|
|
||||||
// const userData = localStorage.getItem("user");
|
|
||||||
//
|
|
||||||
// if (userData) {
|
|
||||||
// const parsedData = JSON.parse(userData);
|
|
||||||
// setUserName(`${parsedData.firstName} ${parsedData.lastName}`);
|
|
||||||
// } else {
|
|
||||||
// const data = await getCurrentUser();
|
|
||||||
// setUserName(`${data.firstName} ${data.lastName}`);
|
|
||||||
// }
|
|
||||||
// } catch (error) {
|
|
||||||
// console.error("Ошибка загрузки данных пользователя:", error);
|
|
||||||
// } finally {
|
|
||||||
// setIsLoading(false);
|
|
||||||
// }
|
|
||||||
// };
|
|
||||||
//
|
|
||||||
// fetchUserData();
|
|
||||||
// }, []);
|
|
||||||
|
|
||||||
useEffect(() => {
|
useEffect(() => {
|
||||||
const fetchUserData = async () => {
|
const fetchUserData = async () => {
|
||||||
try {
|
try {
|
||||||
|
|
|
||||||
|
|
@ -3,15 +3,16 @@
|
||||||
.answerButton {
|
.answerButton {
|
||||||
margin-top: 18px;
|
margin-top: 18px;
|
||||||
display: flex;
|
display: flex;
|
||||||
gap: 10px;
|
gap: 8px;
|
||||||
align-items: center;
|
align-items: center;
|
||||||
border: none;
|
border: none;
|
||||||
background-color: white;
|
background-color: white;
|
||||||
color: #3788D6;
|
color: #3788D6;
|
||||||
font-size: 18px;
|
font-size: 15px;
|
||||||
font-weight: 500;
|
font-weight: 500;
|
||||||
}
|
}
|
||||||
|
|
||||||
.addAnswerImg{
|
.addAnswerImg{
|
||||||
|
width: 12px;
|
||||||
vertical-align: middle;
|
vertical-align: middle;
|
||||||
}
|
}
|
||||||
|
|
@ -4,7 +4,7 @@
|
||||||
margin: 0 auto;
|
margin: 0 auto;
|
||||||
display: flex;
|
display: flex;
|
||||||
flex-direction: column;
|
flex-direction: column;
|
||||||
margin-bottom: 80px;
|
margin-bottom: 40px;
|
||||||
align-items: center;
|
align-items: center;
|
||||||
background-color: #F6F6F6;
|
background-color: #F6F6F6;
|
||||||
border: none;
|
border: none;
|
||||||
|
|
@ -12,12 +12,13 @@
|
||||||
}
|
}
|
||||||
|
|
||||||
.questionButtonImg{
|
.questionButtonImg{
|
||||||
width: 54px;
|
width: 40px;
|
||||||
align-items: center;
|
align-items: center;
|
||||||
|
margin-bottom: -15px;
|
||||||
}
|
}
|
||||||
|
|
||||||
.textButton{
|
.textButton{
|
||||||
font-size: 24px;
|
font-size: 18px;
|
||||||
font-weight: 600;
|
font-weight: 600;
|
||||||
text-align: center;
|
text-align: center;
|
||||||
}
|
}
|
||||||
|
|
@ -4,19 +4,20 @@
|
||||||
width: 100%;
|
width: 100%;
|
||||||
display: flex;
|
display: flex;
|
||||||
gap: 10px;
|
gap: 10px;
|
||||||
margin-bottom: 17px;
|
margin-bottom: 7px;
|
||||||
align-items: flex-start;
|
align-items: flex-start;
|
||||||
}
|
}
|
||||||
|
|
||||||
.textAnswer {
|
.textAnswer {
|
||||||
text-align: left;
|
text-align: left;
|
||||||
border: none;
|
border: none;
|
||||||
|
outline: none;
|
||||||
background: none;
|
background: none;
|
||||||
font-size: 18px;
|
font-size: 15px;
|
||||||
font-weight: 500;
|
font-weight: 500;
|
||||||
word-break: break-word;
|
word-break: break-word;
|
||||||
width: 70%;
|
|
||||||
padding: 0;
|
padding: 0;
|
||||||
|
margin-right: 150px;
|
||||||
line-height: 24px;
|
line-height: 24px;
|
||||||
cursor: text;
|
cursor: text;
|
||||||
margin-top: 2px;
|
margin-top: 2px;
|
||||||
|
|
@ -38,20 +39,21 @@
|
||||||
}
|
}
|
||||||
|
|
||||||
.answerIcon {
|
.answerIcon {
|
||||||
width: 24px;
|
width: 20px;
|
||||||
height: 24px;
|
height: 20px;
|
||||||
display: block;
|
display: block;
|
||||||
}
|
}
|
||||||
|
|
||||||
.answerInput {
|
.answerInput {
|
||||||
font-size: 18px;
|
font-size: 15px;
|
||||||
font-weight: 500;
|
font-weight: 500;
|
||||||
outline: none;
|
outline: none;
|
||||||
border: none;
|
border: none;
|
||||||
resize: none;
|
resize: none;
|
||||||
width: 70%;
|
width: 70%;
|
||||||
padding: 0;
|
padding: 0;
|
||||||
margin-top: 2px;
|
margin-top: 3px;
|
||||||
|
margin-bottom: -20px;
|
||||||
font-family: inherit;
|
font-family: inherit;
|
||||||
min-height: 24px;
|
min-height: 24px;
|
||||||
height: auto;
|
height: auto;
|
||||||
|
|
@ -66,4 +68,8 @@
|
||||||
border: none;
|
border: none;
|
||||||
background-color: transparent;
|
background-color: transparent;
|
||||||
padding: 0;
|
padding: 0;
|
||||||
|
}
|
||||||
|
|
||||||
|
.img{
|
||||||
|
width: 20px;
|
||||||
}
|
}
|
||||||
|
|
@ -6,6 +6,7 @@ import Multiple from '../../assets/emptyCheckbox.svg?react';
|
||||||
import SelectedSingle from '../../assets/radio_button_checked.svg?react'
|
import SelectedSingle from '../../assets/radio_button_checked.svg?react'
|
||||||
import SelectedMultiple from '../../assets/check_box.svg?react';
|
import SelectedMultiple from '../../assets/check_box.svg?react';
|
||||||
import TextareaAutosize from 'react-textarea-autosize';
|
import TextareaAutosize from 'react-textarea-autosize';
|
||||||
|
import {useRouteReadOnly} from "../../hooks/useRouteReadOnly.ts";
|
||||||
|
|
||||||
interface AnswerOptionProps{
|
interface AnswerOptionProps{
|
||||||
index: number;
|
index: number;
|
||||||
|
|
@ -15,14 +16,14 @@ interface AnswerOptionProps{
|
||||||
selectedType: 'SingleAnswerQuestion' | 'MultipleAnswerQuestion';
|
selectedType: 'SingleAnswerQuestion' | 'MultipleAnswerQuestion';
|
||||||
isSelected?: boolean;
|
isSelected?: boolean;
|
||||||
toggleSelect?: () => void;
|
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 [currentValue, setCurrentValue] = useState(value);
|
||||||
const [isEditing, setIsEditing] = useState(false);
|
const [isEditing, setIsEditing] = useState(false);
|
||||||
|
|
||||||
const textAreaRef = useRef<HTMLTextAreaElement>(null);
|
const textAreaRef = useRef<HTMLTextAreaElement>(null);
|
||||||
|
const isReadOnly = useRouteReadOnly();
|
||||||
|
|
||||||
useEffect(() => {
|
useEffect(() => {
|
||||||
setCurrentValue(value);
|
setCurrentValue(value);
|
||||||
|
|
@ -71,7 +72,7 @@ const AnswerOption: React.FC<AnswerOptionProps> = ({index, value, onChange, onDe
|
||||||
}, [isEditing]);
|
}, [isEditing]);
|
||||||
|
|
||||||
const handleMarkerClick = () => {
|
const handleMarkerClick = () => {
|
||||||
if (isCompleteSurveyActive && toggleSelect) {
|
if (isReadOnly && toggleSelect) {
|
||||||
toggleSelect();
|
toggleSelect();
|
||||||
}
|
}
|
||||||
};
|
};
|
||||||
|
|
@ -79,7 +80,7 @@ const AnswerOption: React.FC<AnswerOptionProps> = ({index, value, onChange, onDe
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<div className={styles.answer}>
|
<div className={styles.answer}>
|
||||||
{isCompleteSurveyActive ? (
|
{isReadOnly ? (
|
||||||
<button
|
<button
|
||||||
className={`${styles.buttonMarker} ${isSelected ? styles.selected : ''}`}
|
className={`${styles.buttonMarker} ${isSelected ? styles.selected : ''}`}
|
||||||
onClick={handleMarkerClick}
|
onClick={handleMarkerClick}
|
||||||
|
|
@ -108,7 +109,7 @@ const AnswerOption: React.FC<AnswerOptionProps> = ({index, value, onChange, onDe
|
||||||
</button>
|
</button>
|
||||||
)}
|
)}
|
||||||
|
|
||||||
{isCompleteSurveyActive ? (
|
{isReadOnly ? (
|
||||||
<button className={styles.textAnswer}>
|
<button className={styles.textAnswer}>
|
||||||
{currentValue || `Ответ ${index}`}
|
{currentValue || `Ответ ${index}`}
|
||||||
</button>
|
</button>
|
||||||
|
|
@ -128,9 +129,9 @@ const AnswerOption: React.FC<AnswerOptionProps> = ({index, value, onChange, onDe
|
||||||
</button>
|
</button>
|
||||||
)}
|
)}
|
||||||
|
|
||||||
{!isCompleteSurveyActive && (
|
{!isReadOnly && (
|
||||||
<button className={styles.deleteButton} onClick={() => onDelete?.(index)}>
|
<button className={styles.deleteButton} onClick={() => onDelete?.(index)}>
|
||||||
<Delete />
|
<Delete className={styles.img}/>
|
||||||
</button>
|
</button>
|
||||||
)}
|
)}
|
||||||
</div>
|
</div>
|
||||||
|
|
|
||||||
|
|
@ -4,4 +4,19 @@
|
||||||
max-width: 100vw;
|
max-width: 100vw;
|
||||||
min-height: 100vh;
|
min-height: 100vh;
|
||||||
padding: 34px 16%;
|
padding: 34px 16%;
|
||||||
|
}
|
||||||
|
|
||||||
|
.departur_button{
|
||||||
|
display: block;
|
||||||
|
margin: 10px auto;
|
||||||
|
padding: 20px 40px;
|
||||||
|
border: none;
|
||||||
|
border-radius: 20px;
|
||||||
|
background-color: #3788D6;
|
||||||
|
color: white;
|
||||||
|
font-weight: 700;
|
||||||
|
font-size: 18px;
|
||||||
|
text-align: center;
|
||||||
|
box-shadow: 0 0 7.4px 0 rgba(154, 202, 247, 1);
|
||||||
|
box-sizing: border-box;
|
||||||
}
|
}
|
||||||
|
|
@ -1,30 +1,117 @@
|
||||||
import SurveyInfo from "../SurveyInfo/SurveyInfo.tsx";
|
import SurveyInfo from "../SurveyInfo/SurveyInfo.tsx";
|
||||||
import QuestionsList, {Question} from "../QuestionsList/QuestionsList.tsx";
|
import QuestionsList, {Question} from "../QuestionsList/QuestionsList.tsx";
|
||||||
import {useState} from "react";
|
import {useEffect, useState} from "react";
|
||||||
import styles from './CompletingSurvey.module.css'
|
import styles from './CompletingSurvey.module.css'
|
||||||
|
import {useNavigate, 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";
|
||||||
|
import {addNewCompletion} from "../../api/CompletionApi.ts";
|
||||||
|
|
||||||
|
interface ISelectedAnswers{
|
||||||
|
questionId: number;
|
||||||
|
answerText: string;
|
||||||
|
}
|
||||||
|
|
||||||
export const CompletingSurvey = () => {
|
export const CompletingSurvey = () => {
|
||||||
const [titleSurvey, setTitleSurvey] = useState("Название опроса");
|
const {surveyId} = useParams<{surveyId: string}>();
|
||||||
const [descriptionSurvey, setDescriptionSurvey] = useState("");
|
const [survey, setSurvey] = useState<ISurvey | null>(null);
|
||||||
const [questions, setQuestions] = useState<Question[]>([
|
const [questions, setQuestions] = useState<Question[]>([]);
|
||||||
{ id: 1, text: 'Вопрос 1', questionType: 'SingleAnswerQuestion', answerVariants: [{ id: 1, text: 'Ответ 1' },
|
const [loading, setLoading] = useState(true);
|
||||||
{ id: 2, text: 'Ответ 1' }, { id: 3, text: 'Ответ 1' }]},
|
const [error, setError] = useState<string | null>(null);
|
||||||
{ id: 2, text: 'Вопрос 2', questionType: 'MultipleAnswerQuestion', answerVariants: [{ id: 1, text: 'Ответ 1' },
|
|
||||||
{ id: 2, text: 'Ответ 1' }, { id: 3, text: 'Ответ 1' }]}
|
const [selectedAnswers, setSelectedAnswers] = useState<ISelectedAnswers[]>([]);
|
||||||
]);
|
const navigate = useNavigate();
|
||||||
|
|
||||||
|
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]);
|
||||||
|
|
||||||
|
const handleAnswerSelect = (questionId: number, answerText: string) => {
|
||||||
|
setSelectedAnswers(prev => {
|
||||||
|
const question = questions.find(q => q.id === questionId);
|
||||||
|
if (question?.questionType === 'SingleAnswerQuestion') {
|
||||||
|
return [
|
||||||
|
...prev.filter(a => a.questionId !== questionId),
|
||||||
|
{ questionId, answerText }
|
||||||
|
];
|
||||||
|
}
|
||||||
|
|
||||||
|
const existingAnswerIndex = prev.findIndex(
|
||||||
|
a => a.questionId === questionId && a.answerText === answerText
|
||||||
|
);
|
||||||
|
|
||||||
|
if (existingAnswerIndex >= 0) {
|
||||||
|
return prev.filter((_, index) => index !== existingAnswerIndex);
|
||||||
|
} else {
|
||||||
|
return [...prev, { questionId, answerText }];
|
||||||
|
}
|
||||||
|
});
|
||||||
|
};
|
||||||
|
|
||||||
|
const handleSubmit = async () => {
|
||||||
|
if (!surveyId) return;
|
||||||
|
|
||||||
|
try {
|
||||||
|
await addNewCompletion(parseInt(surveyId), {
|
||||||
|
answers: selectedAnswers
|
||||||
|
});
|
||||||
|
|
||||||
|
navigate('/my-surveys');
|
||||||
|
} catch (error) {
|
||||||
|
console.error('Ошибка при отправке ответов:', error);
|
||||||
|
}
|
||||||
|
};
|
||||||
|
|
||||||
|
if (loading) return <div>Загрузка...</div>;
|
||||||
|
if (error) return <div>{error}</div>;
|
||||||
|
if (!survey) return <div>Опрос не найден</div>;
|
||||||
|
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<div className={styles.survey}>
|
<div className={styles.survey}>
|
||||||
<SurveyInfo
|
<SurveyInfo
|
||||||
titleSurvey={titleSurvey}
|
titleSurvey={survey.title}
|
||||||
descriptionSurvey={descriptionSurvey}
|
descriptionSurvey={survey.description}
|
||||||
setDescriptionSurvey={setDescriptionSurvey}
|
setDescriptionSurvey={(value) => setSurvey({ ...survey, description: value })}
|
||||||
setTitleSurvey={setTitleSurvey}
|
setTitleSurvey={(value) => setSurvey({ ...survey, title: value })}
|
||||||
/>
|
/>
|
||||||
<QuestionsList
|
<QuestionsList
|
||||||
questions={questions}
|
questions={questions}
|
||||||
setQuestions={setQuestions}
|
setQuestions={setQuestions}
|
||||||
|
onAnswerSelect={handleAnswerSelect}
|
||||||
/>
|
/>
|
||||||
|
<button className={styles.departur_button} onClick={handleSubmit} disabled={selectedAnswers.length === 0}>Отправить</button>
|
||||||
</div>
|
</div>
|
||||||
)
|
)
|
||||||
}
|
}
|
||||||
|
|
|
||||||
|
|
@ -5,18 +5,19 @@
|
||||||
padding: 0;
|
padding: 0;
|
||||||
width: 100%;
|
width: 100%;
|
||||||
display: flex;
|
display: flex;
|
||||||
|
height: fit-content;
|
||||||
}
|
}
|
||||||
|
|
||||||
.pagesNav{
|
.pagesNav{
|
||||||
display: flex;
|
display: flex;
|
||||||
gap: 60px;
|
gap: 80px;
|
||||||
list-style: none;
|
list-style: none;
|
||||||
align-items: center;
|
align-items: center;
|
||||||
margin-right: 20%;
|
margin-right: 20%;
|
||||||
}
|
}
|
||||||
|
|
||||||
.pageLink{
|
.pageLink{
|
||||||
font-size: 24px;
|
font-size: 18px;
|
||||||
font-weight: 600;
|
font-weight: 600;
|
||||||
color: #2A6DAE;
|
color: #2A6DAE;
|
||||||
padding: 0;
|
padding: 0;
|
||||||
|
|
|
||||||
|
|
@ -10,13 +10,12 @@ const Header: React.FC = () => {
|
||||||
|
|
||||||
const isCreateSurveyActive = location.pathname.startsWith('/survey/create');
|
const isCreateSurveyActive = location.pathname.startsWith('/survey/create');
|
||||||
const isMySurveysActive = location.pathname === '/my-surveys';
|
const isMySurveysActive = location.pathname === '/my-surveys';
|
||||||
const isCompleteSurveyActive = location.pathname === '/complete-survey';
|
|
||||||
|
|
||||||
const isSurveyViewPage = location.pathname.startsWith('/survey/') &&
|
const isSurveyViewPage = location.pathname.startsWith('/survey/') &&
|
||||||
!location.pathname.startsWith('/survey/create');
|
!location.pathname.startsWith('/survey/create');
|
||||||
|
|
||||||
const handleLogoClick = () => {
|
const handleLogoClick = () => {
|
||||||
navigate(location.pathname, { replace: true });
|
navigate(location.pathname);
|
||||||
};
|
};
|
||||||
|
|
||||||
return (
|
return (
|
||||||
|
|
@ -37,13 +36,6 @@ const Header: React.FC = () => {
|
||||||
Мои опросы
|
Мои опросы
|
||||||
{(isMySurveysActive || isSurveyViewPage) && <hr className={styles.activeLine}/>}
|
{(isMySurveysActive || isSurveyViewPage) && <hr className={styles.activeLine}/>}
|
||||||
</Link>
|
</Link>
|
||||||
<Link
|
|
||||||
to='/complete-survey'
|
|
||||||
className={`${styles.pageLink} ${isCompleteSurveyActive ? styles.active : ''}`}
|
|
||||||
>
|
|
||||||
Прохождение опроса
|
|
||||||
{isCompleteSurveyActive && <hr className={styles.activeLine}/>}
|
|
||||||
</Link>
|
|
||||||
</nav>
|
</nav>
|
||||||
<Account href={'/profile'} />
|
<Account href={'/profile'} />
|
||||||
</div>
|
</div>
|
||||||
|
|
|
||||||
|
|
@ -1,19 +1,19 @@
|
||||||
.loginContainer{
|
.loginContainer{
|
||||||
width: 31%;
|
width: 26%;
|
||||||
|
height: fit-content;
|
||||||
background-color: #FFFFFF;
|
background-color: #FFFFFF;
|
||||||
padding: 42.5px 65px;
|
padding: 42.5px 65px;
|
||||||
margin: auto;
|
margin: 0 auto;
|
||||||
border-radius: 43px;
|
border-radius: 43px;
|
||||||
margin-bottom: 0;
|
|
||||||
}
|
}
|
||||||
|
|
||||||
.title{
|
.title{
|
||||||
text-align: center;
|
text-align: center;
|
||||||
font-weight: 600;
|
font-weight: 600;
|
||||||
font-size: 40px;
|
font-size: 30px;
|
||||||
line-height: 88%;
|
line-height: 88%;
|
||||||
padding: 0;
|
padding: 0;
|
||||||
margin-bottom: 80px;
|
margin-bottom: 60px;
|
||||||
margin-top: 0;
|
margin-top: 0;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
@ -21,12 +21,12 @@
|
||||||
text-align: center;
|
text-align: center;
|
||||||
display: flex;
|
display: flex;
|
||||||
flex-direction: column;
|
flex-direction: column;
|
||||||
gap: 80px;
|
gap: 50px;
|
||||||
margin-bottom: 80px;
|
margin-bottom: 40px;
|
||||||
}
|
}
|
||||||
|
|
||||||
.input {
|
.input {
|
||||||
font-size: 24px;
|
font-size: 18px;
|
||||||
font-weight: 600;
|
font-weight: 600;
|
||||||
line-height: 88%;
|
line-height: 88%;
|
||||||
color: #000000;
|
color: #000000;
|
||||||
|
|
@ -49,14 +49,14 @@
|
||||||
|
|
||||||
.errorMessage{
|
.errorMessage{
|
||||||
text-align: left;
|
text-align: left;
|
||||||
font-size: 14px;
|
font-size: 12px;
|
||||||
font-weight: 400;
|
font-weight: 400;
|
||||||
line-height: 88%;
|
line-height: 88%;
|
||||||
color: #C0231F;
|
color: #C0231F;
|
||||||
}
|
}
|
||||||
|
|
||||||
.input::placeholder {
|
.input::placeholder {
|
||||||
font-size: 24px;
|
font-size: 18px;
|
||||||
font-weight: 600;
|
font-weight: 600;
|
||||||
line-height: 88%;
|
line-height: 88%;
|
||||||
color: #000000;
|
color: #000000;
|
||||||
|
|
@ -78,12 +78,12 @@
|
||||||
|
|
||||||
.signIn{
|
.signIn{
|
||||||
margin: auto;
|
margin: auto;
|
||||||
padding: 26.5px 67px;
|
padding: 20px 40px;
|
||||||
width: fit-content;
|
width: fit-content;
|
||||||
border-radius: 24px;
|
border-radius: 24px;
|
||||||
background-color: #3788D6;
|
background-color: #3788D6;
|
||||||
color: #FFFFFF;
|
color: #FFFFFF;
|
||||||
font-size: 24px;
|
font-size: 20px;
|
||||||
font-weight: 600;
|
font-weight: 600;
|
||||||
line-height: 120%;
|
line-height: 120%;
|
||||||
border: none;
|
border: none;
|
||||||
|
|
@ -92,7 +92,7 @@
|
||||||
|
|
||||||
.recommendation{
|
.recommendation{
|
||||||
text-align: center;
|
text-align: center;
|
||||||
font-size: 18px;
|
font-size: 15px;
|
||||||
font-weight: 500;
|
font-weight: 500;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
|
||||||
|
|
@ -27,7 +27,7 @@ const LoginForm = () => {
|
||||||
else {
|
else {
|
||||||
const responseData = await authUser({email, password});
|
const responseData = await authUser({email, password});
|
||||||
if (responseData && !responseData.error)
|
if (responseData && !responseData.error)
|
||||||
navigate('/my-surveys');
|
navigate('/my-surveys', {replace: true});
|
||||||
else
|
else
|
||||||
setError('Неверный логин или пароль')
|
setError('Неверный логин или пароль')
|
||||||
}
|
}
|
||||||
|
|
|
||||||
|
|
@ -1,8 +1,13 @@
|
||||||
/*Logo.module.css*/
|
/*Logo.module.css*/
|
||||||
|
|
||||||
.logo {
|
.logo {
|
||||||
|
outline: none;
|
||||||
padding: 0;
|
padding: 0;
|
||||||
height: 52px;
|
margin: 0 100px 0 40px;
|
||||||
width: 52px;
|
display: flex;
|
||||||
margin: 31px 77px 25px 40px;
|
align-items: center;
|
||||||
|
}
|
||||||
|
.logoImg{
|
||||||
|
outline: none;
|
||||||
|
width: 40px;
|
||||||
}
|
}
|
||||||
|
|
@ -10,7 +10,7 @@ interface LogoProps {
|
||||||
const Logo: React.FC<LogoProps> = ({href, onClick}) => {
|
const Logo: React.FC<LogoProps> = ({href, onClick}) => {
|
||||||
return (
|
return (
|
||||||
<a className={styles.logo} href={href} onClick={onClick}>
|
<a className={styles.logo} href={href} onClick={onClick}>
|
||||||
<LogoImg/>
|
<LogoImg className={styles.logoImg}/>
|
||||||
</a>
|
</a>
|
||||||
);
|
);
|
||||||
};
|
};
|
||||||
|
|
|
||||||
|
|
@ -12,7 +12,7 @@
|
||||||
background-color: white;
|
background-color: white;
|
||||||
width: 79%;
|
width: 79%;
|
||||||
border-radius: 14px;
|
border-radius: 14px;
|
||||||
padding: 29px 36px 29px 54px;
|
padding: 29px 36px 15px 54px;
|
||||||
margin-bottom: 23px;
|
margin-bottom: 23px;
|
||||||
gap: 20px;
|
gap: 20px;
|
||||||
border: none;
|
border: none;
|
||||||
|
|
@ -35,6 +35,8 @@
|
||||||
|
|
||||||
.buttonDelete{
|
.buttonDelete{
|
||||||
border-radius: 8px;
|
border-radius: 8px;
|
||||||
|
margin-top: 10px;
|
||||||
|
margin-left: 30px;
|
||||||
align-items: center;
|
align-items: center;
|
||||||
background-color: #FFFFFF;
|
background-color: #FFFFFF;
|
||||||
border: none;
|
border: none;
|
||||||
|
|
@ -42,7 +44,7 @@
|
||||||
padding: 5px 3px;
|
padding: 5px 3px;
|
||||||
color: black;
|
color: black;
|
||||||
font-weight: 500;
|
font-weight: 500;
|
||||||
font-size: 18px;
|
font-size: 15px;
|
||||||
}
|
}
|
||||||
|
|
||||||
.buttonDelete:hover{
|
.buttonDelete:hover{
|
||||||
|
|
@ -51,12 +53,13 @@
|
||||||
|
|
||||||
.imgDelete{
|
.imgDelete{
|
||||||
vertical-align: middle;
|
vertical-align: middle;
|
||||||
|
width: 18px;
|
||||||
}
|
}
|
||||||
|
|
||||||
.status {
|
.status {
|
||||||
width: fit-content;
|
width: fit-content;
|
||||||
height: fit-content;
|
height: fit-content;
|
||||||
padding: 15px 47px;
|
padding: 12px 35px;
|
||||||
border-radius: 15px;
|
border-radius: 15px;
|
||||||
color: #FFFFFF;
|
color: #FFFFFF;
|
||||||
white-space: nowrap;
|
white-space: nowrap;
|
||||||
|
|
@ -65,30 +68,33 @@
|
||||||
|
|
||||||
.completed {
|
.completed {
|
||||||
background-color: #B0B0B0;
|
background-color: #B0B0B0;
|
||||||
|
font-size: 18px;
|
||||||
}
|
}
|
||||||
|
|
||||||
.active {
|
.active {
|
||||||
background-color: #65B953;
|
background-color: #65B953;
|
||||||
|
font-size: 18px;
|
||||||
}
|
}
|
||||||
|
|
||||||
.surveyData {
|
.surveyData {
|
||||||
margin-bottom: 33px;
|
margin-top: -15px;
|
||||||
|
margin-bottom: 15px;
|
||||||
}
|
}
|
||||||
|
|
||||||
.title {
|
.title {
|
||||||
font-size: 40px;
|
font-size: 25px;
|
||||||
font-weight: 600;
|
font-weight: 600;
|
||||||
word-break: break-word;
|
word-break: break-word;
|
||||||
}
|
}
|
||||||
|
|
||||||
.description {
|
.description {
|
||||||
font-size: 24px;
|
font-size: 17px;
|
||||||
font-weight: 500;
|
font-weight: 500;
|
||||||
word-break: break-word;
|
word-break: break-word;
|
||||||
}
|
}
|
||||||
|
|
||||||
.date {
|
.date {
|
||||||
font-size: 18px;
|
font-size: 15px;
|
||||||
font-weight: 500;
|
font-weight: 500;
|
||||||
color: #7D7983;
|
color: #7D7983;
|
||||||
}
|
}
|
||||||
|
|
|
||||||
|
|
@ -6,12 +6,12 @@
|
||||||
}
|
}
|
||||||
|
|
||||||
.nav{
|
.nav{
|
||||||
margin: 34px 0 48px 40px;
|
margin: 34px 0 0 60px;
|
||||||
background-color: white;
|
background-color: white;
|
||||||
border-radius: 20px;
|
border-radius: 20px;
|
||||||
}
|
}
|
||||||
|
|
||||||
.navList{
|
.navList{
|
||||||
list-style: none;
|
list-style: none;
|
||||||
padding: 52px 57px 70px 36px;
|
padding: 32px 37px 40px 26px;
|
||||||
}
|
}
|
||||||
|
|
@ -2,13 +2,14 @@
|
||||||
|
|
||||||
.navItem{
|
.navItem{
|
||||||
padding: 0;
|
padding: 0;
|
||||||
margin-bottom: 42px;
|
margin-bottom: 35px;
|
||||||
}
|
}
|
||||||
|
|
||||||
.page{
|
.page{
|
||||||
background-color: white;
|
background-color: white;
|
||||||
border: none;
|
border: none;
|
||||||
font-size: 24px;
|
outline: none;
|
||||||
|
font-size: 18px;
|
||||||
font-weight: 600;
|
font-weight: 600;
|
||||||
color: #AFAFAF;
|
color: #AFAFAF;
|
||||||
}
|
}
|
||||||
|
|
|
||||||
|
|
@ -4,7 +4,7 @@
|
||||||
background-color: white;
|
background-color: white;
|
||||||
display: flex;
|
display: flex;
|
||||||
justify-content: space-between;
|
justify-content: space-between;
|
||||||
margin-bottom: 34px;
|
margin-bottom: 20px;
|
||||||
padding: 27px 29px 26px 36px;
|
padding: 27px 29px 26px 36px;
|
||||||
border-radius: 14px;
|
border-radius: 14px;
|
||||||
}
|
}
|
||||||
|
|
@ -22,15 +22,17 @@
|
||||||
}
|
}
|
||||||
|
|
||||||
.questionTextarea{
|
.questionTextarea{
|
||||||
|
font-family: Monserrat, sans-serif;
|
||||||
width: 70%;
|
width: 70%;
|
||||||
align-items: center;
|
align-items: center;
|
||||||
border: none;
|
border: none;
|
||||||
outline: none;
|
outline: none;
|
||||||
resize: none;
|
resize: none;
|
||||||
margin-bottom: 5px;
|
margin-bottom: 24px;
|
||||||
font-size: 24px;
|
margin-left: -2px;
|
||||||
|
font-size: 18px;
|
||||||
|
margin-top: -2px;
|
||||||
font-weight: 600;
|
font-weight: 600;
|
||||||
line-height: 1.5;
|
|
||||||
overflow-y: hidden;
|
overflow-y: hidden;
|
||||||
min-height: 1em;
|
min-height: 1em;
|
||||||
}
|
}
|
||||||
|
|
@ -44,11 +46,12 @@
|
||||||
}
|
}
|
||||||
|
|
||||||
.textQuestion{
|
.textQuestion{
|
||||||
|
min-height: 1em;
|
||||||
margin-top: 0;
|
margin-top: 0;
|
||||||
width: 100%;
|
width: 100%;
|
||||||
font-size: 24px;
|
font-size: 18px;
|
||||||
font-weight: 600;
|
font-weight: 600;
|
||||||
margin-bottom: 35px;
|
margin-bottom: 30px;
|
||||||
text-align: start;
|
text-align: start;
|
||||||
word-break: break-word;
|
word-break: break-word;
|
||||||
}
|
}
|
||||||
|
|
@ -59,7 +62,7 @@
|
||||||
}
|
}
|
||||||
|
|
||||||
.deleteQuestionButton{
|
.deleteQuestionButton{
|
||||||
font-size: 18px;
|
font-size: 15px;
|
||||||
font-weight: 500;
|
font-weight: 500;
|
||||||
color: #EC221F;
|
color: #EC221F;
|
||||||
border: none;
|
border: none;
|
||||||
|
|
@ -72,6 +75,6 @@
|
||||||
|
|
||||||
.basketImg{
|
.basketImg{
|
||||||
vertical-align: middle;
|
vertical-align: middle;
|
||||||
width: 24px;
|
width: 20px;
|
||||||
color: #EC221F;
|
color: #EC221F;
|
||||||
}
|
}
|
||||||
|
|
@ -9,9 +9,9 @@ import {
|
||||||
deleteAnswerVariant,
|
deleteAnswerVariant,
|
||||||
getAnswerVariants,
|
getAnswerVariants,
|
||||||
updateAnswerVariant
|
updateAnswerVariant
|
||||||
} from "../../api/AnswerApi.ts";
|
} from "../../api/AnswerVariantsApi.ts";
|
||||||
import {useLocation} from "react-router-dom";
|
|
||||||
import TextareaAutosize from "react-textarea-autosize";
|
import TextareaAutosize from "react-textarea-autosize";
|
||||||
|
import {useRouteReadOnly} from "../../hooks/useRouteReadOnly.ts";
|
||||||
|
|
||||||
interface QuestionItemProps {
|
interface QuestionItemProps {
|
||||||
questionId: number;
|
questionId: number;
|
||||||
|
|
@ -23,8 +23,8 @@ interface QuestionItemProps {
|
||||||
onDeleteQuestion: (index: number) => Promise<void>;
|
onDeleteQuestion: (index: number) => Promise<void>;
|
||||||
initialQuestionType: 'SingleAnswerQuestion' | 'MultipleAnswerQuestion';
|
initialQuestionType: 'SingleAnswerQuestion' | 'MultipleAnswerQuestion';
|
||||||
onQuestionTypeChange: (type: 'SingleAnswerQuestion' | 'MultipleAnswerQuestion') => void;
|
onQuestionTypeChange: (type: 'SingleAnswerQuestion' | 'MultipleAnswerQuestion') => void;
|
||||||
|
|
||||||
surveyId?: number;
|
surveyId?: number;
|
||||||
|
onAnswerSelect?: (questionId: number, answerText: string) => void;
|
||||||
}
|
}
|
||||||
|
|
||||||
const QuestionItem: React.FC<QuestionItemProps> = ({
|
const QuestionItem: React.FC<QuestionItemProps> = ({
|
||||||
|
|
@ -37,7 +37,8 @@ const QuestionItem: React.FC<QuestionItemProps> = ({
|
||||||
onDeleteQuestion,
|
onDeleteQuestion,
|
||||||
initialQuestionType,
|
initialQuestionType,
|
||||||
onQuestionTypeChange,
|
onQuestionTypeChange,
|
||||||
surveyId
|
surveyId,
|
||||||
|
onAnswerSelect
|
||||||
}) => {
|
}) => {
|
||||||
const [textQuestion, setTextQuestion] = useState(initialTextQuestion);
|
const [textQuestion, setTextQuestion] = useState(initialTextQuestion);
|
||||||
const [isEditingQuestion, setIsEditingQuestion] = useState(false);
|
const [isEditingQuestion, setIsEditingQuestion] = useState(false);
|
||||||
|
|
@ -45,8 +46,7 @@ const QuestionItem: React.FC<QuestionItemProps> = ({
|
||||||
const [questionType, setQuestionType] = useState<'SingleAnswerQuestion' | 'MultipleAnswerQuestion'>(initialQuestionType);
|
const [questionType, setQuestionType] = useState<'SingleAnswerQuestion' | 'MultipleAnswerQuestion'>(initialQuestionType);
|
||||||
const textareaQuestionRef = useRef<HTMLTextAreaElement>(null);
|
const textareaQuestionRef = useRef<HTMLTextAreaElement>(null);
|
||||||
|
|
||||||
const location = useLocation();
|
const isReadOnly = useRouteReadOnly();
|
||||||
const isCompleteSurveyActive = location.pathname === '/complete-survey';
|
|
||||||
|
|
||||||
|
|
||||||
useEffect(() => {
|
useEffect(() => {
|
||||||
|
|
@ -63,6 +63,8 @@ const QuestionItem: React.FC<QuestionItemProps> = ({
|
||||||
};
|
};
|
||||||
|
|
||||||
const handleAddAnswer = async () => {
|
const handleAddAnswer = async () => {
|
||||||
|
if (isReadOnly) return
|
||||||
|
|
||||||
if (!surveyId) {
|
if (!surveyId) {
|
||||||
onAnswerVariantsChange([...initialAnswerVariants, { text: '' }]);
|
onAnswerVariantsChange([...initialAnswerVariants, { text: '' }]);
|
||||||
return;
|
return;
|
||||||
|
|
@ -123,8 +125,6 @@ const QuestionItem: React.FC<QuestionItemProps> = ({
|
||||||
if (surveyId && newAnswerVariants[index].id) {
|
if (surveyId && newAnswerVariants[index].id) {
|
||||||
try {
|
try {
|
||||||
await updateAnswerVariant(
|
await updateAnswerVariant(
|
||||||
surveyId,
|
|
||||||
questionId,
|
|
||||||
newAnswerVariants[index].id!,
|
newAnswerVariants[index].id!,
|
||||||
{ text: value }
|
{ text: value }
|
||||||
);
|
);
|
||||||
|
|
@ -139,7 +139,7 @@ const QuestionItem: React.FC<QuestionItemProps> = ({
|
||||||
|
|
||||||
if (surveyId && answerToDelete.id) {
|
if (surveyId && answerToDelete.id) {
|
||||||
try {
|
try {
|
||||||
await deleteAnswerVariant(surveyId, questionId, answerToDelete.id);
|
await deleteAnswerVariant(answerToDelete.id);
|
||||||
const newAnswerVariants = initialAnswerVariants.filter((_, i) => i !== index);
|
const newAnswerVariants = initialAnswerVariants.filter((_, i) => i !== index);
|
||||||
onAnswerVariantsChange(newAnswerVariants);
|
onAnswerVariantsChange(newAnswerVariants);
|
||||||
setSelectedAnswers(selectedAnswers.filter((i) => i !== index));
|
setSelectedAnswers(selectedAnswers.filter((i) => i !== index));
|
||||||
|
|
@ -167,12 +167,28 @@ 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;
|
||||||
|
|
||||||
|
if (onAnswerSelect) {
|
||||||
|
onAnswerSelect(questionId, answerText);
|
||||||
|
}
|
||||||
|
|
||||||
if (initialQuestionType === 'SingleAnswerQuestion') {
|
if (initialQuestionType === 'SingleAnswerQuestion') {
|
||||||
// Для одиночного выбора: заменяем массив одним выбранным индексом
|
|
||||||
setSelectedAnswers([index]);
|
setSelectedAnswers([index]);
|
||||||
} else {
|
} else {
|
||||||
// Для множественного выбора: добавляем/удаляем индекс
|
|
||||||
setSelectedAnswers(prev =>
|
setSelectedAnswers(prev =>
|
||||||
prev.includes(index)
|
prev.includes(index)
|
||||||
? prev.filter(i => i !== index)
|
? prev.filter(i => i !== index)
|
||||||
|
|
@ -183,7 +199,7 @@ const QuestionItem: React.FC<QuestionItemProps> = ({
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<div className={styles.questionCard}>
|
<div className={styles.questionCard}>
|
||||||
{isCompleteSurveyActive ? (
|
{isReadOnly ? (
|
||||||
<div>
|
<div>
|
||||||
<div className={styles.questionContainer}>
|
<div className={styles.questionContainer}>
|
||||||
<h2 className={styles.textQuestion}>{textQuestion || initialTextQuestion}</h2>
|
<h2 className={styles.textQuestion}>{textQuestion || initialTextQuestion}</h2>
|
||||||
|
|
@ -196,7 +212,6 @@ const QuestionItem: React.FC<QuestionItemProps> = ({
|
||||||
value={answer.text}
|
value={answer.text}
|
||||||
isSelected={selectedAnswers.includes(index)}
|
isSelected={selectedAnswers.includes(index)}
|
||||||
toggleSelect={() => toggleSelect(index)}
|
toggleSelect={() => toggleSelect(index)}
|
||||||
isCompleteSurveyActive={isCompleteSurveyActive}
|
|
||||||
/>
|
/>
|
||||||
))}
|
))}
|
||||||
</div>
|
</div>
|
||||||
|
|
|
||||||
|
|
@ -1,16 +0,0 @@
|
||||||
/*QuestionsList.module.css*/
|
|
||||||
|
|
||||||
.departur_button{
|
|
||||||
display: block;
|
|
||||||
margin: 10px auto;
|
|
||||||
padding: 25px 50.5px;
|
|
||||||
border: none;
|
|
||||||
border-radius: 20px;
|
|
||||||
background-color: #3788D6;
|
|
||||||
color: white;
|
|
||||||
font-weight: 700;
|
|
||||||
font-size: 24px;
|
|
||||||
text-align: center;
|
|
||||||
box-shadow: 0 0 7.4px 0 rgba(154, 202, 247, 1);
|
|
||||||
box-sizing: border-box;
|
|
||||||
}
|
|
||||||
|
|
@ -2,14 +2,14 @@ import React from "react";
|
||||||
import QuestionItem from "../QuestionItem/QuestionItem.tsx";
|
import QuestionItem from "../QuestionItem/QuestionItem.tsx";
|
||||||
import AddQuestionButton from "../AddQuestionButton/AddQuestionButton.tsx";
|
import AddQuestionButton from "../AddQuestionButton/AddQuestionButton.tsx";
|
||||||
import {addNewQuestion, deleteQuestion, getListQuestions} from "../../api/QuestionApi.ts";
|
import {addNewQuestion, deleteQuestion, getListQuestions} from "../../api/QuestionApi.ts";
|
||||||
import {addNewAnswerVariant} from "../../api/AnswerApi.ts";
|
import {addNewAnswerVariant} from "../../api/AnswerVariantsApi.ts";
|
||||||
import {useLocation} from "react-router-dom";
|
import {useRouteReadOnly} from "../../hooks/useRouteReadOnly.ts";
|
||||||
import styles from './QuestionsList.module.css'
|
|
||||||
|
|
||||||
interface QuestionsListProps {
|
interface QuestionsListProps {
|
||||||
questions: Question[];
|
questions: Question[];
|
||||||
setQuestions: (questions: Question[]) => void;
|
setQuestions: (questions: Question[]) => void;
|
||||||
surveyId?: number;
|
surveyId?: number;
|
||||||
|
onAnswerSelect?: (questionId: number, answerText: string) => void;
|
||||||
}
|
}
|
||||||
|
|
||||||
export interface Question {
|
export interface Question {
|
||||||
|
|
@ -22,9 +22,8 @@ export interface Question {
|
||||||
}>;
|
}>;
|
||||||
}
|
}
|
||||||
|
|
||||||
const QuestionsList: React.FC<QuestionsListProps> = ({questions, setQuestions, surveyId}) => {
|
const QuestionsList: React.FC<QuestionsListProps> = ({questions, setQuestions, surveyId, onAnswerSelect}) => {
|
||||||
const location = useLocation();
|
const isReadOnly = useRouteReadOnly();
|
||||||
const isCompleteSurveyActive = location.pathname === '/complete-survey';
|
|
||||||
|
|
||||||
const handleAddQuestion = async () => {
|
const handleAddQuestion = async () => {
|
||||||
if (!surveyId) {
|
if (!surveyId) {
|
||||||
|
|
@ -72,7 +71,7 @@ const QuestionsList: React.FC<QuestionsListProps> = ({questions, setQuestions, s
|
||||||
if (surveyId) {
|
if (surveyId) {
|
||||||
const listQuestions = await getListQuestions(surveyId);
|
const listQuestions = await getListQuestions(surveyId);
|
||||||
if (listQuestions.find(q => q.id === id)) {
|
if (listQuestions.find(q => q.id === id)) {
|
||||||
const response = await deleteQuestion(surveyId, id);
|
const response = await deleteQuestion(id);
|
||||||
if (!response?.success) {
|
if (!response?.success) {
|
||||||
throw new Error('Не удалось удалить вопрос на сервере');
|
throw new Error('Не удалось удалить вопрос на сервере');
|
||||||
}
|
}
|
||||||
|
|
@ -126,11 +125,10 @@ const QuestionsList: React.FC<QuestionsListProps> = ({questions, setQuestions, s
|
||||||
initialQuestionType={question.questionType}
|
initialQuestionType={question.questionType}
|
||||||
onQuestionTypeChange={(type) => handleQuestionTypeChange(question.id, type)}
|
onQuestionTypeChange={(type) => handleQuestionTypeChange(question.id, type)}
|
||||||
surveyId={surveyId}
|
surveyId={surveyId}
|
||||||
|
onAnswerSelect={onAnswerSelect}
|
||||||
/>
|
/>
|
||||||
))}
|
))}
|
||||||
{!isCompleteSurveyActive ? <AddQuestionButton onClick={handleAddQuestion} /> : (
|
{!isReadOnly ? <AddQuestionButton onClick={handleAddQuestion} /> : ''}
|
||||||
<button className={styles.departur_button}>Отправить</button>
|
|
||||||
)}
|
|
||||||
|
|
||||||
</>
|
</>
|
||||||
);
|
);
|
||||||
|
|
|
||||||
|
|
@ -1,18 +1,19 @@
|
||||||
.registerContainer{
|
.registerContainer{
|
||||||
width: 31%;
|
width: 26%;
|
||||||
|
height: fit-content;
|
||||||
background-color: #FFFFFF;
|
background-color: #FFFFFF;
|
||||||
padding: 94px 80px;
|
padding: 60px 50px;
|
||||||
margin: auto;
|
margin: 0 auto;
|
||||||
border-radius: 43px;
|
border-radius: 43px;
|
||||||
}
|
}
|
||||||
|
|
||||||
.title{
|
.title{
|
||||||
text-align: center;
|
text-align: center;
|
||||||
font-weight: 600;
|
font-weight: 600;
|
||||||
font-size: 40px;
|
font-size: 30px;
|
||||||
line-height: 88%;
|
line-height: 88%;
|
||||||
padding: 0;
|
padding: 0;
|
||||||
margin-bottom: 80px;
|
margin-bottom: 60px;
|
||||||
margin-top: 0;
|
margin-top: 0;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
@ -20,12 +21,12 @@
|
||||||
text-align: center;
|
text-align: center;
|
||||||
display: flex;
|
display: flex;
|
||||||
flex-direction: column;
|
flex-direction: column;
|
||||||
gap: 80px;
|
gap: 50px;
|
||||||
margin-bottom: 80px;
|
margin-bottom: 40px;
|
||||||
}
|
}
|
||||||
|
|
||||||
.input {
|
.input {
|
||||||
font-size: 24px;
|
font-size: 18px;
|
||||||
font-weight: 600;
|
font-weight: 600;
|
||||||
line-height: 88%;
|
line-height: 88%;
|
||||||
color: #000000;
|
color: #000000;
|
||||||
|
|
@ -37,7 +38,7 @@
|
||||||
}
|
}
|
||||||
|
|
||||||
.input::placeholder {
|
.input::placeholder {
|
||||||
font-size: 24px;
|
font-size: 18px;
|
||||||
font-weight: 600;
|
font-weight: 600;
|
||||||
line-height: 88%;
|
line-height: 88%;
|
||||||
color: #000000;
|
color: #000000;
|
||||||
|
|
@ -59,12 +60,12 @@
|
||||||
|
|
||||||
.signUp{
|
.signUp{
|
||||||
margin: auto;
|
margin: auto;
|
||||||
padding: 25.5px 16px;
|
padding: 22.5px 14px;
|
||||||
width: fit-content;
|
width: fit-content;
|
||||||
border-radius: 24px;
|
border-radius: 24px;
|
||||||
background-color: #3788D6;
|
background-color: #3788D6;
|
||||||
color: #FFFFFF;
|
color: #FFFFFF;
|
||||||
font-size: 24px;
|
font-size: 20px;
|
||||||
font-weight: 600;
|
font-weight: 600;
|
||||||
line-height: 120%;
|
line-height: 120%;
|
||||||
border: none;
|
border: none;
|
||||||
|
|
@ -73,8 +74,9 @@
|
||||||
|
|
||||||
.recommendation{
|
.recommendation{
|
||||||
text-align: center;
|
text-align: center;
|
||||||
font-size: 18px;
|
font-size: 14px;
|
||||||
font-weight: 500;
|
font-weight: 500;
|
||||||
|
margin-bottom: 0;
|
||||||
}
|
}
|
||||||
|
|
||||||
.recommendationLink{
|
.recommendationLink{
|
||||||
|
|
@ -94,7 +96,7 @@
|
||||||
|
|
||||||
.errorMessage{
|
.errorMessage{
|
||||||
text-align: left;
|
text-align: left;
|
||||||
font-size: 14px;
|
font-size: 12px;
|
||||||
font-weight: 400;
|
font-weight: 400;
|
||||||
line-height: 88%;
|
line-height: 88%;
|
||||||
color: #C0231F;
|
color: #C0231F;
|
||||||
|
|
|
||||||
|
|
@ -38,11 +38,8 @@ const RegisterForm = () => {
|
||||||
const responseData = await registerUser({username, firstName, lastName, email, password});
|
const responseData = await registerUser({username, firstName, lastName, email, password});
|
||||||
if (responseData && !responseData.error) {
|
if (responseData && !responseData.error) {
|
||||||
console.log('Регистрация успешна');
|
console.log('Регистрация успешна');
|
||||||
localStorage.setItem("user", JSON.stringify({
|
localStorage.setItem("user", JSON.stringify(responseData.user));
|
||||||
firstName,
|
navigate('/my-surveys', {replace: true});
|
||||||
lastName
|
|
||||||
}));
|
|
||||||
navigate('/my-surveys');
|
|
||||||
}
|
}
|
||||||
else if (responseData.status === 409){
|
else if (responseData.status === 409){
|
||||||
setError('Аккаунт с такой почтой уже зарегистрирован');
|
setError('Аккаунт с такой почтой уже зарегистрирован');
|
||||||
|
|
|
||||||
|
|
@ -9,7 +9,7 @@
|
||||||
display: flex;
|
display: flex;
|
||||||
justify-content: space-between;
|
justify-content: space-between;
|
||||||
align-items: stretch;
|
align-items: stretch;
|
||||||
margin: 30px 0;
|
margin: 20px 0;
|
||||||
gap: 17px;
|
gap: 17px;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
@ -17,7 +17,7 @@
|
||||||
display: flex;
|
display: flex;
|
||||||
flex-direction: column;
|
flex-direction: column;
|
||||||
border-radius: 15px;
|
border-radius: 15px;
|
||||||
min-height: 180px;
|
min-height: 140px;
|
||||||
padding: 20px;
|
padding: 20px;
|
||||||
box-sizing: border-box;
|
box-sizing: border-box;
|
||||||
position: relative;
|
position: relative;
|
||||||
|
|
@ -43,9 +43,9 @@
|
||||||
}
|
}
|
||||||
|
|
||||||
.statItem h3 {
|
.statItem h3 {
|
||||||
margin: 0 0 15px 0;
|
margin: 0 0 0 0;
|
||||||
color: #FFFFFF;
|
color: #FFFFFF;
|
||||||
font-size: 28px;
|
font-size: 20px;
|
||||||
font-weight: 600;
|
font-weight: 600;
|
||||||
line-height: 1.2;
|
line-height: 1.2;
|
||||||
}
|
}
|
||||||
|
|
@ -68,20 +68,20 @@
|
||||||
|
|
||||||
.countAnswer p,
|
.countAnswer p,
|
||||||
.completion_percentage p {
|
.completion_percentage p {
|
||||||
font-size: 60px;
|
margin-bottom: -10px;
|
||||||
|
font-size: 35px;
|
||||||
}
|
}
|
||||||
|
|
||||||
.imgGroup,
|
.imgGroup,
|
||||||
.imgSend {
|
.imgSend {
|
||||||
width: 58px;
|
width: 43px;
|
||||||
height: 61px;
|
|
||||||
align-self: flex-end;
|
align-self: flex-end;
|
||||||
}
|
}
|
||||||
|
|
||||||
.status p {
|
.status p {
|
||||||
text-align: center;
|
text-align: center;
|
||||||
margin-top: auto;
|
margin-top: auto;
|
||||||
font-size: 32px;
|
font-size: 26px;
|
||||||
}
|
}
|
||||||
|
|
||||||
.questionContainer {
|
.questionContainer {
|
||||||
|
|
@ -106,13 +106,13 @@
|
||||||
.textContainer {
|
.textContainer {
|
||||||
display: flex;
|
display: flex;
|
||||||
flex-direction: column;
|
flex-direction: column;
|
||||||
gap: 11px;
|
gap: 5px;
|
||||||
width: 30%;
|
width: 30%;
|
||||||
min-width: 250px;
|
min-width: 250px;
|
||||||
}
|
}
|
||||||
|
|
||||||
.questionContainer h3 {
|
.questionContainer h3 {
|
||||||
font-size: 24px;
|
font-size: 20px;
|
||||||
font-weight: 600;
|
font-weight: 600;
|
||||||
color: #000000;
|
color: #000000;
|
||||||
margin: 0;
|
margin: 0;
|
||||||
|
|
@ -120,7 +120,7 @@
|
||||||
|
|
||||||
.answerCount {
|
.answerCount {
|
||||||
color: #000000;
|
color: #000000;
|
||||||
font-size: 18px;
|
font-size: 16px;
|
||||||
font-weight: 600;
|
font-weight: 600;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
@ -133,16 +133,31 @@
|
||||||
}
|
}
|
||||||
|
|
||||||
.pieContainer {
|
.pieContainer {
|
||||||
width: 100%;
|
width: 70%;
|
||||||
height: 450px;
|
|
||||||
position: relative;
|
position: relative;
|
||||||
}
|
}
|
||||||
|
|
||||||
.barContainer {
|
.barContainer {
|
||||||
width: 100%;
|
width: 100%;
|
||||||
height: 450px;
|
|
||||||
display: flex;
|
display: flex;
|
||||||
justify-content: center;
|
justify-content: center;
|
||||||
align-items: center;
|
align-items: center;
|
||||||
padding-right: 150px;
|
padding-right: 150px;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
.exportButtonContainer {
|
||||||
|
padding: 10px 15px;
|
||||||
|
/*background-color: #4CAF50;*/
|
||||||
|
background-color: #3788D6;
|
||||||
|
color: white;
|
||||||
|
border: none;
|
||||||
|
border-radius: 4px;
|
||||||
|
cursor: pointer;
|
||||||
|
font-size: 16px;
|
||||||
|
display: block;
|
||||||
|
margin: 30px auto 15px;
|
||||||
|
}
|
||||||
|
|
||||||
|
.exportButtonContainer:hover {
|
||||||
|
background-color: #45a049;
|
||||||
|
}
|
||||||
|
|
@ -3,66 +3,170 @@ import styles from './Results.module.css';
|
||||||
import {Bar, Pie} from 'react-chartjs-2';
|
import {Bar, Pie} from 'react-chartjs-2';
|
||||||
import {Chart as ChartJS, ArcElement, Tooltip, Legend, CategoryScale, LinearScale, BarElement, Title} from 'chart.js';
|
import {Chart as ChartJS, ArcElement, Tooltip, Legend, CategoryScale, LinearScale, BarElement, Title} from 'chart.js';
|
||||||
import {useOutletContext} from "react-router-dom";
|
import {useOutletContext} from "react-router-dom";
|
||||||
import {ISurvey} from "../../api/SurveyApi.ts";
|
import {ISurvey, getSurveyById} from "../../api/SurveyApi.ts";
|
||||||
import ChartDataLabels from 'chartjs-plugin-datalabels';
|
import ChartDataLabels from 'chartjs-plugin-datalabels';
|
||||||
import annotationPlugin from 'chartjs-plugin-annotation';
|
import annotationPlugin from 'chartjs-plugin-annotation';
|
||||||
import Group from '../../assets/gmail_groups.svg?react';
|
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 {getAnswer} from "../../api/AnswerApi.ts";
|
||||||
|
import {useEffect, useState} from "react";
|
||||||
|
import {getListQuestions} from "../../api/QuestionApi.ts";
|
||||||
|
import {getAnswerVariants, IAnswerVariant} from "../../api/AnswerVariantsApi.ts";
|
||||||
|
import {getResultsFile} from "../../api/ExportResultApi.ts";
|
||||||
|
|
||||||
ChartJS.register(
|
ChartJS.register(
|
||||||
ArcElement, Tooltip, Legend,
|
ArcElement, Tooltip, Legend,
|
||||||
CategoryScale, LinearScale, BarElement, Title, ChartDataLabels, annotationPlugin
|
CategoryScale, LinearScale, BarElement, Title, ChartDataLabels, annotationPlugin
|
||||||
);
|
);
|
||||||
|
|
||||||
// Типы для данных
|
|
||||||
interface QuestionStats {
|
interface QuestionStats {
|
||||||
questionText: string;
|
questionText: string;
|
||||||
totalAnswers: number;
|
totalAnswers: number;
|
||||||
options: {
|
options: {
|
||||||
text: string;
|
text: string;
|
||||||
percentage: number;
|
percentage: number;
|
||||||
|
id: number;
|
||||||
}[];
|
}[];
|
||||||
isMultipleChoice?: boolean;
|
isMultipleChoice: boolean;
|
||||||
|
}
|
||||||
|
|
||||||
|
interface IAnswer {
|
||||||
|
questionId: number;
|
||||||
|
completionId: number;
|
||||||
|
answerText: string;
|
||||||
|
optionId?: number;
|
||||||
}
|
}
|
||||||
|
|
||||||
export const Results = () => {
|
export const Results = () => {
|
||||||
const { survey, setSurvey } = useOutletContext<{
|
const { survey: initialSurvey, setSurvey } = useOutletContext<{
|
||||||
survey: ISurvey;
|
survey: ISurvey;
|
||||||
setSurvey: (survey: ISurvey) => void;
|
setSurvey: (survey: ISurvey) => void;
|
||||||
}>();
|
}>();
|
||||||
|
|
||||||
|
const [surveyStats, setSurveyStats] = useState({
|
||||||
const surveyStats = {
|
totalParticipants: 0,
|
||||||
totalParticipants: 100,
|
completionPercentage: 0,
|
||||||
completionPercentage: 80,
|
|
||||||
status: 'Активен',
|
status: 'Активен',
|
||||||
questions: [
|
questions: [] as QuestionStats[]
|
||||||
{
|
});
|
||||||
questionText: "Вопрос 1",
|
|
||||||
totalAnswers: 80,
|
const [survey, setLocalSurvey] = useState<ISurvey>(initialSurvey);
|
||||||
options: [
|
// const [questions, setQuestions] = useState<IQuestion[]>([]);
|
||||||
{ text: "Вариант 1", percentage: 46 },
|
|
||||||
{ text: "Вариант 2", percentage: 15 },
|
const handleExportToExcel = async (id: number) => {
|
||||||
{ text: "Вариант 3", percentage: 39 }
|
await getResultsFile(id)
|
||||||
],
|
|
||||||
isMultipleChoice: false
|
|
||||||
},
|
|
||||||
{
|
|
||||||
questionText: "Вопрос 2",
|
|
||||||
totalAnswers: 100,
|
|
||||||
options: [
|
|
||||||
{ text: "Вариант 1", percentage: 50 },
|
|
||||||
{ text: "Вариант 2", percentage: 20 },
|
|
||||||
{ text: "Вариант 3", percentage: 100 },
|
|
||||||
{ text: "Вариант 4", percentage: 80 }
|
|
||||||
],
|
|
||||||
isMultipleChoice: true
|
|
||||||
}
|
|
||||||
] as QuestionStats[]
|
|
||||||
};
|
};
|
||||||
|
|
||||||
// Цветовая палитра
|
useEffect(() => {
|
||||||
|
const fetchSurveyData = async () => {
|
||||||
|
try {
|
||||||
|
const surveyData = await getSurveyById(survey.id);
|
||||||
|
setLocalSurvey(surveyData);
|
||||||
|
|
||||||
|
const questionsList = await getListQuestions(survey.id);
|
||||||
|
// setQuestions(questionsList);
|
||||||
|
|
||||||
|
const questionsWithVariants = await Promise.all(
|
||||||
|
questionsList.map(async (question) => {
|
||||||
|
const variants = await getAnswerVariants(survey.id, question.id);
|
||||||
|
return {
|
||||||
|
...question,
|
||||||
|
options: variants,
|
||||||
|
isMultipleChoice: question.questionType !== 'SingleAnswerQuestion'
|
||||||
|
};
|
||||||
|
})
|
||||||
|
);
|
||||||
|
|
||||||
|
const completions = await getAllCompletions(survey.id);
|
||||||
|
const totalParticipants = completions.length;
|
||||||
|
|
||||||
|
const questionsWithAnswers = await Promise.all(
|
||||||
|
questionsWithVariants.map(async (question) => {
|
||||||
|
const answers = await getAnswer(question.id);
|
||||||
|
return {
|
||||||
|
...question,
|
||||||
|
answers: answers
|
||||||
|
};
|
||||||
|
})
|
||||||
|
);
|
||||||
|
|
||||||
|
const questionsStats = questionsWithAnswers.map(question => {
|
||||||
|
const questionAnswers = question.answers as IAnswer[];
|
||||||
|
|
||||||
|
let uniqueAnswers = questionAnswers;
|
||||||
|
if (question.isMultipleChoice) {
|
||||||
|
const groupedByCompletion = questionAnswers.reduce((acc, answer) => {
|
||||||
|
if (!acc[answer.completionId]) {
|
||||||
|
acc[answer.completionId] = [];
|
||||||
|
}
|
||||||
|
acc[answer.completionId].push(answer);
|
||||||
|
return acc;
|
||||||
|
}, {} as Record<number, IAnswer[]>);
|
||||||
|
|
||||||
|
uniqueAnswers = Object.values(groupedByCompletion).map(group => ({
|
||||||
|
...group[0],
|
||||||
|
answerText: group.map(a => a.answerText).join("; ")
|
||||||
|
}));
|
||||||
|
}
|
||||||
|
|
||||||
|
const optionsStats = question.options.map((option: IAnswerVariant) => {
|
||||||
|
const count = uniqueAnswers.filter(answer =>
|
||||||
|
question.isMultipleChoice
|
||||||
|
? answer.answerText.includes(option.text)
|
||||||
|
: answer.answerText === option.text
|
||||||
|
).length;
|
||||||
|
|
||||||
|
const percentage = totalParticipants > 0
|
||||||
|
? Math.round((count / totalParticipants) * 100)
|
||||||
|
: 0;
|
||||||
|
|
||||||
|
return {
|
||||||
|
text: option.text,
|
||||||
|
percentage: percentage,
|
||||||
|
id: option.id
|
||||||
|
};
|
||||||
|
});
|
||||||
|
|
||||||
|
return {
|
||||||
|
questionText: question.title,
|
||||||
|
totalAnswers: uniqueAnswers.length,
|
||||||
|
options: optionsStats,
|
||||||
|
isMultipleChoice: question.isMultipleChoice
|
||||||
|
};
|
||||||
|
});
|
||||||
|
|
||||||
|
const totalAnswersCount = questionsWithAnswers.reduce((sum, question) => {
|
||||||
|
if (question.isMultipleChoice) {
|
||||||
|
const uniqueCompletions = new Set(
|
||||||
|
question.answers.map((answer : IAnswer) => answer.completionId)
|
||||||
|
);
|
||||||
|
return sum + uniqueCompletions.size;
|
||||||
|
} else {
|
||||||
|
return sum + question.answers.length;
|
||||||
|
}
|
||||||
|
}, 0);
|
||||||
|
|
||||||
|
const maxPossibleAnswers = questionsWithAnswers.length * totalParticipants;
|
||||||
|
const completionPercentage = totalParticipants > 0 && maxPossibleAnswers > 0
|
||||||
|
? Math.round((totalAnswersCount / maxPossibleAnswers) * 100)
|
||||||
|
: 0;
|
||||||
|
|
||||||
|
setSurveyStats({
|
||||||
|
totalParticipants,
|
||||||
|
completionPercentage,
|
||||||
|
status: 'Активен',
|
||||||
|
questions: questionsStats
|
||||||
|
});
|
||||||
|
|
||||||
|
} catch (error) {
|
||||||
|
console.error('Error fetching survey data:', error);
|
||||||
|
}
|
||||||
|
};
|
||||||
|
|
||||||
|
fetchSurveyData();
|
||||||
|
}, [survey.id]);
|
||||||
|
|
||||||
const colorsForPie = ['#67C587', '#C9EAD4', '#EAF6ED'];
|
const colorsForPie = ['#67C587', '#C9EAD4', '#EAF6ED'];
|
||||||
const colorsForBar = ['#8979FF'];
|
const colorsForBar = ['#8979FF'];
|
||||||
|
|
||||||
|
|
@ -71,8 +175,14 @@ export const Results = () => {
|
||||||
<SurveyInfo
|
<SurveyInfo
|
||||||
titleSurvey={survey.title}
|
titleSurvey={survey.title}
|
||||||
descriptionSurvey={survey.description}
|
descriptionSurvey={survey.description}
|
||||||
setDescriptionSurvey={(value) => setSurvey({ ...survey, description: value })}
|
setDescriptionSurvey={(value) => {
|
||||||
setTitleSurvey={(value) => setSurvey({ ...survey, title: value })}
|
setSurvey({ ...survey, description: value });
|
||||||
|
setLocalSurvey({ ...survey, description: value });
|
||||||
|
}}
|
||||||
|
setTitleSurvey={(value) => {
|
||||||
|
setSurvey({ ...survey, title: value });
|
||||||
|
setLocalSurvey({ ...survey, title: value });
|
||||||
|
}}
|
||||||
/>
|
/>
|
||||||
<div className={styles.statsContainer}>
|
<div className={styles.statsContainer}>
|
||||||
<div className={`${styles.statItem} ${styles.countAnswer}`}>
|
<div className={`${styles.statItem} ${styles.countAnswer}`}>
|
||||||
|
|
@ -133,7 +243,7 @@ export const Results = () => {
|
||||||
xValue: i,
|
xValue: i,
|
||||||
yValue: opt.percentage + 5,
|
yValue: opt.percentage + 5,
|
||||||
content: `${opt.percentage}%`,
|
content: `${opt.percentage}%`,
|
||||||
font: { size: 16, weight: 400 },
|
font: { size: 1, weight: 400 },
|
||||||
color: '#000'
|
color: '#000'
|
||||||
}))
|
}))
|
||||||
}
|
}
|
||||||
|
|
@ -148,7 +258,7 @@ export const Results = () => {
|
||||||
ticks: {
|
ticks: {
|
||||||
color: '#000000',
|
color: '#000000',
|
||||||
font: {
|
font: {
|
||||||
size: 16,
|
size: 12,
|
||||||
weight: 400
|
weight: 400
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
|
|
@ -178,7 +288,7 @@ export const Results = () => {
|
||||||
labels: {
|
labels: {
|
||||||
color: '#000000',
|
color: '#000000',
|
||||||
font: {
|
font: {
|
||||||
size: 18,
|
size: 12,
|
||||||
weight: 500
|
weight: 500
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
@ -191,7 +301,7 @@ export const Results = () => {
|
||||||
datalabels: {
|
datalabels: {
|
||||||
formatter: (value) => `${value}%`,
|
formatter: (value) => `${value}%`,
|
||||||
color: '#000',
|
color: '#000',
|
||||||
font: { weight: 400, size: 16 }
|
font: { weight: 400, size: 12 }
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
animation: { animateRotate: true }
|
animation: { animateRotate: true }
|
||||||
|
|
@ -203,6 +313,8 @@ export const Results = () => {
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
))}
|
))}
|
||||||
|
|
||||||
|
<button className={styles.exportButtonContainer} onClick={() => handleExportToExcel(survey.id)}>Экспорт в excel</button>
|
||||||
</div>
|
</div>
|
||||||
);
|
);
|
||||||
};
|
};
|
||||||
|
|
@ -2,14 +2,14 @@
|
||||||
|
|
||||||
.createSurveyButton {
|
.createSurveyButton {
|
||||||
display: block;
|
display: block;
|
||||||
margin: 10px auto;
|
margin: 0 auto;
|
||||||
padding: 25px 50.5px;
|
padding: 20px 35px;
|
||||||
border: none;
|
border: none;
|
||||||
border-radius: 20px;
|
border-radius: 20px;
|
||||||
background-color: #3788D6;
|
background-color: #3788D6;
|
||||||
color: white;
|
color: white;
|
||||||
font-weight: 700;
|
font-weight: 700;
|
||||||
font-size: 24px;
|
font-size: 18px;
|
||||||
text-align: center;
|
text-align: center;
|
||||||
box-shadow: 0 0 7.4px 0 rgba(154, 202, 247, 1);
|
box-shadow: 0 0 7.4px 0 rgba(154, 202, 247, 1);
|
||||||
box-sizing: border-box;
|
box-sizing: border-box;
|
||||||
|
|
|
||||||
|
|
@ -7,7 +7,7 @@ interface CreateSurveyButtonProps {
|
||||||
|
|
||||||
const SaveButton: React.FC<CreateSurveyButtonProps> = ({onClick}) => {
|
const SaveButton: React.FC<CreateSurveyButtonProps> = ({onClick}) => {
|
||||||
return (
|
return (
|
||||||
<button onClick={onClick} className={styles.createSurveyButton}>
|
<button onClick={onClick} className={styles.createSurveyButton} >
|
||||||
Сохранить
|
Сохранить
|
||||||
</button>
|
</button>
|
||||||
);
|
);
|
||||||
|
|
|
||||||
|
|
@ -14,14 +14,31 @@
|
||||||
.param{
|
.param{
|
||||||
border-radius: 4px;
|
border-radius: 4px;
|
||||||
background-color: #FFFFFF;
|
background-color: #FFFFFF;
|
||||||
padding-top: 15px;
|
padding-top: 8px;
|
||||||
padding-bottom: 97px;
|
padding-bottom: 60px;
|
||||||
padding-left: 19px;
|
padding-left: 19px;
|
||||||
margin-bottom: 30px;
|
margin-bottom: 30px;
|
||||||
}
|
}
|
||||||
|
|
||||||
.param h2{
|
.param h2{
|
||||||
font-size: 24px;
|
font-size: 18px;
|
||||||
font-weight: 600;
|
font-weight: 600;
|
||||||
border-radius: 4px;
|
border-radius: 4px;
|
||||||
|
}
|
||||||
|
|
||||||
|
.copyButton {
|
||||||
|
padding: 10px 15px;
|
||||||
|
/*background-color: #4CAF50;*/
|
||||||
|
background-color: #3788D6;
|
||||||
|
color: white;
|
||||||
|
border: none;
|
||||||
|
border-radius: 4px;
|
||||||
|
cursor: pointer;
|
||||||
|
font-size: 16px;
|
||||||
|
display: block;
|
||||||
|
margin: 30px auto 15px;
|
||||||
|
}
|
||||||
|
|
||||||
|
.copyButton:hover {
|
||||||
|
background-color: #45a049;
|
||||||
}
|
}
|
||||||
|
|
@ -1,10 +1,11 @@
|
||||||
import React, {useState} from 'react';
|
import React 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 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";
|
||||||
|
|
||||||
|
|
||||||
const SettingSurvey: React.FC = () => {
|
const SettingSurvey: React.FC = () => {
|
||||||
|
|
@ -14,18 +15,25 @@ const SettingSurvey: React.FC = () => {
|
||||||
survey: ISurvey;
|
survey: ISurvey;
|
||||||
setSurvey: (survey: ISurvey) => void;
|
setSurvey: (survey: ISurvey) => void;
|
||||||
}>();
|
}>();
|
||||||
|
const { tempSurvey, setTempSurvey } = useSurveyContext();
|
||||||
|
|
||||||
const [descriptionSurvey, setDescriptionSurvey] = useState('');
|
const handleCopyLink = () => {
|
||||||
const [titleSurvey, setTitleSurvey] = useState('');
|
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 (
|
return (
|
||||||
<div className={styles.settingSurvey}>
|
<div className={styles.settingSurvey}>
|
||||||
{isSettingCreatePage ? (
|
{isSettingCreatePage ? (
|
||||||
<SurveyInfo
|
<SurveyInfo
|
||||||
titleSurvey={titleSurvey}
|
titleSurvey={tempSurvey.title}
|
||||||
descriptionSurvey={descriptionSurvey}
|
descriptionSurvey={tempSurvey.description}
|
||||||
setDescriptionSurvey={setDescriptionSurvey}
|
setDescriptionSurvey={(value) => setTempSurvey({ ...tempSurvey, description: value })}
|
||||||
setTitleSurvey={setTitleSurvey}
|
setTitleSurvey={(value) => setTempSurvey({ ...tempSurvey, title: value })}
|
||||||
/>
|
/>
|
||||||
) : (
|
) : (
|
||||||
<SurveyInfo
|
<SurveyInfo
|
||||||
|
|
@ -43,6 +51,7 @@ const SettingSurvey: React.FC = () => {
|
||||||
<h2>Параметры видимости</h2>
|
<h2>Параметры видимости</h2>
|
||||||
</div>
|
</div>
|
||||||
<SaveButton onClick={() => {}}/>
|
<SaveButton onClick={() => {}}/>
|
||||||
|
{!isSettingCreatePage ? <button onClick={handleCopyLink} className={styles.copyButton}>Копировать ссылку</button> : ''}
|
||||||
</div>
|
</div>
|
||||||
)
|
)
|
||||||
}
|
}
|
||||||
|
|
|
||||||
|
|
@ -1,64 +1,78 @@
|
||||||
import React, {useState} from "react";
|
import React, { useState, useEffect } from "react";
|
||||||
import SurveyInfo from "../SurveyInfo/SurveyInfo.tsx";
|
import SurveyInfo from "../SurveyInfo/SurveyInfo.tsx";
|
||||||
import QuestionsList, {Question} from "../QuestionsList/QuestionsList.tsx";
|
import QuestionsList, { Question } from "../QuestionsList/QuestionsList.tsx";
|
||||||
import styles from './Survey.module.css'
|
import styles from './Survey.module.css';
|
||||||
import SaveButton from "../SaveButton/SaveButton.tsx";
|
import SaveButton from "../SaveButton/SaveButton.tsx";
|
||||||
import {ISurvey, postNewSurvey} from "../../api/SurveyApi.ts";
|
import { ISurvey, postNewSurvey } from "../../api/SurveyApi.ts";
|
||||||
import {addNewQuestion} from "../../api/QuestionApi.ts";
|
import { addNewQuestion } from "../../api/QuestionApi.ts";
|
||||||
import {useNavigate} from "react-router-dom";
|
import { useLocation, useNavigate } from "react-router-dom";
|
||||||
import {addNewAnswerVariant} from "../../api/AnswerApi.ts";
|
import { addNewAnswerVariant } from "../../api/AnswerVariantsApi.ts";
|
||||||
|
import { useSurveyContext } from "../../context/SurveyContext.tsx";
|
||||||
|
|
||||||
const Survey: React.FC = () => {
|
const Survey: React.FC = () => {
|
||||||
const navigate = useNavigate();
|
const navigate = useNavigate();
|
||||||
const [descriptionSurvey, setDescriptionSurvey] = useState('');
|
const location = useLocation();
|
||||||
const [titleSurvey, setTitleSurvey] = useState('Название опроса');
|
const { tempSurvey, setTempSurvey, clearTempSurvey } = useSurveyContext();
|
||||||
const [survey] = useState<ISurvey | null>(null);
|
const [survey] = useState<ISurvey | null>(null);
|
||||||
|
|
||||||
const [questions, setQuestions] = useState<Question[]>([
|
const [questions, setQuestions] = useState<Question[]>(
|
||||||
{ id: 1, text: '', questionType: 'SingleAnswerQuestion', answerVariants: [{ text: '' }]},
|
tempSurvey.questions.length > 0
|
||||||
]);
|
? tempSurvey.questions
|
||||||
|
: [{ id: 1, text: '', questionType: 'SingleAnswerQuestion', answerVariants: [{ text: '' }] }]
|
||||||
|
);
|
||||||
|
|
||||||
|
useEffect(() => {
|
||||||
|
if (!tempSurvey.title && !tempSurvey.description && tempSurvey.questions.length === 0) {
|
||||||
|
setTempSurvey({
|
||||||
|
title: "Название опроса",
|
||||||
|
description: "",
|
||||||
|
questions: [{ id: 1, text: '', questionType: 'SingleAnswerQuestion', answerVariants: [{ text: '' }] }]
|
||||||
|
});
|
||||||
|
setQuestions([{ id: 1, text: '', questionType: 'SingleAnswerQuestion', answerVariants: [{ text: '' }] }]);
|
||||||
|
}
|
||||||
|
}, []);
|
||||||
|
|
||||||
|
useEffect(() => {
|
||||||
|
setTempSurvey({
|
||||||
|
...tempSurvey,
|
||||||
|
questions: questions,
|
||||||
|
});
|
||||||
|
}, [questions]);
|
||||||
|
|
||||||
|
useEffect(() => {
|
||||||
|
const isCreateSurveyRoute = location.pathname.includes('/survey/create');
|
||||||
|
|
||||||
|
if (!isCreateSurveyRoute) {
|
||||||
|
clearTempSurvey();
|
||||||
|
setQuestions([{ id: 1, text: '', questionType: 'SingleAnswerQuestion', answerVariants: [{ text: '' }] }]);
|
||||||
|
}
|
||||||
|
}, [location.pathname]);
|
||||||
|
|
||||||
const handleSave = async () => {
|
const handleSave = async () => {
|
||||||
try {
|
try {
|
||||||
const savedSurvey = await postNewSurvey({
|
const savedSurvey = await postNewSurvey({
|
||||||
title: titleSurvey,
|
title: tempSurvey.title,
|
||||||
description: descriptionSurvey
|
description: tempSurvey.description,
|
||||||
});
|
});
|
||||||
|
|
||||||
const updatedQuestions: Question[] = [];
|
const questionPromises = questions.map(async (question) => {
|
||||||
for (const question of questions) {
|
|
||||||
const newQuestion = await addNewQuestion(savedSurvey.id, {
|
const newQuestion = await addNewQuestion(savedSurvey.id, {
|
||||||
title: question.text,
|
title: question.text,
|
||||||
questionType: question.questionType
|
questionType: question.questionType,
|
||||||
});
|
});
|
||||||
|
|
||||||
const updatedQuestion: Question = {
|
if (question.answerVariants?.length > 0) {
|
||||||
...question,
|
await Promise.all(
|
||||||
id: newQuestion.id,
|
|
||||||
answerVariants: []
|
|
||||||
};
|
|
||||||
|
|
||||||
if (question.answerVariants && question.answerVariants.length > 0) {
|
|
||||||
const newVariants = await Promise.all(
|
|
||||||
question.answerVariants.map(answer =>
|
question.answerVariants.map(answer =>
|
||||||
addNewAnswerVariant(
|
addNewAnswerVariant(savedSurvey.id, newQuestion.id, { text: answer.text })
|
||||||
savedSurvey.id,
|
|
||||||
newQuestion.id,
|
|
||||||
{ text: answer.text }
|
|
||||||
)
|
|
||||||
)
|
)
|
||||||
);
|
);
|
||||||
|
|
||||||
updatedQuestion.answerVariants = newVariants.map((variant: { id: number, text: string }) => ({
|
|
||||||
id: variant.id,
|
|
||||||
text: variant.text
|
|
||||||
}));
|
|
||||||
}
|
}
|
||||||
|
});
|
||||||
|
|
||||||
updatedQuestions.push(updatedQuestion);
|
await Promise.all(questionPromises);
|
||||||
}
|
|
||||||
|
|
||||||
setQuestions(updatedQuestions);
|
clearTempSurvey();
|
||||||
navigate('/my-surveys');
|
navigate('/my-surveys');
|
||||||
} catch (error) {
|
} catch (error) {
|
||||||
console.error('Ошибка при сохранении:', error);
|
console.error('Ошибка при сохранении:', error);
|
||||||
|
|
@ -68,20 +82,19 @@ const Survey: React.FC = () => {
|
||||||
return (
|
return (
|
||||||
<div className={styles.survey}>
|
<div className={styles.survey}>
|
||||||
<SurveyInfo
|
<SurveyInfo
|
||||||
titleSurvey={titleSurvey}
|
titleSurvey={tempSurvey.title || "Название опроса"}
|
||||||
descriptionSurvey={descriptionSurvey}
|
descriptionSurvey={tempSurvey.description}
|
||||||
setDescriptionSurvey={setDescriptionSurvey}
|
setDescriptionSurvey={(value) => setTempSurvey({ ...tempSurvey, description: value })}
|
||||||
setTitleSurvey={setTitleSurvey}
|
setTitleSurvey={(value) => setTempSurvey({ ...tempSurvey, title: value })}
|
||||||
/>
|
/>
|
||||||
<QuestionsList
|
<QuestionsList
|
||||||
questions={questions}
|
questions={questions}
|
||||||
setQuestions={setQuestions}
|
setQuestions={setQuestions}
|
||||||
surveyId={survey?.id}
|
surveyId={survey?.id}
|
||||||
/>
|
/>
|
||||||
|
<SaveButton onClick={handleSave} />
|
||||||
<SaveButton onClick={handleSave}/>
|
|
||||||
</div>
|
</div>
|
||||||
);
|
);
|
||||||
}
|
};
|
||||||
|
|
||||||
export default Survey;
|
export default Survey;
|
||||||
|
|
@ -5,33 +5,30 @@
|
||||||
padding: 0;
|
padding: 0;
|
||||||
width: 100%;
|
width: 100%;
|
||||||
margin-top: 34px;
|
margin-top: 34px;
|
||||||
margin-bottom: 49px;
|
margin-bottom: 30px;
|
||||||
border-radius: 14px;
|
border-radius: 14px;
|
||||||
/*min-height: 191px;*/
|
|
||||||
/*max-height: 100vh;*/
|
|
||||||
max-height: fit-content;
|
max-height: fit-content;
|
||||||
}
|
}
|
||||||
|
|
||||||
.info{
|
.info{
|
||||||
min-width: 373px;
|
min-width: 300px;
|
||||||
/*display: block;*/
|
padding: 20px 35px;
|
||||||
padding: 35px;
|
display: flex;
|
||||||
display: flex; /* Добавляем flex */
|
flex-direction: column;
|
||||||
flex-direction: column; /* Элементы в колонку */
|
|
||||||
align-items: center;
|
align-items: center;
|
||||||
}
|
}
|
||||||
|
|
||||||
.titleSurvey{
|
.titleSurvey{
|
||||||
|
min-height: 60px;
|
||||||
width: 80%;
|
width: 80%;
|
||||||
display: block;
|
display: block;
|
||||||
border: none;
|
border: none;
|
||||||
|
outline: none;
|
||||||
margin: 0 auto 13px;
|
margin: 0 auto 13px;
|
||||||
background-color: white;
|
background-color: white;
|
||||||
text-align: center;
|
text-align: center;
|
||||||
font-size: 20px;
|
font-size: 26px;
|
||||||
font-weight: 600;
|
font-weight: 600;
|
||||||
/*margin-bottom: 23px;*/
|
|
||||||
/*margin-bottom: 15px;*/
|
|
||||||
word-break: break-word;
|
word-break: break-word;
|
||||||
padding: 0;
|
padding: 0;
|
||||||
}
|
}
|
||||||
|
|
@ -52,7 +49,9 @@
|
||||||
}
|
}
|
||||||
|
|
||||||
.textareaTitle {
|
.textareaTitle {
|
||||||
font-size: 32px;
|
margin-top: 14px;
|
||||||
|
margin-bottom: -1px;
|
||||||
|
font-size: 26px;
|
||||||
font-weight: 600;
|
font-weight: 600;
|
||||||
text-align: center;
|
text-align: center;
|
||||||
line-height: 1.2;
|
line-height: 1.2;
|
||||||
|
|
@ -60,11 +59,13 @@
|
||||||
}
|
}
|
||||||
|
|
||||||
.textareaDescrip {
|
.textareaDescrip {
|
||||||
font-size: 18px;
|
font-size: 16px;
|
||||||
font-weight: 500;
|
font-weight: 500;
|
||||||
text-align: center;
|
text-align: center;
|
||||||
line-height: 1.4;
|
line-height: 1.4;
|
||||||
min-height: 24px;
|
min-height: 24px;
|
||||||
|
margin-top: -2px;
|
||||||
|
margin-bottom: -3px;
|
||||||
}
|
}
|
||||||
|
|
||||||
.descriptionWrapper {
|
.descriptionWrapper {
|
||||||
|
|
@ -74,8 +75,10 @@
|
||||||
}
|
}
|
||||||
|
|
||||||
.description {
|
.description {
|
||||||
|
min-height: 24px;
|
||||||
border: none;
|
border: none;
|
||||||
font-size: 24px;
|
outline: none;
|
||||||
|
font-size: 16px;
|
||||||
font-weight: 500;
|
font-weight: 500;
|
||||||
text-align: center;
|
text-align: center;
|
||||||
background-color: white;
|
background-color: white;
|
||||||
|
|
@ -87,7 +90,8 @@
|
||||||
}
|
}
|
||||||
|
|
||||||
.desc{
|
.desc{
|
||||||
font-size: 24px;
|
font-size: 20px;
|
||||||
|
outline: none;
|
||||||
font-weight: 500;
|
font-weight: 500;
|
||||||
background-color: white;
|
background-color: white;
|
||||||
max-width: 80%;
|
max-width: 80%;
|
||||||
|
|
@ -105,12 +109,12 @@
|
||||||
|
|
||||||
.descButtonImg{
|
.descButtonImg{
|
||||||
vertical-align: middle;
|
vertical-align: middle;
|
||||||
width: 28px;
|
width: 20px;
|
||||||
}
|
}
|
||||||
|
|
||||||
.textButton{
|
.textButton{
|
||||||
vertical-align: middle;
|
vertical-align: middle;
|
||||||
font-size: 24px;
|
font-size: 16px;
|
||||||
font-weight: 500;
|
font-weight: 500;
|
||||||
color: #7D7983;
|
color: #7D7983;
|
||||||
padding: 10px;
|
padding: 10px;
|
||||||
|
|
@ -118,7 +122,7 @@
|
||||||
|
|
||||||
.createdAt{
|
.createdAt{
|
||||||
text-align: center;
|
text-align: center;
|
||||||
font-size: 18px;
|
font-size: 15px;
|
||||||
font-weight: 500;
|
font-weight: 500;
|
||||||
color: #7D7983;
|
color: #7D7983;
|
||||||
}
|
}
|
||||||
|
|
@ -3,6 +3,7 @@ import styles from './SurveyInfo.module.css'
|
||||||
import AddDescripImg from '../../assets/add_circle.svg?react';
|
import AddDescripImg from '../../assets/add_circle.svg?react';
|
||||||
import TextareaAutosize from 'react-textarea-autosize';
|
import TextareaAutosize from 'react-textarea-autosize';
|
||||||
import {useLocation} from "react-router-dom";
|
import {useLocation} from "react-router-dom";
|
||||||
|
import {useRouteReadOnly} from "../../hooks/useRouteReadOnly.ts";
|
||||||
|
|
||||||
|
|
||||||
interface SurveyInfoProps {
|
interface SurveyInfoProps {
|
||||||
|
|
@ -20,10 +21,11 @@ const SurveyInfo: React.FC<SurveyInfoProps> = ({titleSurvey, setDescriptionSurve
|
||||||
const descriptionTextareaRef = useRef<HTMLTextAreaElement>(null);
|
const descriptionTextareaRef = useRef<HTMLTextAreaElement>(null);
|
||||||
|
|
||||||
const location = useLocation();
|
const location = useLocation();
|
||||||
const isCompleteSurveyActive = location.pathname === '/complete-survey';
|
|
||||||
const isSurveyViewPage = location.pathname.startsWith('/survey/') &&
|
const isSurveyViewPage = location.pathname.startsWith('/survey/') &&
|
||||||
!location.pathname.startsWith('/survey/create');
|
!location.pathname.startsWith('/survey/create');
|
||||||
|
|
||||||
|
const isReadOnly = useRouteReadOnly();
|
||||||
|
|
||||||
const handleDescriptionChange = (descripEvent: React.ChangeEvent<HTMLTextAreaElement>) => {
|
const handleDescriptionChange = (descripEvent: React.ChangeEvent<HTMLTextAreaElement>) => {
|
||||||
setDescriptionSurvey?.(descripEvent.target.value);
|
setDescriptionSurvey?.(descripEvent.target.value);
|
||||||
};
|
};
|
||||||
|
|
@ -88,17 +90,15 @@ const SurveyInfo: React.FC<SurveyInfoProps> = ({titleSurvey, setDescriptionSurve
|
||||||
}
|
}
|
||||||
|
|
||||||
const renderTitle = () => {
|
const renderTitle = () => {
|
||||||
if (isCompleteSurveyActive) {
|
if (isReadOnly) {
|
||||||
return (
|
return (
|
||||||
<button className={styles.titleSurvey}>
|
<button className={styles.titleSurvey}>{titleSurvey || 'Название опроса'}</button>
|
||||||
<h1>{titleSurvey || 'Название опроса'}</h1>
|
|
||||||
</button>
|
|
||||||
)
|
)
|
||||||
}
|
}
|
||||||
|
|
||||||
if (showNewTitleField) {
|
if (showNewTitleField) {
|
||||||
return (
|
return (
|
||||||
<h1 className={styles.titleSurvey}>
|
// <h1 className={styles.titleSurvey}>
|
||||||
<TextareaAutosize
|
<TextareaAutosize
|
||||||
className={styles.textareaTitle}
|
className={styles.textareaTitle}
|
||||||
ref={titleTextareaRef}
|
ref={titleTextareaRef}
|
||||||
|
|
@ -108,22 +108,20 @@ const SurveyInfo: React.FC<SurveyInfoProps> = ({titleSurvey, setDescriptionSurve
|
||||||
onKeyDown={handleTitleKeyDown}
|
onKeyDown={handleTitleKeyDown}
|
||||||
onBlur={handleTitleBlur}
|
onBlur={handleTitleBlur}
|
||||||
/>
|
/>
|
||||||
</h1>
|
// </h1>
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<button className={styles.titleSurvey} onClick={handleAddNewTitleClick}>
|
<button className={styles.titleSurvey} onClick={handleAddNewTitleClick}>{titleSurvey || 'Название опроса'}</button>
|
||||||
<h1>{titleSurvey || 'Название опроса'}</h1>
|
|
||||||
</button>
|
|
||||||
);
|
);
|
||||||
};
|
};
|
||||||
|
|
||||||
const renderDescription = () => {
|
const renderDescription = () => {
|
||||||
if (isCompleteSurveyActive) {
|
if (isReadOnly) {
|
||||||
return descriptionSurvey ? (
|
return descriptionSurvey ? (
|
||||||
<p className={styles.desc}>{descriptionSurvey}</p>
|
<p className={styles.desc}>{descriptionSurvey}</p>
|
||||||
) : 'Описание';
|
) : '';
|
||||||
}
|
}
|
||||||
|
|
||||||
if (descriptionSurvey && !showDescriptionField) {
|
if (descriptionSurvey && !showDescriptionField) {
|
||||||
|
|
@ -167,7 +165,7 @@ const SurveyInfo: React.FC<SurveyInfoProps> = ({titleSurvey, setDescriptionSurve
|
||||||
{renderTitle()}
|
{renderTitle()}
|
||||||
{renderDescription()}
|
{renderDescription()}
|
||||||
|
|
||||||
{(isSurveyViewPage || isCompleteSurveyActive) && createdAt && (
|
{(isSurveyViewPage || isReadOnly) && createdAt && (
|
||||||
<p className={styles.createdAt}>Дата создания: {addDate()}</p>
|
<p className={styles.createdAt}>Дата создания: {addDate()}</p>
|
||||||
)}
|
)}
|
||||||
</div>
|
</div>
|
||||||
|
|
|
||||||
|
|
@ -6,5 +6,5 @@
|
||||||
color: #C0231F;
|
color: #C0231F;
|
||||||
text-align: center;
|
text-align: center;
|
||||||
margin: 10px 0;
|
margin: 10px 0;
|
||||||
font-size: 18px;
|
font-size: 15px;
|
||||||
}
|
}
|
||||||
|
|
@ -6,7 +6,7 @@ import {useOutletContext} from "react-router-dom";
|
||||||
import { addNewQuestion, getListQuestions, updateQuestion, deleteQuestion } from "../../api/QuestionApi.ts";
|
import { addNewQuestion, getListQuestions, updateQuestion, deleteQuestion } from "../../api/QuestionApi.ts";
|
||||||
import styles from "./SurveyPage.module.css";
|
import styles from "./SurveyPage.module.css";
|
||||||
import SaveButton from "../SaveButton/SaveButton.tsx";
|
import SaveButton from "../SaveButton/SaveButton.tsx";
|
||||||
import { addNewAnswerVariant, deleteAnswerVariant, getAnswerVariants, IAnswerVariant, updateAnswerVariant } from "../../api/AnswerApi.ts";
|
import { addNewAnswerVariant, deleteAnswerVariant, getAnswerVariants, IAnswerVariant, updateAnswerVariant } from "../../api/AnswerVariantsApi.ts";
|
||||||
|
|
||||||
type ActionType =
|
type ActionType =
|
||||||
| 'update-survey'
|
| 'update-survey'
|
||||||
|
|
@ -118,14 +118,14 @@ class ActionQueue {
|
||||||
}
|
}
|
||||||
|
|
||||||
private async handleUpdateQuestion(data: QuestionActionData & { id: number }) {
|
private async handleUpdateQuestion(data: QuestionActionData & { id: number }) {
|
||||||
return await updateQuestion(data.surveyId, data.id, {
|
return await updateQuestion(data.id, {
|
||||||
title: data.title,
|
title: data.title,
|
||||||
questionType: data.questionType
|
questionType: data.questionType
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
private async handleDeleteQuestion(data: QuestionActionData & { id: number }) {
|
private async handleDeleteQuestion(data: QuestionActionData & { id: number }) {
|
||||||
return await deleteQuestion(data.surveyId, data.id);
|
return await deleteQuestion(data.id);
|
||||||
}
|
}
|
||||||
|
|
||||||
private async handleCreateAnswer(data: AnswerActionData) {
|
private async handleCreateAnswer(data: AnswerActionData) {
|
||||||
|
|
@ -136,7 +136,7 @@ class ActionQueue {
|
||||||
|
|
||||||
private async handleUpdateAnswer(data: AnswerActionData & { id: number }) {
|
private async handleUpdateAnswer(data: AnswerActionData & { id: number }) {
|
||||||
try {
|
try {
|
||||||
const result = await updateAnswerVariant(data.surveyId, data.questionId, data.id, {
|
const result = await updateAnswerVariant(data.id, {
|
||||||
text: data.text
|
text: data.text
|
||||||
});
|
});
|
||||||
return result;
|
return result;
|
||||||
|
|
@ -148,16 +148,14 @@ class ActionQueue {
|
||||||
}
|
}
|
||||||
|
|
||||||
private async handleDeleteAnswer(data: AnswerActionData & { id: number }) {
|
private async handleDeleteAnswer(data: AnswerActionData & { id: number }) {
|
||||||
return await deleteAnswerVariant(data.surveyId, data.questionId, data.id);
|
return await deleteAnswerVariant(data.id);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
export const SurveyPage: React.FC = () => {
|
export const SurveyPage: React.FC = () => {
|
||||||
// const [survey, setSurvey] = useState<ISurvey | null>(null);
|
|
||||||
const [questions, setQuestions] = useState<Question[]>([]);
|
const [questions, setQuestions] = useState<Question[]>([]);
|
||||||
const [loading, setLoading] = useState(true);
|
const [loading, setLoading] = useState(true);
|
||||||
const [error, setError] = useState<string | null>(null);
|
const [error, setError] = useState<string | null>(null);
|
||||||
// const { surveyId } = useParams<{ surveyId: string }>();
|
|
||||||
|
|
||||||
const [description, setDescription] = useState('');
|
const [description, setDescription] = useState('');
|
||||||
const [title, setTitle] = useState('');
|
const [title, setTitle] = useState('');
|
||||||
|
|
@ -173,7 +171,6 @@ export const SurveyPage: React.FC = () => {
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
|
||||||
// const id = parseInt(survey.id);
|
|
||||||
const id = survey.id;
|
const id = survey.id;
|
||||||
if (isNaN(id)) {
|
if (isNaN(id)) {
|
||||||
console.error('Invalid survey ID');
|
console.error('Invalid survey ID');
|
||||||
|
|
@ -183,7 +180,6 @@ export const SurveyPage: React.FC = () => {
|
||||||
const fetchData = async () => {
|
const fetchData = async () => {
|
||||||
try {
|
try {
|
||||||
setLoading(true);
|
setLoading(true);
|
||||||
// const surveyData = await getSurveyById(id);
|
|
||||||
setSurvey(survey);
|
setSurvey(survey);
|
||||||
setTitle(survey.title);
|
setTitle(survey.title);
|
||||||
setDescription(survey.description);
|
setDescription(survey.description);
|
||||||
|
|
@ -218,7 +214,6 @@ export const SurveyPage: React.FC = () => {
|
||||||
|
|
||||||
try {
|
try {
|
||||||
setError(null);
|
setError(null);
|
||||||
// const id = parseInt(survey.id);
|
|
||||||
const id = survey.id;
|
const id = survey.id;
|
||||||
const actionQueue = new ActionQueue();
|
const actionQueue = new ActionQueue();
|
||||||
|
|
||||||
|
|
|
||||||
|
|
@ -3,7 +3,7 @@
|
||||||
|
|
||||||
.timeEvent{
|
.timeEvent{
|
||||||
width: 44%;
|
width: 44%;
|
||||||
padding: 17px 25px 48px 20px;
|
padding: 8px 25px 28px 20px;
|
||||||
background-color: #FFFFFF;
|
background-color: #FFFFFF;
|
||||||
border-radius: 6px;
|
border-radius: 6px;
|
||||||
margin-bottom: 34px;
|
margin-bottom: 34px;
|
||||||
|
|
@ -11,7 +11,7 @@
|
||||||
|
|
||||||
.title{
|
.title{
|
||||||
font-weight: 600;
|
font-weight: 600;
|
||||||
font-size: 24px;
|
font-size: 18px;
|
||||||
margin-bottom: 23px;
|
margin-bottom: 23px;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
@ -24,8 +24,9 @@
|
||||||
.inputDate{
|
.inputDate{
|
||||||
width: fit-content;
|
width: fit-content;
|
||||||
border: 3px solid #007AFF26;
|
border: 3px solid #007AFF26;
|
||||||
padding: 12px 40px 12px 21px;
|
padding: 8px 25px 8px 21px;
|
||||||
font-size: 20px;
|
font-family: inherit;
|
||||||
|
font-size: 15px;
|
||||||
font-weight: 400;
|
font-weight: 400;
|
||||||
border-radius: 3px;
|
border-radius: 3px;
|
||||||
}
|
}
|
||||||
|
|
@ -33,8 +34,8 @@
|
||||||
.inputTime{
|
.inputTime{
|
||||||
width: fit-content;
|
width: fit-content;
|
||||||
border: 3px solid #007AFF26;
|
border: 3px solid #007AFF26;
|
||||||
padding: 12px 22px;
|
padding: 8px 25px;
|
||||||
font-size: 20px;
|
font-size: 15px;
|
||||||
font-weight: 400;
|
font-weight: 400;
|
||||||
border-radius: 3px;
|
border-radius: 3px;
|
||||||
}
|
}
|
||||||
|
|
|
||||||
|
|
@ -1,6 +1,7 @@
|
||||||
/*TypeDropdown.module.css*/
|
/*TypeDropdown.module.css*/
|
||||||
|
|
||||||
.dropdownContainer {
|
.dropdownContainer {
|
||||||
|
margin-top: -5px;
|
||||||
width: 23%;
|
width: 23%;
|
||||||
position: relative;
|
position: relative;
|
||||||
display: inline-block;
|
display: inline-block;
|
||||||
|
|
@ -12,7 +13,7 @@
|
||||||
border: 1px solid #000000;
|
border: 1px solid #000000;
|
||||||
border-radius: 19px;
|
border-radius: 19px;
|
||||||
padding: 9px 7px 7px 10px;
|
padding: 9px 7px 7px 10px;
|
||||||
font-size: 16px;
|
font-size: 13px;
|
||||||
font-weight: 500;
|
font-weight: 500;
|
||||||
cursor: pointer;
|
cursor: pointer;
|
||||||
display: flex;
|
display: flex;
|
||||||
|
|
@ -25,7 +26,6 @@
|
||||||
|
|
||||||
.selectedTypeIcon {
|
.selectedTypeIcon {
|
||||||
margin-right: 4px;
|
margin-right: 4px;
|
||||||
width: 22px;
|
|
||||||
}
|
}
|
||||||
|
|
||||||
.dropdownArrow {
|
.dropdownArrow {
|
||||||
|
|
@ -33,7 +33,7 @@
|
||||||
}
|
}
|
||||||
|
|
||||||
.dropdownList {
|
.dropdownList {
|
||||||
width: 70%;
|
width: 85%;
|
||||||
margin-top: 11px;
|
margin-top: 11px;
|
||||||
position: absolute;
|
position: absolute;
|
||||||
background-color: #fff;
|
background-color: #fff;
|
||||||
|
|
@ -58,13 +58,11 @@
|
||||||
|
|
||||||
.dropdownItemIcon {
|
.dropdownItemIcon {
|
||||||
margin-right: 5px;
|
margin-right: 5px;
|
||||||
width: 24px;
|
|
||||||
}
|
}
|
||||||
|
|
||||||
.selectedTypeIcon,
|
.selectedTypeIcon,
|
||||||
.dropdownItemIcon {
|
.dropdownItemIcon {
|
||||||
width: 20px;
|
width: 17px;
|
||||||
height: 20px;
|
|
||||||
margin-right: 5px;
|
margin-right: 5px;
|
||||||
vertical-align: middle;
|
vertical-align: middle;
|
||||||
}
|
}
|
||||||
|
|
|
||||||
37
SurveyFrontend/src/context/SurveyContext.tsx
Normal file
37
SurveyFrontend/src/context/SurveyContext.tsx
Normal file
|
|
@ -0,0 +1,37 @@
|
||||||
|
import React, {createContext, useContext, useState} from "react";
|
||||||
|
import { INewSurvey } from "../api/SurveyApi.ts";
|
||||||
|
import { Question } from "../components/QuestionsList/QuestionsList.tsx";
|
||||||
|
|
||||||
|
interface SurveyContextType {
|
||||||
|
tempSurvey: INewSurvey & { questions: Question[] };
|
||||||
|
setTempSurvey: (survey: INewSurvey & { questions: Question[] }) => void;
|
||||||
|
clearTempSurvey: () => void;
|
||||||
|
}
|
||||||
|
|
||||||
|
const SurveyContext = createContext<SurveyContextType | undefined>(undefined);
|
||||||
|
|
||||||
|
export const SurveyProvider: React.FC<{ children: React.ReactNode }> = ({ children }) => {
|
||||||
|
const [tempSurvey, setTempSurvey] = useState<INewSurvey & { questions: Question[] }>({
|
||||||
|
title: "",
|
||||||
|
description: "",
|
||||||
|
questions: [],
|
||||||
|
});
|
||||||
|
|
||||||
|
const clearTempSurvey = () => {
|
||||||
|
setTempSurvey({ title: "", description: "", questions: [] });
|
||||||
|
};
|
||||||
|
|
||||||
|
return (
|
||||||
|
<SurveyContext.Provider value={{ tempSurvey, setTempSurvey, clearTempSurvey }}>
|
||||||
|
{children}
|
||||||
|
</SurveyContext.Provider>
|
||||||
|
);
|
||||||
|
};
|
||||||
|
|
||||||
|
export const useSurveyContext = () => {
|
||||||
|
const context = useContext(SurveyContext);
|
||||||
|
if (!context) {
|
||||||
|
throw new Error("useSurveyContext must be used within a SurveyProvider");
|
||||||
|
}
|
||||||
|
return context;
|
||||||
|
};
|
||||||
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/');
|
||||||
|
};
|
||||||
|
|
@ -2,11 +2,14 @@
|
||||||
width: 100%;
|
width: 100%;
|
||||||
min-height: 100vh;
|
min-height: 100vh;
|
||||||
background-color: #F6F6F6;
|
background-color: #F6F6F6;
|
||||||
padding: 61.5px 0;
|
padding: 50px 0;
|
||||||
}
|
}
|
||||||
|
|
||||||
.pageLogin{
|
.pageLogin{
|
||||||
|
min-height: 80vh;
|
||||||
width: 100%;
|
width: 100%;
|
||||||
background-color: #F6F6F6;
|
background-color: #F6F6F6;
|
||||||
padding: 157px 0;
|
padding: 100px 0;
|
||||||
|
display: flex;
|
||||||
|
justify-content: center;
|
||||||
}
|
}
|
||||||
|
|
@ -2,6 +2,7 @@ import styles from './AuthForm.module.css';
|
||||||
import LoginForm from "../../components/LoginForm/LoginForm.tsx";
|
import LoginForm from "../../components/LoginForm/LoginForm.tsx";
|
||||||
import RegisterForm from "../../components/RegisterForm/RegisterForm.tsx";
|
import RegisterForm from "../../components/RegisterForm/RegisterForm.tsx";
|
||||||
import {useLocation} from "react-router-dom";
|
import {useLocation} from "react-router-dom";
|
||||||
|
import {useEffect} from "react";
|
||||||
|
|
||||||
|
|
||||||
const AuthForm = () => {
|
const AuthForm = () => {
|
||||||
|
|
@ -9,6 +10,22 @@ const AuthForm = () => {
|
||||||
const isLoginPage = location.pathname === '/login';
|
const isLoginPage = location.pathname === '/login';
|
||||||
const isRegisterPage = location.pathname === '/register';
|
const isRegisterPage = location.pathname === '/register';
|
||||||
|
|
||||||
|
useEffect(() => {
|
||||||
|
if (isLoginPage || isRegisterPage) {
|
||||||
|
window.history.pushState(null, "", window.location.href);
|
||||||
|
|
||||||
|
const handlePopState = () => {
|
||||||
|
window.history.pushState(null, "", window.location.href);
|
||||||
|
};
|
||||||
|
|
||||||
|
window.addEventListener("popstate", handlePopState);
|
||||||
|
|
||||||
|
return () => {
|
||||||
|
window.removeEventListener("popstate", handlePopState);
|
||||||
|
};
|
||||||
|
}
|
||||||
|
}, [isLoginPage, isRegisterPage]);
|
||||||
|
|
||||||
let content;
|
let content;
|
||||||
if (isLoginPage) {
|
if (isLoginPage) {
|
||||||
content = <LoginForm />;
|
content = <LoginForm />;
|
||||||
|
|
|
||||||
|
|
@ -1,11 +1,9 @@
|
||||||
import Header from "../../components/Header/Header.tsx";
|
|
||||||
import styles from './CompleteSurvey.module.css'
|
import styles from './CompleteSurvey.module.css'
|
||||||
import CompletingSurvey from "../../components/CompletingSurvey/CompletingSurvey.tsx";
|
import CompletingSurvey from "../../components/CompletingSurvey/CompletingSurvey.tsx";
|
||||||
|
|
||||||
export const CompleteSurvey = () => {
|
export const CompleteSurvey = () => {
|
||||||
return(
|
return(
|
||||||
<div className={styles.layout}>
|
<div className={styles.layout}>
|
||||||
<Header/>
|
|
||||||
<CompletingSurvey/>
|
<CompletingSurvey/>
|
||||||
</div>
|
</div>
|
||||||
)
|
)
|
||||||
|
|
|
||||||
Loading…
Add table
Add a link
Reference in a new issue