This commit is contained in:
kuwsh1n 2024-02-25 03:25:13 +03:00
parent f9ed0cd04f
commit d469d7d690
17 changed files with 649 additions and 30 deletions

View File

@ -30,7 +30,10 @@
"@fortawesome/fontawesome-svg-core": "^6.5.1",
"@fortawesome/free-solid-svg-icons": "^6.5.1",
"@fortawesome/react-fontawesome": "^0.2.0",
"@popperjs/core": "^2.11.8",
"bootstrap": "^5.3.3",
"react": "^18.2.0",
"react-bootstrap": "^2.10.1",
"react-dom": "^18.2.0",
"react-router-dom": "^6.22.1"
}

View File

@ -4,8 +4,11 @@
<meta charset="UTF-8">
<meta name="viewport" content="width=device-width, initial-scale=1.0">
<title>Document</title>
<link href="https://cdn.jsdelivr.net/npm/bootstrap@5.3.3/dist/css/bootstrap.min.css" rel="stylesheet" integrity="sha384-QWTKZyjpPEjISv5WaRU9OFeRpok6YctnYmDr5pNlyT2bRjXh0JMhjY6hW+ALEwIH" crossorigin="anonymous">
<link rel="stylesheet" href="https://cdnjs.cloudflare.com/ajax/libs/font-awesome/6.5.1/css/all.min.css">
</head>
<body>
<div id="root"></div>
<div id="root" style="height: 100%; width: 100%;"></div>
<script src="https://cdn.jsdelivr.net/npm/bootstrap@5.0.2/dist/js/bootstrap.bundle.min.js" integrity="sha384-MrcW6ZMFYlzcLA8Nl+NtUVF0sA7MsXsP1UyJoMp4YLEuNSfAP+JcXn/tWtIaxVXM" crossorigin="anonymous"></script>
</body>
</html>

View File

@ -1,21 +1,31 @@
import React from "react";
import React, { useState } from "react";
import { RouterProvider } from "react-router-dom";
import {FormsData} from "./context";
import router from "./router/router"
import classes from "./assets/styles/app.module.scss"
import NavBar from "./components/NavBar.jsx";
import 'bootstrap/dist/css/bootstrap.min.css';
const App = () => {
const [forms, setForms] = useState([])
return (
<div className={classes.main}>
<div className={classes.container}>
<div className={classes.header}>
<NavBar/>
</div>
<div className={classes.content}>
<RouterProvider router={router}/>
<FormsData.Provider value={{
forms,
setForms
}}>
<div className={classes.main}>
<div className={classes.container}>
<div className={classes.header}>
<NavBar/>
</div>
<div className={classes.content}>
<RouterProvider router={router}/>
</div>
</div>
</div>
</div>
</FormsData.Provider>
)
}

View File

@ -1,4 +1,5 @@
@import url('./base.css');
@import url('https://fonts.googleapis.com/css2?family=Montserrat:ital,wght@0,100..900;1,100..900&display=swap');
.main {
width: 100%;
@ -13,7 +14,7 @@
.header {
background-color: rgb(240, 240, 240);
width: 100%;
height: 50px;
height: 10%;
box-shadow: 0 0 5px 1px rgb(210, 210, 210);
}

View File

@ -1,6 +1,12 @@
.main {
height: 100%;
button {
font-size: 15px;
padding: 10%;
height: 100%;
padding: .40em 1.2rem;
color: white;
background-color: rgb(150, 209, 158);
border-radius: 5px;
font-family: "Montserrat", sans-serif;
}
}

View File

@ -1,5 +1,12 @@
.main {
height: 100%;
input {
border: 1px solid rgb(180, 180, 180);
height: 100%;
border-radius: 5px;
font-family: "Montserrat", sans-serif;
padding: 0 3px;
color: rgb(40, 40, 40);
font-size: 15px;
}
}

View File

@ -0,0 +1,71 @@
.myModal {
&__dialog {
&__content {
&__header {
display: flex;
justify-content: space-between;
align-items: center;
padding: 3%;
h5 {
display: block;
}
select {
display: block;
font-size: 15px;
font-family: "Montserrat", sans-serif;
border: 1px solid rgb(200, 200, 200);
padding: 5px;
}
}
&__body {
&__answer {
&__title {
display: block;
font-size: 15px;
font-family: "Montserrat", sans-serif;
}
&__text {
display: block;
font-family: "Montserrat", sans-serif;
border: 1px solid rgb(200, 200, 200);
width: 100%;
padding: 0 1%;
}
&__file {
margin-top: 10px;
}
}
&__comment {
margin-top: 5%;
&__title {
display: block;
font-size: 15px;
font-family: "Montserrat", sans-serif;
}
&__text {
display: block;
font-family: "Montserrat", sans-serif;
border: 1px solid rgb(200, 200, 200);
width: 100%;
padding: 0 1%;
}
}
&__time {
margin-top: 5%;
span {
display: block;
font-size: 15px;
font-family: "Montserrat", sans-serif;
}
input {
display: block;
font-size: 15px;
font-family: "Montserrat", sans-serif;
border: 1px solid rgb(200, 200, 200);
padding: 1%;
}
}
}
}
}
}

View File

@ -7,17 +7,71 @@
.wrapper {
width: 100%;
height: 100%;
border: 1px solid red;
}
.panel {
width: 100%;
height: 30px;
height: 10%;
display: flex;
justify-content: space-between;
align-items: center;
padding: 0 5%;
}
.listForms {
box-shadow: 0 0 5px 1px rgb(200, 200, 200);
border-radius: 5px;
margin-top: 5%;
height: 70%;
width: 100%;
&__columns {
display: flex;
justify-content: space-around;
align-items: center;
height: 15%;
width: 100%;
border-bottom: 1px solid rgb(220, 220, 220);
&__item {
font-size: 15px;
font-family: "Montserrat", sans-serif;
}
}
&__forms {
width: 100%;
height: 85%;
overflow-y: auto;
&::-webkit-scrollbar {
width: 7px;
}
&::-webkit-scrollbar-thumb {
background-color: rgb(200, 200, 200);
}
&__item {
display: flex;
justify-content: space-around;
align-items: center;
height: 25%;
width: 100%;
font-family: "Montserrat", sans-serif;
&:hover {
background-color: rgba(240, 240, 240, 0.8);
}
&__title {
width: 33.3%;
text-align: center;
cursor: pointer;
&:hover {
text-decoration: underline;
}
}
&__answers {
width: 33.3%;
text-align: center;
}
&__update {
width: 33.3%;
text-align: center;
}
}
}
}

View File

@ -0,0 +1,131 @@
.main {
width: 100%;
height: 100%;
padding: 4% 8%;
}
.wrapper {
width: 100%;
height: 100%;
}
.header {
text-align: right;
display: flex;
justify-content: end;
width: 100%;
height: 8%;
&__listBtn {
display: flex;
justify-content: space-between;
width: 20%;
}
}
.content {
margin-top: 3%;
height: 85%;
width: 100%;
display: flex;
justify-content: space-between;
&__listQuestion {
height: 100%;
width: 25%;
&__list {
height: 100%;
width: 100%;
&__item {
height: 12.5%;
width: 100%;
display: flex;
align-items: center;
padding: 0 5px;
cursor: pointer;
&:hover {
background-color: rgba(230, 230, 230, 0.6);
}
span {
display: block;
font-size: 15px;
font-family: "Montserrat", sans-serif;
}
}
&__item:not(:last-child) {
border-bottom: 1px solid rgb(220, 220, 220);
}
}
}
&__newForm {
box-shadow: 0 0 5px 1px rgb(220, 220, 220);
height: 100%;
width: 75%;
&__title {
padding: 1%;
width: 100%;
height: 10%;
h3 {
font-size: 20px;
font-family: "Montserrat", sans-serif;
color: rgb(80, 80, 80);
}
}
&__list {
overflow-y: auto;
width: 100%;
height: 90%;
&::-webkit-scrollbar {
width: 7px;
}
&::-webkit-scrollbar-thumb {
background-color: rgb(200, 200, 200);
}
&__item {
padding: 1%;
width: 100%;
height: 25%;
border-bottom: 1px solid rgb(220, 220, 220);
display: flex;
justify-content: space-between;
align-items: center;
&:hover {
background-color: rgba(230, 230, 230, 0.6);
cursor: pointer;
}
&__answer {
width: 90%;
span {
display: block;
}
span:first-child {
font-size: 15px;
font-family: "Montserrat", sans-serif;
color: rgb(80, 80, 80);
}
span:last-child {
font-size: 12px;
font-family: "Montserrat", sans-serif;
color: rgb(180, 180, 180);
}
}
&__btn {
width: 10%;
display: flex;
justify-content: space-around;
align-items: center;
i {
display: block;
}
i:first-child:hover {
color: rgb(42, 64, 187);
}
i:last-child:hover {
color: rgb(199, 73, 73);
}
}
}
}
}
}

View File

@ -1,10 +1,17 @@
import React from "react";
import classes from "../assets/styles/components/myButton.module.scss"
const MyButton = () => {
const MyButton = (props) => {
return (
<div className={classes.main}>
<button type="button">Создать</button>
<button
type="button"
class={props.class}
onClick={props.click}
style={{backgroundColor: props.backgroundColor, ...props.otherStyle}}>
{props.text}
</button>
</div>
)
}

View File

@ -1,10 +1,10 @@
import React from "react";
import classes from "../assets/styles/components/myInput.module.scss"
const MyInput = () => {
const MyInput = (props) => {
return (
<div className={classes.main}>
<input type="text" />
<input type="text" placeholder={props.placeholder}/>
</div>
)
}

View File

@ -0,0 +1,67 @@
import React from "react";
import classes from "../assets/styles/components/myModal.module.scss";
const MyModal = ({
cleanStates,
currentTypeAnswer,
updateAnswerByForm,
stateModal,
saveStates,
answer,
file,
listTypeAnswer,
comment,
datetime,
setAnswer,
setComment,
setDatetime,
setCurrentTypeAnswer,
setFile
}) => {
return (
<div class="modal fade myModal" className={classes.myModal} id="exampleModal" tabIndex="-1" aria-labelledby="exampleModalLabel" data-bs-backdrop="static" aria-hidden="true">
<div class="modal-dialog myModal__dialog" className={classes.myModal__dialog}>
<div class="modal-content" className={classes.myModal__dialog__content}>
<div class="modal-header" className={classes.myModal__dialog__content__header}>
<h5 class="modal-title" id="exampleModalLabel">Редактирование вопроса</h5>
<select value={currentTypeAnswer} onChange={e => setCurrentTypeAnswer(Number(e.target.value))}>
{listTypeAnswer.map((item, i) =>
<option value={item.id} key={i}>{item.text}</option>
)}
</select>
</div>
<div class="modal-body" className={classes.myModal__dialog__content__body}>
<div className={classes.myModal__dialog__content__body__answer}>
<span className={classes.myModal__dialog__content__body__answer__title}>Вопрос</span>
<textarea className={classes.myModal__dialog__content__body__answer__text} value={answer} onChange={event => setAnswer(event.target.value)}></textarea>
<input
type="file"
multiple
accept="image/*,image/jpeg,video/mp4,video/x-m4v,video/*"
className={classes.myModal__dialog__content__body__answer__file}
value={file}
onChange={event => setFile(event.target.value)}
></input>
</div>
<div className={classes.myModal__dialog__content__body__comment}>
<span className={classes.myModal__dialog__content__body__comment__title}>Комментарий</span>
<textarea className={classes.myModal__dialog__content__body__comment__text} value={comment} onChange={event => setComment(event.target.value)}></textarea>
</div>
<div className={classes.myModal__dialog__content__body__time}>
<span>Дедлайн выполнения</span>
<input type="datetime-local" value={datetime} onChange={event => setDatetime(event.target.value)}/>
</div>
</div>
<div class="modal-footer myModal__dialog__content__footer" className={classes.myModal__dialog__content__footer}>
<button type="button" class="btn btn-light" data-bs-dismiss="modal" onClick={cleanStates}>Отмена</button>
<button type="button" class="btn" style={{color: 'white', backgroundColor: 'rgb(150, 209, 158)'}} data-bs-dismiss="modal" onClick={stateModal ? updateAnswerByForm : saveStates}>Сохранить</button>
</div>
</div>
</div>
</div>
)
}
export default MyModal;

View File

@ -1,13 +1,15 @@
import React from "react";
import { Link } from "react-router-dom";
import classes from "../assets/styles/components/navbar.module.scss"
const NavBar = () => {
return (
<navbar className={classes.main}>
<div className={classes.main}>
<div className={classes.wrapper}>
{/* <Link to={'/new'}>New Form</Link>
<Link to={'/forms'}>Forms</Link> */}
</div>
</navbar>
</div>
)
}

3
src/context/index.js Normal file
View File

@ -0,0 +1,3 @@
import { createContext } from "react";
export const FormsData = createContext([])

0
src/hooks/dragDrop.js Normal file
View File

View File

@ -1,17 +1,67 @@
import React from "react";
import React, { useState, useContext } from "react";
import { useNavigate } from 'react-router-dom';
import classes from "../assets/styles/forms.module.scss"
import MyButton from "../components/MyButton.jsx";
import MyInput from "../components/MyInput.jsx";
import { FormsData } from "../context";
const Forms = () => {
const navigate = useNavigate()
const {forms, setForms} = useContext(FormsData);
const [stateLoading, setStateLoading] = useState(false)
const response = ms => {
return new Promise(r => setTimeout(() => r('response end'), ms))
}
function createForm() {
setStateLoading(true);
// Эмуляция пост запроса
response(1000)
.then((r) => {
console.log(r);
setStateLoading(false);
navigate("/new");
}
)
}
function editForm(item) {
navigate("/new", {
state: {
id: item.id,
data: item.listAnswer
}
});
}
return (
<div className={classes.main}>
<div className={classes.wrapper}>
<div className={classes.panel}>
<MyInput/>
<MyButton/>
<MyInput placeholder={'Поиск...'}/>
<MyButton click={createForm} otherStyle={{width: '200px'}} text={
stateLoading ? <div class="spinner-border text-light" role="status">
<span class="visually-hidden">Загрузка...</span>
</div> : 'Создать'
}/>
</div>
<div className={classes.listForms}>
<div className={classes.listForms__columns}>
<div className={classes.listForms__columns__item}>Название</div>
<div className={classes.listForms__columns__item}>Ответы</div>
<div className={classes.listForms__columns__item}>Изменения</div>
</div>
<div className={classes.listForms__forms}>
{forms.map((item, i) =>
<div className={classes.listForms__forms__item} key={i}>
<div className={classes.listForms__forms__item__title} onClick={() => editForm(item)}>{item.title}</div>
<div className={classes.listForms__forms__item__answers}>{item.answers}</div>
<div className={classes.listForms__forms__item__update}>{item.update}</div>
</div>
)}
</div>
</div>
<div className={classes.listForms}></div>
</div>
</div>
)

View File

@ -1,9 +1,213 @@
import React from "react";
import React, { useState, useContext } from "react";
import { useNavigate, useLocation } from 'react-router-dom';
import classes from "../assets/styles/newForm.module.scss";
import MyButton from "../components/MyButton.jsx";
import MyModal from "../components/MyModal.jsx";
import { FormsData } from "../context";
const NewForm = () => {
const navigate = useNavigate();
const location = useLocation();
const [dragElem, setDragElem] = useState(null);
const [dropElem, setDropElem] = useState(null);
const {forms, setForms} = useContext(FormsData);
const nextID = (list) => {
return list.length ? list.at(-1).id + 1 : 1
};
const [answer, setAnswer] = useState("");
const [comment, setComment] = useState("");
const [datetime, setDatetime] = useState("");
const [file, setFile] = useState([]);
const [currentTypeAnswer, setCurrentTypeAnswer] = useState("");
const [newForm, setNewForm] = useState(location.state ? location.state.data : []);
const [stateModal, setStateModal] = useState(false)
const [listTypeAnswer, setListTypeAnswer] = useState([
{id: 1, text: 'Краткий ответ'},
{id: 2, text: 'Расширенный ответ'},
{id: 3, text: 'Выбор из вариантов'},
{id: 4, text: 'Множественный выбор'},
{id: 5, text: 'Выпадающий список'},
{id: 6, text: 'Да/Нет'},
{id: 7, text: 'Файл'},
{id: 8, text: 'Дата'}
]);
function removeAnswerByForm(id) {
setNewForm([...newForm.filter(item => item.id !== id)]);
}
function cleanStates() {
setStateModal(false)
setAnswer("");
setComment("");
setDatetime("");
setFile("");
setCurrentTypeAnswer("");
}
function editAnswerByForm(id) {
const obj = newForm.find(item => item.id === id);
setAnswer(obj.answer);
setComment(obj.comment);
setDatetime(obj.datetime);
setFile(obj.file);
setCurrentTypeAnswer(obj.typeAnswer);
setStateModal(id);
}
function updateAnswerByForm() {
console.log(currentTypeAnswer)
setNewForm(newForm.map(item => {
if (item.id === stateModal) {
item.answer = answer;
item.comment = comment;
item.datetime = datetime;
item.file = file;
item.typeAnswer = currentTypeAnswer;
}
return item
}))
cleanStates()
}
function saveStates() {
setNewForm([...newForm, {
id: nextID(newForm),
answer: answer,
typeAnswer: currentTypeAnswer,
comment: comment,
datetime: datetime,
file: file
}]);
cleanStates();
}
function updateFormByForms() {
setForms(
forms.map(item => {
if (item.id === location.state.id) {
item.title = 'Новая форма',
item.answers = 'Без изменений',
item.update = '01/01/24',
item.listAnswer = newForm
}
return item
})
)
cleanStates();
navigate("/forms");
}
function saveForm() {
setForms(
[...forms, {
id: nextID(forms),
title: 'Новая форма',
answers: 'Без изменений',
update: '01/01/24',
listAnswer: newForm
}]
);
cleanStates();
navigate("/forms");
}
return (
<div>
NEW
<div className={classes.main}>
<div className={classes.wrapper}>
<div className={classes.header}>
<div className={classes.header__listBtn}>
<MyButton text={'Опубликовать'} click={location.state ? updateFormByForms : saveForm}/>
<MyButton text={<i class="fa-solid fa-ellipsis-vertical"></i>} click={() => console.log(newForm)} backgroundColor={'rgb(225, 225, 225)'}/>
</div>
</div>
<div className={classes.content}>
<div className={classes.content__listQuestion}>
<div className={classes.content__listQuestion__list}>
{listTypeAnswer.map((item, i) =>
<div className={classes.content__listQuestion__list__item} data-bs-toggle="modal" data-bs-target="#exampleModal" onClick={() => setCurrentTypeAnswer(item.id)} key={i}>
<span>{item.text}</span>
</div>
)}
</div>
</div>
<MyModal
stateModal={stateModal}
currentTypeAnswer={currentTypeAnswer}
answer={answer}
comment={comment}
datetime={datetime}
file={file}
listTypeAnswer={listTypeAnswer}
setAnswer={setAnswer}
setComment={setComment}
setDatetime={setDatetime}
setFile={setFile}
cleanStates={cleanStates}
saveStates={saveStates}
updateAnswerByForm={updateAnswerByForm}
setCurrentTypeAnswer={setCurrentTypeAnswer}/>
<div className={classes.content__newForm}>
<div className={classes.content__newForm__title}>
<h3>Новая форма</h3>
</div>
<div className={classes.content__newForm__list}>
{newForm.map((item, i) =>
<div
className={classes.content__newForm__list__item}
key={i}
id={i}
draggable="true"
onDragOver={(event) => {event.preventDefault()}}
onDragStart={(event) => {
if (event.target.id) {
setDragElem(Number(event.target.id))
}
}}
onDragEnter={(event) => {
if (event.target.id) {
setDropElem(Number(event.target.id))
}
}}
onDrop={(event) => {
const currentElem = newForm[dragElem]
const tNewForm = [...newForm]
if (dragElem > dropElem) {
tNewForm.splice(dropElem, 0, currentElem)
tNewForm.splice(dragElem + 1, 1)
}
else {
tNewForm.splice(dropElem + 1, 0, currentElem)
tNewForm.splice(dragElem, 1)
}
setNewForm(tNewForm)
}}
>
<div className={classes.content__newForm__list__item__answer}>
<span>{item.answer}</span>
<span>{listTypeAnswer.map(typeItem => {
if (typeItem.id === item.typeAnswer) {
return typeItem.text
}
})}</span>
</div>
<div className={classes.content__newForm__list__item__btn}>
<i class="fa-solid fa-pen" data-bs-toggle="modal" data-bs-target="#exampleModal" onClick={() => {editAnswerByForm(item.id)}}></i>
<i class="fa-solid fa-trash" onClick={() => removeAnswerByForm(item.id)}></i>
</div>
</div>
)}
</div>
</div>
</div>
</div>
</div>
)
}