Merge branch 'spliting_components' into 'unstable'
add components frontend See merge request internship-2025/survey-webapp/survey-webapp!1
This commit is contained in:
commit
a27087d681
40 changed files with 966 additions and 0 deletions
9
SurveyFrontend/src/components/Account/Account.module.css
Normal file
9
SurveyFrontend/src/components/Account/Account.module.css
Normal file
|
|
@ -0,0 +1,9 @@
|
|||
/*Account.module.css*/
|
||||
|
||||
.account {
|
||||
margin: 0;
|
||||
padding: 0;
|
||||
width: 52px;
|
||||
height: 52px;
|
||||
margin: 31px 39px 25px 0;
|
||||
}
|
||||
16
SurveyFrontend/src/components/Account/Account.tsx
Normal file
16
SurveyFrontend/src/components/Account/Account.tsx
Normal file
|
|
@ -0,0 +1,16 @@
|
|||
import React from 'react';
|
||||
import styles from './Account.module.css'
|
||||
|
||||
interface AccountProps {
|
||||
href: string
|
||||
}
|
||||
|
||||
const Account: React.FC<AccountProps> = ({href}) => {
|
||||
return (
|
||||
<a className={styles.account} href={href}>
|
||||
<img src='../../../public/account.svg' alt='Личный кабинет'/>
|
||||
</a>
|
||||
);
|
||||
};
|
||||
|
||||
export default Account;
|
||||
|
|
@ -0,0 +1,16 @@
|
|||
/*AddAnswerButton.module.css*/
|
||||
|
||||
.answerButton {
|
||||
display: flex;
|
||||
gap: 10px;
|
||||
align-items: center;
|
||||
border: none;
|
||||
background-color: white;
|
||||
color: #3788D6;
|
||||
font-size: 18px;
|
||||
font-weight: 500;
|
||||
}
|
||||
|
||||
.addAnswerImg{
|
||||
vertical-align: middle;
|
||||
}
|
||||
|
|
@ -0,0 +1,17 @@
|
|||
import React from "react";
|
||||
import styles from './AddAnswerButton.module.css'
|
||||
|
||||
interface AddAnswerButtonProps {
|
||||
onClick(): void;
|
||||
}
|
||||
|
||||
const AddAnswerButton: React.FC<AddAnswerButtonProps> = ({onClick}) => {
|
||||
return (
|
||||
<button className={styles.answerButton} onClick={onClick}>
|
||||
Добавить вариант ответа
|
||||
<img className={styles.addAnswerImg} src='../../../public/add_answer.svg' alt=''/>
|
||||
</button>
|
||||
);
|
||||
};
|
||||
|
||||
export default AddAnswerButton;
|
||||
|
|
@ -0,0 +1,18 @@
|
|||
/*AddQuestionButton.module.css*/
|
||||
|
||||
.questionButton{
|
||||
background-color: #F6F6F6;
|
||||
font-size: 24px;
|
||||
font-weight: 600;
|
||||
border: none;
|
||||
margin: 104px auto 80px;
|
||||
display: flex;
|
||||
flex-direction: column;
|
||||
}
|
||||
|
||||
.questionButtonImg{
|
||||
vertical-align: middle;
|
||||
margin: 0 auto;
|
||||
margin-bottom: 16px;
|
||||
width: 54px;
|
||||
}
|
||||
|
|
@ -0,0 +1,17 @@
|
|||
import React from "react";
|
||||
import styles from './AddQuestionButton.module.css'
|
||||
|
||||
interface AddQuestionButtonProps {
|
||||
onClick: () => void;
|
||||
}
|
||||
|
||||
const AddQuestionButton: React.FC<AddQuestionButtonProps> = ({onClick}) => {
|
||||
return (
|
||||
<button className={styles.questionButton} onClick={onClick}>
|
||||
<img className={styles.questionButtonImg} src='../../../public/add_question.svg' alt=''/>
|
||||
Добавить вопрос
|
||||
</button>
|
||||
);
|
||||
};
|
||||
|
||||
export default AddQuestionButton;
|
||||
|
|
@ -0,0 +1,23 @@
|
|||
/*AnswerOption.module.css*/
|
||||
|
||||
.answer{
|
||||
display: flex;
|
||||
gap: 10px;
|
||||
margin-bottom: 17px;
|
||||
}
|
||||
|
||||
.textAnswer{
|
||||
font-size: 18px;
|
||||
font-weight: 500;
|
||||
align-items: center;
|
||||
}
|
||||
|
||||
.answerIcon{
|
||||
vertical-align: middle;
|
||||
}
|
||||
|
||||
.answerInput{
|
||||
outline: none;
|
||||
border: none;
|
||||
resize: none;
|
||||
}
|
||||
72
SurveyFrontend/src/components/AnswerOption/AnswerOption.tsx
Normal file
72
SurveyFrontend/src/components/AnswerOption/AnswerOption.tsx
Normal file
|
|
@ -0,0 +1,72 @@
|
|||
import React, {useState, useRef, useEffect} from "react";
|
||||
import styles from'./AnswerOption.module.css';
|
||||
|
||||
interface AnswerOptionProps{
|
||||
src: string;
|
||||
index: number;
|
||||
value: string;
|
||||
onChange: (value: string) => void;
|
||||
}
|
||||
|
||||
const AnswerOption: React.FC<AnswerOptionProps> = ({src, index, value, onChange}) => {
|
||||
const [currentValue, setCurrentValue] = useState(value);
|
||||
const [isEditing, setIsEditing] = useState(false); //редактируется ли сейчас
|
||||
|
||||
const textAreaRef = useRef<HTMLTextAreaElement>(null);
|
||||
|
||||
useEffect(() => {
|
||||
setCurrentValue(value);
|
||||
}, [value]);
|
||||
|
||||
const handleSpanClick = () => {
|
||||
setIsEditing(true);
|
||||
}
|
||||
|
||||
const handleTextareaChange = (event: React.ChangeEvent<HTMLTextAreaElement>) => {
|
||||
setCurrentValue(event.target.value);
|
||||
};
|
||||
|
||||
const handleSave = () => {
|
||||
setIsEditing(false);
|
||||
onChange(currentValue); // Отправляем измененное значение родителю
|
||||
};
|
||||
|
||||
const handleKeyDown = (event: React.KeyboardEvent<HTMLTextAreaElement>) => {
|
||||
if (event.key === "Enter") {
|
||||
event.preventDefault(); // Предотвращаем перенос строки в textarea
|
||||
handleSave();
|
||||
}
|
||||
};
|
||||
|
||||
const handleBlur = () => {
|
||||
handleSave();
|
||||
};
|
||||
|
||||
useEffect(() => {
|
||||
if (isEditing && textAreaRef.current) {
|
||||
textAreaRef.current.focus();
|
||||
}
|
||||
}, [isEditing]);
|
||||
|
||||
return (
|
||||
<div className={styles.answer}>
|
||||
<img className={styles.answerIcon} src={src} alt="" />
|
||||
{isEditing ? (
|
||||
<textarea className={styles.answerInput}
|
||||
ref={textAreaRef}
|
||||
value={currentValue}
|
||||
onChange={handleTextareaChange}
|
||||
onKeyDown={handleKeyDown}
|
||||
onBlur={handleBlur}
|
||||
placeholder={`Ответ ${index}`}
|
||||
/>
|
||||
) : (
|
||||
<span className={styles.textAnswer} onClick={handleSpanClick}>
|
||||
{currentValue || `Ответ ${index}`}
|
||||
</span>
|
||||
)}
|
||||
</div>
|
||||
);
|
||||
};
|
||||
|
||||
export default AnswerOption;
|
||||
9
SurveyFrontend/src/components/Header/Header.module.css
Normal file
9
SurveyFrontend/src/components/Header/Header.module.css
Normal file
|
|
@ -0,0 +1,9 @@
|
|||
/*Header.module.css*/
|
||||
|
||||
.header{
|
||||
margin: 0;
|
||||
padding: 0;
|
||||
width: 100%;
|
||||
display: flex;
|
||||
/*justify-content: space-between;*/
|
||||
}
|
||||
28
SurveyFrontend/src/components/Header/Header.tsx
Normal file
28
SurveyFrontend/src/components/Header/Header.tsx
Normal file
|
|
@ -0,0 +1,28 @@
|
|||
import React, {useState} from "react";
|
||||
import Logo from "../Logo/Logo.tsx";
|
||||
import Account from "../Account/Account.tsx";
|
||||
import styles from './Header.module.css'
|
||||
import SurveyPagesList from "../SurveyPagesList/SurveyPagesList.tsx";
|
||||
|
||||
interface HeaderProps {}
|
||||
|
||||
const Header: React.FC<HeaderProps> = () => {
|
||||
const [activePage, setActivePage] = useState('Создать опрос');
|
||||
|
||||
const handlePageClick = (name: string)=> {
|
||||
setActivePage(name);
|
||||
}
|
||||
|
||||
return (
|
||||
<div className={styles.header}>
|
||||
<Logo href='' />
|
||||
<SurveyPagesList
|
||||
activePage={activePage}
|
||||
onPageClick = {handlePageClick}
|
||||
/>
|
||||
<Account href='' />
|
||||
</div>
|
||||
);
|
||||
};
|
||||
|
||||
export default Header;
|
||||
9
SurveyFrontend/src/components/Logo/Logo.module.css
Normal file
9
SurveyFrontend/src/components/Logo/Logo.module.css
Normal file
|
|
@ -0,0 +1,9 @@
|
|||
/*Logo.module.css*/
|
||||
|
||||
.logo {
|
||||
margin: 0;
|
||||
padding: 0;
|
||||
height: 52px;
|
||||
width: 52px;
|
||||
margin: 31px auto 25px 40px;
|
||||
}
|
||||
16
SurveyFrontend/src/components/Logo/Logo.tsx
Normal file
16
SurveyFrontend/src/components/Logo/Logo.tsx
Normal file
|
|
@ -0,0 +1,16 @@
|
|||
import React from "react";
|
||||
import styles from './Logo.module.css'
|
||||
|
||||
interface LogoProps {
|
||||
href: string;
|
||||
}
|
||||
|
||||
const Logo: React.FC<LogoProps> = ({href}) => {
|
||||
return (
|
||||
<a className={styles.logo} href={href}>
|
||||
<img src='../../../public/logo.svg' alt='Логотип'/>
|
||||
</a>
|
||||
);
|
||||
};
|
||||
|
||||
export default Logo;
|
||||
|
|
@ -0,0 +1,5 @@
|
|||
.mainPage{
|
||||
width: 100%;
|
||||
display: flex;
|
||||
background-color: #F6F6F6;
|
||||
}
|
||||
|
|
@ -0,0 +1,24 @@
|
|||
import Navigation from "../Navigation/Navigation.tsx";
|
||||
import React, {useState} from "react";
|
||||
import styles from './MainComponent.module.css'
|
||||
import Survey from "../Survey/Survey.tsx";
|
||||
|
||||
const MainComponent: React.FC = () => {
|
||||
const [activePage, setActivePage] = useState('Вопросы');
|
||||
|
||||
const handleNavigationClick = (title: string) => {
|
||||
setActivePage(title);
|
||||
}
|
||||
|
||||
return (
|
||||
<main className={styles.mainPage}>
|
||||
<Navigation
|
||||
activePage={activePage}
|
||||
onNavigationClick={handleNavigationClick}
|
||||
/>
|
||||
<Survey />
|
||||
</main>
|
||||
)
|
||||
}
|
||||
|
||||
export default MainComponent;
|
||||
|
|
@ -0,0 +1,12 @@
|
|||
/*Navigation.module.css*/
|
||||
|
||||
.nav{
|
||||
margin: 34px 0 48px 40px;
|
||||
background-color: white;
|
||||
border-radius: 20px;
|
||||
}
|
||||
|
||||
.navList{
|
||||
list-style: none;
|
||||
padding: 52px 57px 70px 36px;
|
||||
}
|
||||
34
SurveyFrontend/src/components/Navigation/Navigation.tsx
Normal file
34
SurveyFrontend/src/components/Navigation/Navigation.tsx
Normal file
|
|
@ -0,0 +1,34 @@
|
|||
import React from 'react'
|
||||
import styles from './Navigation.module.css'
|
||||
import NavigationItem from "../NavigationItem/NavigationItem.tsx";
|
||||
import SaveButton from "../SaveButton/SaveButton.tsx";
|
||||
|
||||
interface NavigationProps {
|
||||
onNavigationClick: (title: string) => void;
|
||||
activePage: string
|
||||
}
|
||||
|
||||
const Navigation: React.FC<NavigationProps> = ({onNavigationClick, activePage}) => {
|
||||
const items: string[] = ['Вопросы', 'Настройки', 'Результаты']
|
||||
|
||||
return (
|
||||
<div>
|
||||
<nav className={styles.nav}>
|
||||
<ul className={styles.navList}>
|
||||
{items.map(item => (
|
||||
<NavigationItem
|
||||
key={item}
|
||||
title={item}
|
||||
isActive={activePage === item}
|
||||
onClick={() => onNavigationClick(item)}
|
||||
/>
|
||||
))}
|
||||
</ul>
|
||||
</nav>
|
||||
|
||||
<SaveButton />
|
||||
</div>
|
||||
);
|
||||
};
|
||||
|
||||
export default Navigation;
|
||||
|
|
@ -0,0 +1,17 @@
|
|||
/*NavigationItem.module.css*/
|
||||
|
||||
.navItem{
|
||||
font-weight: 600;
|
||||
font-size: 24px;
|
||||
color: #AFAFAF;
|
||||
margin-bottom: 42px;
|
||||
}
|
||||
|
||||
.active{
|
||||
text-decoration: underline 2px #556FB7;
|
||||
color: #000000;;
|
||||
}
|
||||
|
||||
.navItem:last-child{
|
||||
margin-bottom: 0;
|
||||
}
|
||||
|
|
@ -0,0 +1,20 @@
|
|||
import React from 'react'
|
||||
import styles from './NavigationItem.module.css'
|
||||
|
||||
interface NavigationItemProps{
|
||||
title: string;
|
||||
onClick(): void;
|
||||
isActive: boolean; //Дописать для активной ссылки, для класса
|
||||
}
|
||||
|
||||
const NavigationItem: React.FC<NavigationItemProps> = ({title, onClick, isActive}) => {
|
||||
return (
|
||||
<li className={styles.navItem}>
|
||||
<a className={`${styles.page} ${isActive ? styles.active : ''}`} onClick={onClick}>
|
||||
{title}
|
||||
</a>
|
||||
</li>
|
||||
);
|
||||
};
|
||||
|
||||
export default NavigationItem;
|
||||
|
|
@ -0,0 +1,36 @@
|
|||
.pagesSurveyItem{
|
||||
font-size: 24px;
|
||||
font-weight: 600;
|
||||
color: #2A6DAE;
|
||||
}
|
||||
|
||||
.active{
|
||||
color: #000000;
|
||||
text-decoration: underline;
|
||||
text-decoration-color: #3881C8;
|
||||
|
||||
}
|
||||
|
||||
/*.pagesSurveyItem {*/
|
||||
/* font-size: 24px;*/
|
||||
/* font-weight: 600;*/
|
||||
/* color: #2A6DAE;*/
|
||||
/* position: relative; !* Necessary for positioning the underline *!*/
|
||||
/* display: inline-block; !* Makes the width only as wide as the content *!*/
|
||||
/*}*/
|
||||
|
||||
/*.active {*/
|
||||
/* color: #000000;*/
|
||||
/*}*/
|
||||
|
||||
/*.active::after {*/
|
||||
/* content: "";*/
|
||||
/* display: block; !* Makes it a block element for width/height control *!*/
|
||||
/* width: 96px;*/
|
||||
/* height: 2px; !* Adjust as needed for the thickness of the underline *!*/
|
||||
/* background-color: #3881C8;*/
|
||||
/* position: absolute;*/
|
||||
/* left: 50%; !* Center horizontally *!*/
|
||||
/* transform: translateX(-50%); !* Corrects the centering *!*/
|
||||
/* bottom: -5px; !* Position 5px below the text *!*/
|
||||
/*}*/
|
||||
20
SurveyFrontend/src/components/PageSurvey/PageSurvey.tsx
Normal file
20
SurveyFrontend/src/components/PageSurvey/PageSurvey.tsx
Normal file
|
|
@ -0,0 +1,20 @@
|
|||
import React from 'react';
|
||||
import styles from './PageSurvey.module.css';
|
||||
|
||||
interface PageSurveyProps{
|
||||
name: string;
|
||||
isActive: boolean;
|
||||
onClick(): void;
|
||||
}
|
||||
|
||||
const PageSurvey: React.FC<PageSurveyProps> = ({name, isActive, onClick}) => {
|
||||
return (
|
||||
<li className={styles.pagesSurveyItem}>
|
||||
<a className={`${styles.pageSurvey} ${isActive ? styles.active : ''}`} onClick={onClick}>
|
||||
{name}
|
||||
</a>
|
||||
</li>
|
||||
);
|
||||
};
|
||||
|
||||
export default PageSurvey;
|
||||
|
|
@ -0,0 +1,18 @@
|
|||
/*QuestionItem.module.css*/
|
||||
|
||||
.questionCard{
|
||||
width: 100%;
|
||||
background-color: white;
|
||||
display: flex;
|
||||
margin-bottom: 34px;
|
||||
padding: 27px 29px 26px 36px;
|
||||
border-radius: 14px;
|
||||
}
|
||||
|
||||
.questionCard:last-child{
|
||||
margin-bottom: 0;
|
||||
}
|
||||
|
||||
.questionContainer{
|
||||
|
||||
}
|
||||
66
SurveyFrontend/src/components/QuestionItem/QuestionItem.tsx
Normal file
66
SurveyFrontend/src/components/QuestionItem/QuestionItem.tsx
Normal file
|
|
@ -0,0 +1,66 @@
|
|||
import React, {useState} from "react";
|
||||
import AnswerOption from '../AnswerOption/AnswerOption';
|
||||
import AddAnswerButton from "../AddAnswerButton/AddAnswerButton.tsx";
|
||||
import TypeDropdown from "../TypeDropdown/TypeDropdown.tsx";
|
||||
import styles from './QuestionItem.module.css'
|
||||
|
||||
import singleChoiceIcon from '../../../public/radio_button_checked.svg'
|
||||
import multiplyChoiceIcon from '../../../public/check_box.svg'
|
||||
|
||||
interface QuestionItemProps {
|
||||
indexQuestion: number;
|
||||
initialTextQuestion?: string;
|
||||
}
|
||||
|
||||
const QuestionItem: React.FC<QuestionItemProps> = ({indexQuestion, initialTextQuestion = `Вопрос ${indexQuestion}`}) => {
|
||||
const [answerOption, setAnswerOption] = useState(['']);
|
||||
const [questionType] = useState('single');
|
||||
const [textQuestion, setTextQuestion] = useState(initialTextQuestion);
|
||||
|
||||
const handleAddAnswer = () => {
|
||||
setAnswerOption([...answerOption, '']);
|
||||
};
|
||||
|
||||
// const handleTypeChange = (type: string) => {
|
||||
// setQuestionType(type);
|
||||
// }
|
||||
|
||||
const handleAnswerChange = (index: number, value: string) => {
|
||||
const newAnswerOption = [...answerOption];
|
||||
newAnswerOption[index] = value;
|
||||
setAnswerOption(newAnswerOption);
|
||||
}
|
||||
|
||||
const handleQuestionChange = (event: React.ChangeEvent<HTMLTextAreaElement>) => {
|
||||
setTextQuestion(event.target.value);
|
||||
};
|
||||
|
||||
return (
|
||||
<div className={styles.questionCard}>
|
||||
<div className={styles.questionContainer}>
|
||||
<h2>
|
||||
<textarea
|
||||
value={textQuestion}
|
||||
onChange={handleQuestionChange}
|
||||
/>
|
||||
</h2>
|
||||
{answerOption.map((answerText, index) => (
|
||||
<AnswerOption
|
||||
key={index}
|
||||
index={index + 1} // Индекс ответа
|
||||
value={answerText}
|
||||
src={questionType === "single" ? singleChoiceIcon : multiplyChoiceIcon}
|
||||
onChange={(value) => handleAnswerChange(index, value)}
|
||||
/>
|
||||
))}
|
||||
|
||||
<AddAnswerButton
|
||||
onClick={handleAddAnswer}
|
||||
/>
|
||||
</div>
|
||||
<TypeDropdown/>
|
||||
</div>
|
||||
);
|
||||
}
|
||||
|
||||
export default QuestionItem;
|
||||
|
|
@ -0,0 +1,2 @@
|
|||
/*QuestionsList.module.css*/
|
||||
|
||||
|
|
@ -0,0 +1,38 @@
|
|||
import React, {useState} from "react";
|
||||
import QuestionItem from "../QuestionItem/QuestionItem.tsx";
|
||||
import AddQuestionButton from "../AddQuestionButton/AddQuestionButton.tsx";
|
||||
|
||||
interface QuestionsListProps {}
|
||||
|
||||
interface Question {
|
||||
id: number;
|
||||
}
|
||||
|
||||
const QuestionsList: React.FC<QuestionsListProps> = () => {
|
||||
const [questions, setQuestions] = useState<Question[]>([
|
||||
{id: 1},
|
||||
]);
|
||||
|
||||
const handleAddQuestion = () => {
|
||||
// Find the highest ID in the current questions list
|
||||
const maxId = questions.reduce((max, question) => Math.max(max, question.id), 0);
|
||||
const newQuestion: Question = {
|
||||
id: maxId + 1, // Increment the ID
|
||||
};
|
||||
setQuestions([...questions, newQuestion]); // Add the new question to the state
|
||||
};
|
||||
|
||||
return (
|
||||
<>
|
||||
{questions.map((question, index) => (
|
||||
<QuestionItem
|
||||
key={question.id}
|
||||
indexQuestion={index + 1}
|
||||
/>
|
||||
))}
|
||||
<AddQuestionButton onClick={handleAddQuestion} />
|
||||
</>
|
||||
);
|
||||
};
|
||||
|
||||
export default QuestionsList;
|
||||
|
|
@ -0,0 +1,15 @@
|
|||
/*SaveButton.module.css*/
|
||||
|
||||
.createSurveyButton {
|
||||
/*width: 15%;*/
|
||||
margin-left: 40px;
|
||||
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);
|
||||
}
|
||||
16
SurveyFrontend/src/components/SaveButton/SaveButton.tsx
Normal file
16
SurveyFrontend/src/components/SaveButton/SaveButton.tsx
Normal file
|
|
@ -0,0 +1,16 @@
|
|||
import React from 'react'
|
||||
import styles from './SaveButton.module.css'
|
||||
|
||||
interface CreateSurveyButtonProps {
|
||||
// onClick(): void;
|
||||
}
|
||||
|
||||
const SaveButton: React.FC<CreateSurveyButtonProps> = ({}) => {
|
||||
return (
|
||||
<button className={styles.createSurveyButton}>
|
||||
Сохранить
|
||||
</button>
|
||||
);
|
||||
}
|
||||
|
||||
export default SaveButton;
|
||||
6
SurveyFrontend/src/components/Survey/Survey.module.css
Normal file
6
SurveyFrontend/src/components/Survey/Survey.module.css
Normal file
|
|
@ -0,0 +1,6 @@
|
|||
/*Survey.module.css*/
|
||||
|
||||
.survey{
|
||||
width: 63%;
|
||||
margin-left: 8.9%;
|
||||
}
|
||||
15
SurveyFrontend/src/components/Survey/Survey.tsx
Normal file
15
SurveyFrontend/src/components/Survey/Survey.tsx
Normal file
|
|
@ -0,0 +1,15 @@
|
|||
import React from "react";
|
||||
import SurveyInfo from "../SurveyInfo/SurveyInfo.tsx";
|
||||
import QuestionsList from "../QuestionsList/QuestionsList.tsx";
|
||||
import styles from './Survey.module.css'
|
||||
|
||||
const Survey: React.FC = () => {
|
||||
return (
|
||||
<div className={styles.survey}>
|
||||
<SurveyInfo />
|
||||
<QuestionsList />
|
||||
</div>
|
||||
);
|
||||
}
|
||||
|
||||
export default Survey;
|
||||
|
|
@ -0,0 +1,73 @@
|
|||
/*SurveyInfo.module.css*/
|
||||
|
||||
.blockInfo{
|
||||
background-color: #ffffff;
|
||||
/*margin: 0;*/
|
||||
padding: 0;
|
||||
width: 100%;
|
||||
margin-top: 34px;
|
||||
margin-bottom: 49px;
|
||||
border-radius: 14px;
|
||||
height: 191px;
|
||||
}
|
||||
|
||||
.info{
|
||||
display: block;
|
||||
padding: 35px 0;
|
||||
}
|
||||
|
||||
.titleSurvey{
|
||||
resize: none;
|
||||
text-align: center;
|
||||
margin: 0;
|
||||
padding: 0 20px;
|
||||
font-size: 40px;
|
||||
font-weight: 600;
|
||||
margin-bottom: 23px;
|
||||
}
|
||||
|
||||
.textareaTitle{
|
||||
resize: none;
|
||||
text-align: center;
|
||||
font-size: 40px;
|
||||
font-weight: 600;
|
||||
margin: 0;
|
||||
padding: 0;
|
||||
border: none;
|
||||
margin-bottom: 23px;
|
||||
}
|
||||
|
||||
.description{
|
||||
resize: none;
|
||||
text-align: center;
|
||||
margin: 0;
|
||||
padding: 0 20px;
|
||||
font-size: 24px;
|
||||
font-weight: 500;
|
||||
}
|
||||
|
||||
.textareaDescrip{
|
||||
resize: none;
|
||||
text-align: center;
|
||||
margin: 0;
|
||||
font-size: 22px;
|
||||
}
|
||||
|
||||
.descripButton{
|
||||
border: none;
|
||||
background-color: #ffffff;
|
||||
display: block;
|
||||
margin: 0 auto;
|
||||
}
|
||||
|
||||
.descButtonImg{
|
||||
width: 28px;
|
||||
}
|
||||
|
||||
.textButton{
|
||||
vertical-align: center;
|
||||
font-size: 24px;
|
||||
font-weight: 500;
|
||||
color: #7D7983;
|
||||
padding: 10px;
|
||||
}
|
||||
87
SurveyFrontend/src/components/SurveyInfo/SurveyInfo.tsx
Normal file
87
SurveyFrontend/src/components/SurveyInfo/SurveyInfo.tsx
Normal file
|
|
@ -0,0 +1,87 @@
|
|||
import React, {useState} from "react";
|
||||
import styles from './SurveyInfo.module.css'
|
||||
|
||||
interface SurveyInfoProps {}
|
||||
|
||||
const SurveyInfo: React.FC<SurveyInfoProps> = () => {
|
||||
const [descriptionSurvey, setDescriptionSurvey] = useState('');
|
||||
const [titleSurvey, setTitleSurvey] = useState('Название опроса');
|
||||
const [showDescriptionField, setShowDescriptionField] = useState(false);
|
||||
const [showNewTitleField, setShowNewTitleField] = useState(false);
|
||||
|
||||
|
||||
const handleDescriptionChange = (descripEvent: React.ChangeEvent<HTMLTextAreaElement>) => {
|
||||
setDescriptionSurvey(descripEvent.target.value);
|
||||
}
|
||||
|
||||
const handleNewTitleChange = (titleEvent: React.ChangeEvent<HTMLTextAreaElement>) => {
|
||||
setTitleSurvey(titleEvent.target.value);
|
||||
}
|
||||
|
||||
const handleAddNewTitleClick = () => {
|
||||
setShowNewTitleField(true);
|
||||
}
|
||||
|
||||
const handleAddDescriptionClick = () => {
|
||||
setShowDescriptionField(true);
|
||||
}
|
||||
|
||||
const handleTitleKeyDown = (titleClickEnter: React.KeyboardEvent<HTMLTextAreaElement>) => {
|
||||
if (titleClickEnter.key === 'Enter'){
|
||||
titleClickEnter.preventDefault();
|
||||
setShowNewTitleField(false);
|
||||
}
|
||||
}
|
||||
|
||||
const handleDescriptionKeyDown = (descripClickEnter: React.KeyboardEvent<HTMLTextAreaElement>) => {
|
||||
if (descripClickEnter.key === 'Enter'){
|
||||
descripClickEnter.preventDefault();
|
||||
setShowDescriptionField(false);
|
||||
}
|
||||
}
|
||||
|
||||
const handleParagraphClick = () => {
|
||||
setShowDescriptionField(true);
|
||||
}
|
||||
|
||||
return (
|
||||
<div className={styles.blockInfo}>
|
||||
<div className={styles.info}>
|
||||
{
|
||||
showNewTitleField ? (
|
||||
<h1 className={styles.titleSurvey}>
|
||||
<textarea className={styles.textareaTitle}
|
||||
value={titleSurvey}
|
||||
onChange={handleNewTitleChange}
|
||||
onKeyDown={handleTitleKeyDown}
|
||||
/>
|
||||
</h1>
|
||||
) : (
|
||||
<h1 className={styles.titleSurvey} onClick={handleAddNewTitleClick}>{titleSurvey}</h1>
|
||||
)
|
||||
}
|
||||
|
||||
{descriptionSurvey && !showDescriptionField ? (
|
||||
<p className={styles.description} onClick={handleParagraphClick}>{descriptionSurvey}</p>
|
||||
) : showDescriptionField ? (
|
||||
<p className={styles.description}>
|
||||
<textarea className={styles.textareaDescrip}
|
||||
value={descriptionSurvey}
|
||||
onChange={handleDescriptionChange}
|
||||
onKeyDown={handleDescriptionKeyDown}
|
||||
/>
|
||||
</p>
|
||||
) : (
|
||||
<button
|
||||
className={styles.descripButton}
|
||||
onClick={handleAddDescriptionClick}>
|
||||
<span className={styles.textButton}>Добавить описание</span>
|
||||
<img className={styles.descButtonImg} src='../../../public/add_circle.svg'/>
|
||||
</button>
|
||||
)}
|
||||
</div>
|
||||
</div>
|
||||
);
|
||||
};
|
||||
|
||||
export default SurveyInfo;
|
||||
|
|
@ -0,0 +1,7 @@
|
|||
.listSurveyPages{
|
||||
display: flex;
|
||||
gap: 61px;
|
||||
list-style: none;
|
||||
align-items: center;
|
||||
margin-right: 900px;
|
||||
}
|
||||
|
|
@ -0,0 +1,27 @@
|
|||
import React from 'react';
|
||||
import styles from './SurveyPagesList.module.css';
|
||||
import PageSurvey from "../PageSurvey/PageSurvey.tsx";
|
||||
|
||||
interface SurveyPagesListProps{
|
||||
activePage: string;
|
||||
onPageClick: (name: string) => void;
|
||||
}
|
||||
|
||||
const SurveyPagesList: React.FC<SurveyPagesListProps> = ({activePage, onPageClick}) => {
|
||||
const listPages: string[] = ['Создать опрос', 'Мои опросы']
|
||||
|
||||
return (
|
||||
<ul className={styles.listSurveyPages}>
|
||||
{listPages.map((page) => (
|
||||
<PageSurvey
|
||||
key={page}
|
||||
name={page}
|
||||
isActive={activePage === page}
|
||||
onClick={() => onPageClick(page)}
|
||||
/>
|
||||
))}
|
||||
</ul>
|
||||
);
|
||||
};
|
||||
|
||||
export default SurveyPagesList;
|
||||
|
|
@ -0,0 +1,69 @@
|
|||
/*TypeDropdown.module.css*/
|
||||
|
||||
.dropdownContainer {
|
||||
width: 22%;
|
||||
position: relative;
|
||||
display: inline-block;
|
||||
}
|
||||
|
||||
.dropdownButton {
|
||||
background-color: #fff;
|
||||
border: 1px solid #000000;
|
||||
border-radius: 19px;
|
||||
padding: 9px 7px 7px 10px;
|
||||
font-size: 16px;
|
||||
font-weight: 500;
|
||||
cursor: pointer;
|
||||
display: flex;
|
||||
align-items: center;
|
||||
justify-content: space-between;
|
||||
width: 118%;
|
||||
text-align: left;
|
||||
}
|
||||
|
||||
.selectedTypeIcon {
|
||||
margin-right: 5px;
|
||||
}
|
||||
|
||||
.dropdownArrow {
|
||||
margin-left: 5px;
|
||||
}
|
||||
|
||||
.dropdownList {
|
||||
margin-top: 11px;
|
||||
position: absolute;
|
||||
top: 100%;
|
||||
left: 0;
|
||||
background-color: #fff;
|
||||
border: 1px solid #ccc;
|
||||
border-radius: 5px;
|
||||
padding: 5px 0;
|
||||
list-style: none;
|
||||
/*margin: 0;*/
|
||||
z-index: 1; /* Убедитесь, что список отображается поверх других элементов */
|
||||
width: 100%; /* Занимает всю ширину контейнера */
|
||||
box-shadow: 0px 2px 4px rgba(0, 0, 0, 0.1);
|
||||
}
|
||||
|
||||
.dropdownItem {
|
||||
padding: 10px 15px;
|
||||
cursor: pointer;
|
||||
display: flex;
|
||||
align-items: center;
|
||||
}
|
||||
|
||||
.dropdownItem:hover {
|
||||
background-color: #f0f0f0;
|
||||
}
|
||||
|
||||
.dropdownItemIcon {
|
||||
margin-right: 5px;
|
||||
}
|
||||
|
||||
.selectedTypeIcon,
|
||||
.dropdownItemIcon {
|
||||
width: 20px; /* Задайте нужную ширину и высоту */
|
||||
height: 20px;
|
||||
margin-right: 5px;
|
||||
vertical-align: middle; /* Выровняйте значок по вертикали */
|
||||
}
|
||||
87
SurveyFrontend/src/components/TypeDropdown/TypeDropdown.tsx
Normal file
87
SurveyFrontend/src/components/TypeDropdown/TypeDropdown.tsx
Normal file
|
|
@ -0,0 +1,87 @@
|
|||
import React, { useState, useRef, useEffect } from "react";
|
||||
|
||||
import styles from './TypeDropdown.module.css'
|
||||
|
||||
const single_selected = '../../../public/radio_button_checked.svg';
|
||||
const multiple_selected = '../../../public/check_box.svg';
|
||||
|
||||
|
||||
const TypeDropdown: React.FC = () => {
|
||||
const [isOpen, setIsOpen] = useState(false);
|
||||
const [selectedType, setSelectedType] = useState('single');
|
||||
const dropdownRef = useRef<HTMLDivElement>(null);
|
||||
|
||||
const handleToggle = () =>{
|
||||
setIsOpen(!isOpen);
|
||||
}
|
||||
|
||||
const handleSelect = (value: string) => {
|
||||
setSelectedType(value);
|
||||
setIsOpen(false);
|
||||
}
|
||||
|
||||
useEffect(() => {
|
||||
const handleClickOutside = (event: MouseEvent) => {
|
||||
if (dropdownRef.current && !dropdownRef.current.contains(event.target as Node)) {
|
||||
setIsOpen(false);
|
||||
}
|
||||
};
|
||||
|
||||
document.addEventListener("mousedown", handleClickOutside);
|
||||
return () => {
|
||||
document.removeEventListener("mousedown", handleClickOutside);
|
||||
};
|
||||
}, [dropdownRef]);
|
||||
|
||||
const getImage = (typeValue: string, isSelected: boolean): string => {
|
||||
if (typeValue === 'multiply') {
|
||||
return multiple_selected;
|
||||
} else {
|
||||
return single_selected;
|
||||
}
|
||||
};
|
||||
|
||||
return (
|
||||
<div className={styles.dropdownContainer} ref={dropdownRef}>
|
||||
<button className={styles.dropdownButton} onClick={handleToggle}>
|
||||
<img
|
||||
src={getImage(selectedType, true)}
|
||||
alt={selectedType === "single" ? "Одиночный выбор" : "Множественный выбор"}
|
||||
className={styles.selectedTypeIcon}
|
||||
/>
|
||||
{selectedType === "single" ? "Одиночный выбор" : "Множественный выбор"}
|
||||
<span className={styles.dropdownArrow}>▼</span>
|
||||
</button>
|
||||
|
||||
{isOpen && (
|
||||
<ul className={styles.dropdownList}>
|
||||
<li
|
||||
className={styles.dropdownItem}
|
||||
onClick={() => handleSelect("single")}
|
||||
>
|
||||
<img
|
||||
src={getImage("single", selectedType === "single")}
|
||||
alt="Одиночный выбор"
|
||||
className={styles.dropdownItemIcon}
|
||||
/>
|
||||
Одиночный выбор
|
||||
</li>
|
||||
<li
|
||||
className={styles.dropdownItem}
|
||||
onClick={() => handleSelect("multiply")}
|
||||
>
|
||||
<img
|
||||
src={getImage("multiply", selectedType === "multiply")}
|
||||
alt="Множественный выбор"
|
||||
className={styles.dropdownItemIcon}
|
||||
/>
|
||||
Множественный выбор
|
||||
</li>
|
||||
</ul>
|
||||
)}
|
||||
</div>
|
||||
);
|
||||
};
|
||||
|
||||
export default TypeDropdown;
|
||||
|
||||
13
SurveyFrontend/src/pages/Questions.tsx
Normal file
13
SurveyFrontend/src/pages/Questions.tsx
Normal file
|
|
@ -0,0 +1,13 @@
|
|||
import Header from '../components/Header/Header.tsx'
|
||||
import MainComponents from '../components/MainComponent/MainComponent.tsx'
|
||||
|
||||
const Questions = () => {
|
||||
return (
|
||||
<>
|
||||
<Header />
|
||||
<MainComponents />
|
||||
</>
|
||||
);
|
||||
};
|
||||
|
||||
export default Questions;
|
||||
0
SurveyFrontend/src/pages/Results.tsx
Normal file
0
SurveyFrontend/src/pages/Results.tsx
Normal file
0
SurveyFrontend/src/pages/Settings.tsx
Normal file
0
SurveyFrontend/src/pages/Settings.tsx
Normal file
Loading…
Add table
Add a link
Reference in a new issue