Compare commits

..

22 Commits

Author SHA1 Message Date
7bccb29aa6 qrcode temporary fix
All checks were successful
publish-main / release-image (push) Successful in 10m12s
2024-07-03 19:35:51 +03:00
2b50f7c332 articles styles fix
All checks were successful
publish-main / release-image (push) Successful in 10m46s
2024-07-03 18:56:34 +03:00
0e1dc49f59 ticket 16, 4, 18, 19, 20, 21, 22
Some checks failed
publish-main / release-image (push) Has been cancelled
2024-07-03 16:26:24 +03:00
d25e54f73f mybutton fix
All checks were successful
publish-main / release-image (push) Successful in 4m40s
2024-06-27 23:07:29 +03:00
58b6d1387d ticket 17
All checks were successful
publish-main / release-image (push) Successful in 7m24s
2024-06-25 14:01:13 +03:00
0e7f962645 ticket 17 2024-06-25 13:52:14 +03:00
750b571374 design update, bug fixes
All checks were successful
publish-main / release-image (push) Successful in 5m56s
2024-06-24 01:49:10 +03:00
133ed782aa ticket 16
All checks were successful
publish-main / release-image (push) Successful in 6m40s
2024-06-23 21:05:43 +03:00
86854956eb actions by users
All checks were successful
publish-main / release-image (push) Successful in 6m32s
2024-06-21 19:39:42 +03:00
ca131a225c fix ticket 5
All checks were successful
publish-main / release-image (push) Successful in 7m40s
2024-05-12 20:20:29 +03:00
caf16c4ea9 fix profile
All checks were successful
publish-main / release-image (push) Successful in 7m10s
2024-05-08 23:19:32 +03:00
e4dba30177 fix ticket 6, 10, 15
All checks were successful
publish-main / release-image (push) Successful in 6m56s
2024-05-08 22:08:25 +03:00
cdd43807f8 fix tickets 4, 6, 15, 12
All checks were successful
publish-main / release-image (push) Successful in 6m44s
2024-05-05 23:04:39 +03:00
e3455e3e6d fix ticket 14
All checks were successful
publish-main / release-image (push) Successful in 5m47s
2024-04-27 12:49:35 +03:00
8f2ae99f76 fix ticket 11
All checks were successful
publish-main / release-image (push) Successful in 5m52s
2024-04-26 13:20:01 +03:00
2abe6ef765 fix ticket 12
All checks were successful
publish-main / release-image (push) Successful in 5m24s
2024-04-26 11:23:46 +03:00
749cd56dcf fix ticket 6
All checks were successful
publish-main / release-image (push) Successful in 6m8s
2024-04-26 10:56:01 +03:00
949a1b7ed5 fix ticket 10
All checks were successful
publish-main / release-image (push) Successful in 6m47s
2024-04-25 13:03:34 +03:00
cb7d0587e8 fix ticket 9,13
All checks were successful
publish-main / release-image (push) Successful in 6m44s
2024-04-25 02:05:33 +03:00
1e1bdceb2c added link video parse
All checks were successful
publish-main / release-image (push) Successful in 3m59s
2024-04-21 10:37:18 +03:00
006c9bc4ea markdown and video
All checks were successful
publish-main / release-image (push) Successful in 3m31s
2024-04-20 18:30:54 +03:00
e02aa76b9a copy fix
All checks were successful
publish-main / release-image (push) Successful in 3m31s
2024-04-15 06:20:11 +03:00
68 changed files with 4557 additions and 1534 deletions

View File

@ -28,16 +28,24 @@
"webpack-dev-server": "^5.0.2" "webpack-dev-server": "^5.0.2"
}, },
"dependencies": { "dependencies": {
"@ckeditor/ckeditor5-build-classic": "^41.4.2",
"@ckeditor/ckeditor5-react": "^7.0.0",
"@fortawesome/fontawesome-svg-core": "^6.5.1", "@fortawesome/fontawesome-svg-core": "^6.5.1",
"@fortawesome/free-solid-svg-icons": "^6.5.1", "@fortawesome/free-solid-svg-icons": "^6.5.1",
"@fortawesome/react-fontawesome": "^0.2.0", "@fortawesome/react-fontawesome": "^0.2.0",
"@mdxeditor/editor": "^2.20.7",
"@popperjs/core": "^2.11.8", "@popperjs/core": "^2.11.8",
"@uiw/react-markdown-editor": "^5.12.1",
"axios": "^1.6.8", "axios": "^1.6.8",
"bootstrap": "^5.3.3", "bootstrap": "^5.3.3",
"inputmask": "^5.0.9",
"primereact": "^10.6.6",
"qrcode": "^1.5.3",
"react": "^18.2.0", "react": "^18.2.0",
"react-bootstrap": "^2.10.1", "react-bootstrap": "^2.10.1",
"react-cookie": "^7.1.4", "react-cookie": "^7.1.4",
"react-dom": "^18.2.0", "react-dom": "^18.2.0",
"react-qr-code": "^2.0.15",
"react-router-dom": "^6.22.1" "react-router-dom": "^6.22.1"
} }
} }

View File

@ -1,4 +1,4 @@
import React, { useState, useEffect, useLayoutEffect } from "react"; import React, { useState, useEffect, useLayoutEffect, useRef } from "react";
import { Outlet, useNavigate } from "react-router-dom"; import { Outlet, useNavigate } from "react-router-dom";
import { FormsData, UserData, TypeAnswerData, answersData } from "./context"; import { FormsData, UserData, TypeAnswerData, answersData } from "./context";
import { useCookies } from "react-cookie"; import { useCookies } from "react-cookie";
@ -20,6 +20,9 @@ const App = () => {
const navigate = useNavigate(); const navigate = useNavigate();
const [forms, setForms] = useState([]); const [forms, setForms] = useState([]);
const [user, setUser] = useState(false); const [user, setUser] = useState(false);
globalRender(window.location.pathname, user, navigate);
const userRef = useRef(false)
const [answersList, setAnswersList] = useState([]); const [answersList, setAnswersList] = useState([]);
const [listTypeAnswer, setListTypeAnswer] = useState([ const [listTypeAnswer, setListTypeAnswer] = useState([
{id: 1, text: 'Краткий ответ', typeTag: InputText}, {id: 1, text: 'Краткий ответ', typeTag: InputText},
@ -32,20 +35,21 @@ const App = () => {
{id: 8, text: 'Дата', typeTag: InputDate} {id: 8, text: 'Дата', typeTag: InputDate}
]); ]);
// useEffect(() => globalRender(window.location.pathname, user, navigate));
const [cookies, _, __] = useCookies(["user"]); const [cookies, _, __] = useCookies(["user"]);
useEffect(() => { useEffect(() => {
async function verifyUser() { async function verifyUser() {
const response = await verifyUserApi(cookies.token); const response = await verifyUserApi(cookies.token);
console.log("app", user); console.log("verifyUser App")
if (response.status === 200) { if (response.status === 200) {
setUser(response.data); setUser(response.data);
globalRender(window.location.pathname, response.data, navigate); userRef.current = response.data
// globalRender(window.location.pathname, response.data, navigate);
} }
else { else {
globalRender(window.location.pathname, false, navigate); // globalRender(window.location.pathname, false, navigate);
setUser(false);
} }
} }
@ -53,7 +57,7 @@ const App = () => {
}, []); }, []);
return ( return (
<UserData.Provider value={{ user, setUser }}> <UserData.Provider value={{ user, setUser, userRef }}>
<answersData.Provider value={{ answersList, setAnswersList }}> <answersData.Provider value={{ answersList, setAnswersList }}>
<FormsData.Provider value={{ forms, setForms }}> <FormsData.Provider value={{ forms, setForms }}>
<TypeAnswerData.Provider <TypeAnswerData.Provider

View File

@ -1,104 +1,133 @@
.main { .main {
width: 100%; width: 100%;
height: 100%; height: 100%;
} }
.wrapper { .wrapper {
width: 100%; width: 100%;
height: 100%; height: 100%;
display: flex; display: flex;
justify-content: center; justify-content: center;
align-items: center; align-items: center;
} }
.admin { .admin {
width: 70%; width: 85%;
height: 70%; height: 80%;
box-shadow: 0 0 5px 1px rgb(200, 200, 200); background: #fff;
padding: 1.5%; border: 1px solid rgba(255, 255, 255, 0.1);
position: relative; border-radius: 16px;
&__linkProfile { padding: 1.5%;
position: absolute; position: relative;
bottom: 103%; &__linkProfile {
left: 10px; position: absolute;
cursor: pointer; bottom: 103%;
span { left: 10px;
font-size: 15px; cursor: pointer;
font-family: "Montserrat", sans-serif; span {
color: rgb(100, 100, 100); font-size: 15px;
&:hover { font-family: "Montserrat", sans-serif;
text-decoration: underline; color: rgb(100, 100, 100);
color: rgb(66, 68, 189); &:hover {
} text-decoration: underline;
} color: rgb(66, 68, 189);
}
} }
&__wrapper { }
&__header { &__wrapper {
width: 100%; width: 100%;
height: 15%; height: 100%;
&__header {
width: 100%;
height: 15%;
display: flex;
justify-content: space-between;
align-items: center;
margin-bottom: 10px;
h3 {
font-size: 25px;
font-family: "Montserrat", sans-serif;
color: rgb(100, 100, 100);
}
}
&__body {
width: 100%;
height: 85%;
&__columns {
width: 100%;
height: 20%;
border-bottom: 1px solid rgb(200, 200, 200);
&__wrapper {
display: flex;
justify-content: space-between;
align-items: center;
width: 95%;
height: 100%;
}
&__item {
font-size: 15px;
font-family: "Montserrat", sans-serif;
color: rgb(70, 70, 70);
width: 33.3%;
text-align: center;
}
}
&__users {
width: 100%;
height: 80%;
overflow-y: auto;
&::-webkit-scrollbar {
width: 7px;
}
&::-webkit-scrollbar-thumb {
background-color: rgb(200, 200, 200);
}
&__item {
width: 100%;
height: 60px;
border-bottom: 1px solid rgb(200, 200, 200);
position: relative;
&__wrapper {
width: 95%;
height: 100%;
display: flex; display: flex;
justify-content: space-between; justify-content: space-between;
align-items: center; align-items: center;
margin-bottom: 10px; }
h3 { &__link {
font-size: 25px; font-size: 15px;
font-family: "Montserrat", sans-serif; font-family: "Montserrat", sans-serif;
color: rgb(100, 100, 100); color: rgb(70, 70, 70);
} width: 20%;
} text-align: center;
&__body { cursor: pointer;
width: 100%; &:hover {
height: 85%; text-decoration: underline;
&__columns { color: rgb(66, 68, 189);
width: 100%;
height: 20%;
border-bottom: 1px solid rgb(200, 200, 200);
display: flex;
justify-content: space-between;
align-items: center;
&__item {
font-size: 15px;
font-family: "Montserrat", sans-serif;
color: rgb(70, 70, 70);
width: 33.3%;
text-align: center;
}
}
&__users {
width: 100%;
height: 80%;
margin-top: 10px;
&__item {
width: 100%;
height: 20%;
display: flex;
justify-content: space-between;
align-items: center;
border-bottom: 1px solid rgb(200, 200, 200);
&__link {
font-size: 15px;
font-family: "Montserrat", sans-serif;
color: rgb(70, 70, 70);
width: 33.3%;
text-align: center;
cursor: pointer;
&:hover {
text-decoration: underline;
color: rgb(66, 68, 189);
}
}
&__info {
font-size: 15px;
font-family: "Montserrat", sans-serif;
color: rgb(100, 100, 100);
width: 33.3%;
text-align: center;
}
}
}
&__item {
padding: 5px;
} }
}
&__info {
font-size: 15px;
font-family: "Montserrat", sans-serif;
color: rgb(100, 100, 100);
width: 20%;
text-align: center;
}
&__remove {
position: absolute;
right: 2%;
cursor: pointer;
i {
&:hover {
color: rgb(199, 73, 73);
}
}
}
} }
}
&__item {
padding: 5px;
}
} }
} }
}

View File

@ -1,139 +1,156 @@
.main { .main {
width: 100%; width: 100%;
height: 100%; height: 100%;
} }
.wrapper { .wrapper {
width: 100%; width: 100%;
height: 100%; height: 100%;
display: flex; display: flex;
justify-content: center; justify-content: center;
align-items: center; align-items: center;
} }
.loading__wrapper { .loading__wrapper {
width: 100%; width: 100%;
height: 80%; height: 80%;
display: flex; display: flex;
justify-content: center; justify-content: center;
align-items: center; align-items: center;
&__body { &__body {
// margin-top: 100px; // margin-top: 100px;
span { span {
}
} }
}
} }
.answers { .answers {
width: 70%; width: 70%;
height: 70%; height: 70%;
box-shadow: 0 0 5px 1px rgb(200, 200, 200); background: #fff;
padding: 1.5%; border: 1px solid rgba(255, 255, 255, 0.1);
&__linkAdmin { border-radius: 16px;
position: absolute; padding: 1.5%;
bottom: 103%; &__linkAdmin {
left: 10px; position: absolute;
span { bottom: 103%;
font-size: 15px; left: 10px;
font-family: "Montserrat", sans-serif; span {
color: rgb(100, 100, 100); font-size: 15px;
font-family: "Montserrat", sans-serif;
color: rgb(100, 100, 100);
cursor: pointer;
&:hover {
text-decoration: underline;
color: rgb(66, 68, 189);
}
}
}
&__wrapper {
width: 100%;
height: 100%;
&__header {
width: 100%;
height: 15%;
display: flex;
justify-content: start;
align-items: center;
margin-bottom: 10px;
// border-bottom: 1px solid rgb(200, 200, 200);
h3 {
font-size: 25px;
font-family: "Montserrat", sans-serif;
color: rgb(100, 100, 100);
}
}
&__body {
width: 100%;
height: 85%;
overflow-y: auto;
border-bottom: 1px solid rgb(200, 200, 200);
&::-webkit-scrollbar {
width: 7px;
}
&::-webkit-scrollbar-thumb {
background-color: rgb(200, 200, 200);
}
&__column {
font-size: 15px;
font-family: "Montserrat", sans-serif;
padding: 5px;
width: 100%;
border-bottom: 1px solid rgb(200, 200, 200);
&__wrapper {
display: flex;
justify-content: space-between;
align-items: center;
height: 100%;
width: 95%;
}
}
&__item {
padding: 0 5px;
font-size: 15px;
font-family: "Montserrat", sans-serif;
height: 20%;
width: 100%;
position: relative;
&:hover {
background-color: rgba(230, 230, 230, 0.6);
}
&__wrapper {
display: flex;
justify-content: space-between;
align-items: center;
height: 100%;
width: 95%;
&__user {
cursor: pointer; cursor: pointer;
&:hover { &:hover {
text-decoration: underline; text-decoration: underline;
color: rgb(66, 68, 189);
}
}
}
&__wrapper {
width: 100%;
height: 100%;
&__header {
width: 100%;
height: 15%;
display: flex;
justify-content: start;
align-items: center;
margin-bottom: 10px;
// border-bottom: 1px solid rgb(200, 200, 200);
h3 {
font-size: 25px;
font-family: "Montserrat", sans-serif;
color: rgb(100, 100, 100);
}
}
&__body {
width: 100%;
height: 85%;
overflow-y: auto;
border-bottom: 1px solid rgb(200, 200, 200);
&::-webkit-scrollbar {
width: 7px;
}
&::-webkit-scrollbar-thumb {
background-color: rgb(200, 200, 200);
}
&__column {
display: flex;
justify-content: space-between;
align-items: center;
font-size: 15px;
font-family: "Montserrat", sans-serif;
padding: 5px;
border-bottom: 1px solid rgb(200, 200, 200);
}
&__item {
padding: 0 5px;
// border-bottom: 1px solid rgb(200, 200, 200);
font-size: 15px;
font-family: "Montserrat", sans-serif;
display: flex;
justify-content: space-between;
align-items: center;
height: 20%;
width: 100%;
&:hover {
background-color: rgba(230, 230, 230, 0.6);
}
&__user {
cursor: pointer;
&:hover {
text-decoration: underline;
}
}
&__date {
}
} }
}
&__date {
}
i {
font-size: 13px;
position: absolute;
right: 10px;
cursor: pointer;
&:hover {
color: rgb(199, 73, 73);
}
}
} }
}
} }
}
} }
.myModal { .myModal {
&__item { &__item {
padding: 5px; padding: 5px;
border-bottom: 1px solid rgb(200, 200, 200); border-bottom: 1px solid rgb(200, 200, 200);
font-size: 15px;
font-family: "Montserrat", sans-serif;
cursor: pointer;
&__question {
&__text {
font-size: 15px; font-size: 15px;
font-family: "Montserrat", sans-serif; font-family: "Montserrat", sans-serif;
cursor: pointer; }
&__question { &__comment {
&__text { font-size: 11px;
font-size: 15px; font-family: "Montserrat", sans-serif;
font-family: "Montserrat", sans-serif; font-style: italic;
} color: rgb(200, 200, 200);
&__comment { }
font-size: 11px;
font-family: "Montserrat", sans-serif;
font-style: italic;
color: rgb(200, 200, 200);
}
}
&__answer {
&__text {
font-size: 15px;
font-family: "Montserrat", sans-serif;
}
}
} }
} &__answer {
&__text {
font-size: 15px;
font-family: "Montserrat", sans-serif;
}
}
}
}

View File

@ -1,24 +1,29 @@
@import url('./base.css'); @import url("./base.css");
@import url('https://fonts.googleapis.com/css2?family=Montserrat:ital,wght@0,100..900;1,100..900&display=swap'); @import url("https://fonts.googleapis.com/css2?family=Montserrat:ital,wght@0,100..900;1,100..900&display=swap");
body {
background: #9dccf6 !important;
}
.main { .main {
width: 100%; width: 100%;
height: 100%; height: 100%;
} }
.container { .container {
width: 100%; width: 100%;
height: 100%; height: 100%;
} }
.header { .header {
background-color: rgb(240, 240, 240); background-color: #fff;
width: 100%; width: 100%;
height: 10%; height: 50px;
box-shadow: 0 0 5px 1px rgb(210, 210, 210); box-sizing: border-box;
// box-shadow: 0 0 5px 1px rgb(210, 210, 210);
} }
.content { .content {
width: 100%; width: 100%;
height: 90%; height: 90%;
} }

View File

@ -0,0 +1,166 @@
.main {
width: 100%;
min-height: 100%;
padding: 4% 8%;
}
.wrapper {
width: 100%;
min-height: 100%;
}
.panel {
width: 100%;
display: flex;
justify-content: space-between;
align-items: end;
margin-bottom: 25px;
gap: 15px;
div:first-child {
flex-grow: 1;
input {
width: 100%;
}
}
}
.listforms {
border: 1px solid rgba(0, 0, 0, 0.1);
background: #fff;
border-radius: 16px;
height: 70%;
min-height: 700px;
margin-bottom: 50px;
width: 100%;
&__columns {
display: flex;
justify-content: start;
align-items: center;
padding: 20px 25px;
border-bottom: 1px solid rgb(220, 220, 220);
&__item {
font-size: 15px;
font-family: "Montserrat", sans-serif;
font-weight: bold;
}
}
&__forms {
min-height: 100%;
display: inline;
overflow-y: auto;
&__item {
display: flex;
justify-content: start;
align-items: center;
width: 100%;
padding: 20px 25px;
font-family: "Montserrat", sans-serif;
position: relative;
border-bottom: 1px solid rgba(0, 0, 0, 0.05);
&:hover {
background-color: rgba(240, 240, 240, 0.8);
}
&__title {
text-align: center;
cursor: pointer;
&:hover {
text-decoration: underline;
}
}
&__answers {
width: 33.3%;
text-align: center;
}
&__update {
width: 33.3%;
text-align: center;
}
i {
position: absolute;
font-size: 15px;
right: 30px;
top: calc(50% - 7px);
cursor: pointer;
}
ul {
li {
cursor: pointer;
}
}
}
}
}
.listArticles {
border: 1px solid rgba(0, 0, 0, 0.1);
background: #fff;
border-radius: 16px;
height: 70%;
min-height: 700px;
margin-bottom: 50px;
width: 100%;
&__columns {
display: flex;
justify-content: start;
align-items: center;
padding: 20px 25px;
border-bottom: 1px solid rgb(220, 220, 220);
&__item {
width: 33.3%;
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: start;
align-items: center;
width: 100%;
padding: 20px 25px;
font-family: "Montserrat", sans-serif;
position: relative;
border-bottom: 1px solid rgba(0, 0, 0, 0.05);
&:hover {
background-color: rgba(240, 240, 240, 0.8);
}
&__title {
width: 33.3%;
// text-align: center;
cursor: pointer;
&:hover {
text-decoration: underline;
}
}
&__date {
width: 33.3%;
// text-align: center;
}
&__author {
width: 33.3%;
// text-align: center;
}
i {
position: absolute;
font-size: 15px;
right: 30px;
top: calc(50% - 7px);
cursor: pointer;
}
ul {
li {
cursor: pointer;
}
}
}
}
}

View File

@ -1,57 +1,88 @@
* { * {
padding: 0; padding: 0;
margin: 0; margin: 0;
border: 0; } border: 0;
}
*, *:before, *:after {
-moz-box-sizing: border-box; *,
-webkit-box-sizing: border-box; *:before,
box-sizing: border-box; } *:after {
-moz-box-sizing: border-box;
:focus, :active { -webkit-box-sizing: border-box;
outline: none; } box-sizing: border-box;
}
a:focus, a:active {
outline: none; } :focus,
:active {
nav, footer, header, aside { outline: none;
display: block; } }
html, body { a:focus,
height: 100%; a:active {
width: 100%; outline: none;
font-size: 100%; }
line-height: 1;
font-size: 14px; nav,
-ms-text-size-adjust: 100%; footer,
-moz-text-size-adjust: 100%; header,
-webkit-text-size-adjust: 100%; } aside {
display: block;
input, button, textarea { }
font-family: inherit; }
html,
body {
height: 100%;
width: 100%;
font-size: 100%;
line-height: 1;
font-size: 14px;
-ms-text-size-adjust: 100%;
-moz-text-size-adjust: 100%;
-webkit-text-size-adjust: 100%;
}
input,
button,
textarea {
font-family: inherit;
}
input::-ms-clear { input::-ms-clear {
display: none; } display: none;
}
button {
cursor: pointer; } button {
cursor: pointer;
button::-moz-focus-inner { }
padding: 0;
border: 0; } button::-moz-focus-inner {
padding: 0;
a, a:visited { border: 0;
text-decoration: none; } }
a:hover { a,
text-decoration: none; } a:visited {
text-decoration: none;
ul li { }
list-style: none; }
a:hover {
img { text-decoration: none;
vertical-align: top; } }
h1, h2, h3, h4, h5, h6 { ul li {
font-size: inherit; list-style: none;
font-weight: 400; } }
img {
vertical-align: top;
}
h1,
h2,
h3,
h4,
h5,
h6 {
font-size: inherit;
font-weight: 400;
}

View File

@ -32,7 +32,27 @@
padding: 0 1%; padding: 0 1%;
} }
&__file { &__file {
// margin-top: 10px;
display: none;
}
&__fileLabel {
margin-top: 10px; margin-top: 10px;
border: 1px solid rgb(100, 100, 100);
background-color: rgb(240, 240, 240);
font-family: "Montserrat", sans-serif;
color: rgb(100, 100, 100);
cursor: pointer;
padding: 1%;
transition: 0.3s;
&:hover {
background-color: rgb(230, 230, 230);
transition: 0.3s;
}
}
&__listFile {
margin-left: 10px;
font-family: "Montserrat", sans-serif;
color: rgb(100, 100, 100);
} }
} }
&__comment { &__comment {
@ -50,6 +70,21 @@
padding: 0 1%; padding: 0 1%;
} }
} }
&__video {
margin-top: 5%;
&__title {
display: block;
font-size: 15px;
font-family: "Montserrat", sans-serif;
}
&__link {
display: block;
font-family: "Montserrat", sans-serif;
border: 1px solid rgb(200, 200, 200);
width: 100%;
padding: 0 1%;
}
}
&__answerOptions { &__answerOptions {
margin-top: 5%; margin-top: 5%;
&__title { &__title {
@ -64,6 +99,17 @@
margin-top: 4px; margin-top: 4px;
font-size: 12px; font-size: 12px;
font-family: "Montserrat", sans-serif; font-family: "Montserrat", sans-serif;
i {
display: none;
margin-left: 5px;
cursor: pointer;
&:hover {
color: rgb(230, 80, 80);
}
}
&:hover i {
display: inline;
}
} }
} }
&__text { &__text {

View File

@ -0,0 +1,5 @@
.main {
width: 100%;
height: 100%;
padding: 2%;
}

View File

@ -0,0 +1,42 @@
.main {
display: flex;
align-items: center;
flex-direction: column;
padding: 3%;
&__status {
display: flex;
justify-content: space-between;
align-items: center;
width: 100%;
margin-top: 5%;
&__admin {
display: flex;
justify-content: space-around;
align-items: center;
font-family: "Montserrat", sans-serif;
color: rgb(40, 40, 40);
font-size: 15px;
width: 50%;
border: 1px solid rgb(180, 180, 180);
border-radius: 5px 0 0 5px;
border-right: none
&__checkbox {
}
}
&__teacher {
display: flex;
justify-content: space-around;
font-family: "Montserrat", sans-serif;
color: rgb(40, 40, 40);
font-size: 15px;
align-items: center;
width: 50%;
border: 1px solid rgb(180, 180, 180);
border-radius: 0 5px 5px 0;
&__checkbox {
}
}
}
}

View File

@ -0,0 +1,8 @@
.main {
width: 100%;
height: 100%;
display: flex;
justify-content: center;
align-items: center;
padding: 2% 0;
}

View File

@ -0,0 +1,7 @@
.main {
&__dialog {
&__content {
}
}
}

View File

@ -0,0 +1,13 @@
.loading__wrapper {
width: 100%;
height: 80%;
display: flex;
justify-content: center;
align-items: center;
&__body {
// margin-top: 100px;
span {
}
}
}

View File

@ -0,0 +1,3 @@
.main {
border: 1px solid rgb(200, 200, 200);
}

View File

@ -1,12 +1,56 @@
.main { .main {
max-height: 40px;
button {
font-size: 15px;
height: 100%; height: 100%;
button { padding: 0.4em 1.2rem;
font-size: 15px; border-radius: 5px;
height: 100%; font-family: "Montserrat", sans-serif;
padding: .40em 1.2rem; transition: 0.2s;
&:hover {
filter: brightness(1.1);
}
}
&__red {
color: white;
background-color: rgb(235, 130, 130);
}
&__green {
color: white;
background-color: rgb(150, 209, 158);
}
&__blue {
color: white;
background-color: #1379c1;
}
&__white {
color: black;
background-color: white;
border: 1px solid rgba(0, 0, 0, 0.2);
&:hover {
border: 1px solid rgba(0, 0, 0, 0.3);
}
&__green {
color: white; color: white;
background-color: rgb(150, 209, 158); background-color: rgb(150, 209, 158);
border-radius: 5px;
font-family: "Montserrat", sans-serif;
} }
&__white {
color: black;
background-color: white;
border: 1px solid rgba(0, 0, 0, 0.2);
&:hover {
border: 1px solid rgba(0, 0, 0, 0.3);
}
}
&__transparent {
background-color: rgba(0, 0, 0, 0);
border: 1px solid rgba(0, 0, 0, 0);
transition: 0.3s;
&:hover {
background-color: rgba(180, 180, 180, 0.5);
color: white;
transition: 0.3s;
}
}
}
} }

View File

@ -1,12 +1,18 @@
.main { .main {
max-height: 40px;
input {
border: 1px solid rgba(0, 0, 0, 0.1);
height: 100%; height: 100%;
input { border-radius: 5px;
border: 1px solid rgb(180, 180, 180); font-family: "Montserrat", sans-serif;
height: 100%; box-sizing: border-box;
border-radius: 5px; padding: 8px 8px 8px 10px;
font-family: "Montserrat", sans-serif; color: rgb(40, 40, 40);
padding: 0 3px; font-size: 15px;
color: rgb(40, 40, 40); transition: 0.2s;
font-size: 15px; &:hover,
&:focus {
border: 1px solid rgba(0, 0, 0, 0.3);
} }
} }
}

View File

@ -1,67 +1,63 @@
.main { .main {
width: 100%; width: 100%;
height: 100%; height: 100%;
} }
.wrapper { .wrapper {
width: 100%; width: 100%;
height: 100%; height: 100%;
display: flex; display: flex;
justify-content: space-between; justify-content: space-between;
align-items: center; align-items: center;
} }
.menu { .menu {
width: 80%; width: 80%;
height: 100%; height: 100%;
&__authorized { &__authorized {
height: 100%;
display: flex;
align-items: center;
span {
margin-left: 2%;
display: block;
font-size: 15px;
color: rgb(80, 80, 80);
cursor: pointer;
&:hover {
color: rgb(66, 68, 189);
text-decoration: underline;
}
}
}
}
.profile {
width: 20%;
height: 100%; height: 100%;
display: flex; display: flex;
align-items: center; align-items: center;
justify-content: center; span {
&__authorized { margin-left: 30px;
span { display: block;
font-size: 15px; font-size: 15px;
color: rgb(80, 80, 80); color: rgb(80, 80, 80);
cursor: pointer; cursor: pointer;
&:hover { &:hover {
color: rgb(66, 68, 189); text-decoration: underline;
text-decoration: underline; }
}
}
} }
&__nonAuthorized { }
span { }
font-size: 15px;
font-family: "Montserrat", sans-serif;
color: rgb(80, 80, 80);
cursor: pointer;
i {
} .profile {
&:hover { height: 100%;
color: rgb(66, 68, 189); margin-right: 30px;
text-decoration: underline; display: flex;
} align-items: center;
} justify-content: center;
&__authorized {
span {
font-size: 15px;
color: rgb(80, 80, 80);
cursor: pointer;
&:hover {
text-decoration: underline;
}
} }
} }
&__nonAuthorized {
span {
font-size: 15px;
font-family: "Montserrat", sans-serif;
color: rgb(80, 80, 80);
cursor: pointer;
i {
}
&:hover {
text-decoration: underline;
}
}
}
}

View File

@ -1,119 +1,135 @@
.main { .main {
width: 100%; width: 100%;
height: 100%; height: 100%;
display: flex; display: flex;
justify-content: center; justify-content: center;
align-items: center; align-items: center;
} }
.wrapper { .wrapper {
width: 50%; width: 500px;
height: 70%;
} }
.tabs { .tabs {
ul { ul {
li { li {
width: 50%; width: 50%;
cursor: pointer; cursor: pointer;
a { a {
text-align: center; text-align: center;
font-family: "Montserrat", sans-serif; font-family: "Montserrat", sans-serif;
font-size: 12px; font-size: 12px;
} color: #000;
&:hover {
color: #000;
border: 1px solid rgba(0, 0, 0, 0.1);
background: rgba(255, 255, 255, 0.3);
} }
}
} }
}
} }
.content { .content {
width: 100%; width: 500px;
height: 100%; min-height: 320px;
border: 1px solid rgb(220, 220, 220); box-sizing: border-box;
border-top: none; padding: 35px 25px;
&__wrapper { background: #fff;
padding: 2%; border: 1px solid rgba(0, 0, 0, 0.1);
border-top: 0;
border-radius: 0 0 16px 16px;
&__wrapper {
&__login {
display: flex;
flex-direction: column;
gap: 15px;
&__header {
width: 100%; width: 100%;
height: 100%; height: 30%;
&__login { display: flex;
width: 100%; align-items: center;
height: 100%; justify-content: center;
&__header { h3 {
width: 100%; display: block;
height: 30%; font-size: 25px;
display: flex; margin: 0;
align-items: center; font-family: "Montserrat", sans-serif;
justify-content: center; text-align: center;
h3 { color: rgb(100, 100, 100);
display: block;
font-size: 25px;
font-family: "Montserrat", sans-serif;
text-align: center;
color: rgb(100, 100, 100);
}
}
&__body {
width: 100%;
height: 50%;
display: flex;
justify-content: space-around;
flex-direction: column;
position: relative;
&__error {
position: absolute;
font-size: 13px;
font-family: "Montserrat", sans-serif;
top: 0;
left: 10px;
color: rgb(224, 75, 75);
}
}
&__footer {
width: 100%;
height: 20%;
}
} }
&__register { }
width: 100%; &__body {
height: 100%; width: 100%;
&__header { display: flex;
width: 100%; justify-content: space-around;
height: 15%; flex-direction: column;
display: flex; gap: 15px;
align-items: center; position: relative;
justify-content: center; &__error {
h3 { position: absolute;
display: block; font-size: 13px;
font-size: 25px; font-family: "Montserrat", sans-serif;
font-family: "Montserrat", sans-serif; top: 0;
color: rgb(100, 100, 100); left: 10px;
} color: rgb(224, 75, 75);
}
&__body {
width: 100%;
height: 75%;
display: flex;
justify-content: space-around;
flex-direction: column;
padding-bottom: 5px;
position: relative;
&__error {
position: absolute;
font-size: 13px;
font-family: "Montserrat", sans-serif;
top: 0;
left: 10px;
color: rgb(224, 75, 75);
}
&__fio {
height: 15%;
display: flex;
justify-content: space-between;
}
}
&__footer {
width: 100%;
height: 10%;
}
} }
}
&__footer {
div,
button {
width: 100%;
height: 40px;
}
}
} }
} &__register {
display: flex;
flex-direction: column;
gap: 15px;
&__header {
width: 100%;
height: 15%;
display: flex;
align-items: center;
justify-content: center;
h3 {
display: block;
font-size: 25px;
margin: 0;
text-align: center;
font-family: "Montserrat", sans-serif;
color: rgb(100, 100, 100);
}
}
&__body {
width: 100%;
display: flex;
justify-content: space-around;
flex-direction: column;
gap: 15px;
position: relative;
&__error {
position: absolute;
font-size: 13px;
font-family: "Montserrat", sans-serif;
top: 0;
left: 10px;
color: rgb(224, 75, 75);
}
&__fio {
height: 15%;
display: flex;
justify-content: space-between;
}
}
&__footer {
div,
button {
width: 100%;
height: 40px;
}
}
}
}
}

View File

@ -1,92 +1,92 @@
.main { .main {
width: 100%; width: 100%;
height: 100%; min-height: 100%;
padding: 4% 8%; padding: 4% 8%;
} }
.wrapper { .wrapper {
width: 100%; width: 100%;
height: 100%; min-height: 100%;
} }
.panel { .panel {
width: 100%; width: 100%;
height: 10%; display: flex;
display: flex; justify-content: space-between;
justify-content: space-between; align-items: end;
align-items: center; margin-bottom: 25px;
padding: 0 5%; gap: 15px;
div:first-child {
flex-grow: 1;
input {
width: 100%;
}
}
} }
.listForms { .listForms {
box-shadow: 0 0 5px 1px rgb(200, 200, 200); border: 1px solid rgba(0, 0, 0, 0.1);
border-radius: 5px; background: #fff;
margin-top: 5%; border-radius: 16px;
height: 70%; height: 70%;
width: 100%; min-height: 700px;
&__columns { margin-bottom: 50px;
display: flex; width: 100%;
justify-content: start; &__columns {
align-items: center; display: flex;
padding: 0 2%; justify-content: start;
height: 15%; align-items: center;
width: 100%; padding: 20px 25px;
border-bottom: 1px solid rgb(220, 220, 220); border-bottom: 1px solid rgb(220, 220, 220);
&__item { &__item {
font-size: 15px; font-size: 15px;
font-family: "Montserrat", sans-serif; font-family: "Montserrat", sans-serif;
} font-weight: bold;
} }
&__forms { }
width: 100%; &__forms {
height: 85%; min-height: 100%;
overflow-y: auto; display: inline;
&::-webkit-scrollbar { overflow-y: auto;
width: 7px; &__item {
display: flex;
justify-content: start;
align-items: center;
width: 100%;
padding: 20px 25px;
font-family: "Montserrat", sans-serif;
position: relative;
border-bottom: 1px solid rgba(0, 0, 0, 0.05);
&:hover {
background-color: rgba(240, 240, 240, 0.8);
}
&__title {
text-align: center;
cursor: pointer;
&:hover {
text-decoration: underline;
} }
&::-webkit-scrollbar-thumb { }
background-color: rgb(200, 200, 200); &__answers {
} width: 33.3%;
&__item { text-align: center;
display: flex; }
justify-content: start; &__update {
align-items: center; width: 33.3%;
padding: 0 2%; text-align: center;
height: 25%; }
width: 100%; i {
font-family: "Montserrat", sans-serif; position: absolute;
position: relative; font-size: 15px;
&:hover { right: 30px;
background-color: rgba(240, 240, 240, 0.8); top: calc(50% - 7px);
} cursor: pointer;
&__title { }
// width: 33.3%; ul {
text-align: center; li {
cursor: pointer; cursor: pointer;
&:hover {
text-decoration: underline;
}
}
&__answers {
width: 33.3%;
text-align: center;
}
&__update {
width: 33.3%;
text-align: center;
}
i {
position: absolute;
font-size: 15px;
right: 30px;
top: calc(50% - 7px);
cursor: pointer;
}
ul {
li {
cursor: pointer;
}
}
} }
}
} }
} }
}

View File

@ -2,18 +2,47 @@
padding: 10px 20px; padding: 10px 20px;
border-top: 1px solid rgb(200, 200, 200); border-top: 1px solid rgb(200, 200, 200);
&__question { &__question {
width: 100%;
overflow-x: auto;
margin-bottom: 10px;
&__text { &__text {
font-size: 15px; font-size: 15px;
font-family: "Montserrat", sans-serif; font-family: "Montserrat", sans-serif;
width: 100%;
} }
&__comment { &__comment {
font-size: 11px; font-size: 11px;
font-family: "Montserrat", sans-serif; font-family: "Montserrat", sans-serif;
font-style: italic; font-style: italic;
color: rgb(200, 200, 200); color: rgb(200, 200, 200);
width: 100%;
}
&__video {
iframe {
}
&__error {
font-size: 12px;
color: rgb(220, 105, 105);
}
}
&::-webkit-scrollbar {
height: 7px;
border: 1px solid rgb(200, 200, 200);
}
&::-webkit-scrollbar-thumb {
background-color: rgb(200, 200, 200);
} }
} }
&__answer { &__answer {
&__file {
margin-right: 10px;
color: rgb(105, 136, 220);
text-decoration: none;
&:hover {
text-decoration: underline;
color: rgb(53, 88, 182);
}
}
} }
} }

View File

@ -1,68 +1,83 @@
.main { .main {
width: 100%; width: 100%;
height: 100%; height: 100%;
} }
.wrapper { .wrapper {
width: 100%; width: 100%;
height: 100%; height: 100%;
display: flex; display: flex;
flex-direction: column; flex-direction: column;
justify-content: center; justify-content: center;
align-items: center; align-items: center;
} }
.header { .header {
width: 60%; text-align: center;
height: 30%; &__top {
text-align: center; font-size: 24px;
&__top { font-family: "Montserrat", sans-serif;
font-size: 30px; font-weight: bold;
font-family: "Montserrat", sans-serif; color: #000;
color: rgb(100, 100, 100); }
} &__bottom {
&__bottom { margin-top: 25px;
font-size: 30px; font-size: 24px;
font-family: "Montserrat", sans-serif; font-family: "Montserrat", sans-serif;
color: rgb(100, 100, 100); font-weight: bold;
} color: #000;
}
} }
.content { .content {
width: 60%; width: 500px;
height: 50%; margin-top: 50px;
box-shadow: 0 0 5px 1px rgb(180, 180, 180); box-sizing: border-box;
padding: 1% 0; padding: 35px 25px;
&__title { background: #fff;
height: 30%; border: 1px solid rgba(255, 255, 255, 0.1);
display: flex; border-radius: 16px;
justify-content: center; display: flex;
align-items: center; flex-direction: column;
&__wrapper { gap: 25px;
h3 { &__title {
font-size: 35px; display: flex;
font-family: "Montserrat", sans-serif; justify-content: center;
color: rgb(100, 100, 100); align-items: center;
} &__wrapper {
} h3 {
font-size: 24px;
font-family: "Montserrat", sans-serif;
color: #000;
font-weight: normal;
margin: 0;
}
} }
&__token { }
height: 50%; &__token {
display: flex; display: flex;
justify-content: center; justify-content: center;
align-items: center; align-items: center;
&__wrapper { &__wrapper {
height: 35%; height: 40px;
width: 80%; width: 100%;
} input {
height: 40px;
}
input::placeholder {
color: rgba(0, 0, 0, 0.25);
}
} }
&__search { }
height: 20%; &__search {
display: flex; height: 40px;
justify-content: center; display: flex;
align-items: center; justify-content: center;
&__wrapper { align-items: center;
height: 70%; div,
} button {
width: 100%;
height: 40px;
} }
} }
}

View File

@ -0,0 +1,121 @@
.main {
width: 100%;
height: 100%;
padding: 4% 8%;
}
.wrapper {
width: 100%;
height: 100%;
}
.header {
display: flex;
justify-content: space-between;
width: 100%;
height: 8%;
&__listInput {
width: 40%;
height: 100%;
display: flex;
justify-content: space-between;
align-items: center;
&__date {
width: 100%;
height: 100%;
display: flex;
justify-content: space-between;
position: relative;
span {
position: absolute;
font-size: 8px;
font-family: "Montserrat", sans-serif;
top: -40%;
left: 2%;
}
}
&__title {
width: 100%;
height: 100%;
display: flex;
justify-content: space-between;
position: relative;
span {
position: absolute;
font-size: 8px;
font-family: "Montserrat", sans-serif;
top: -40%;
left: 2%;
}
}
}
&__listBtn {
display: flex;
justify-content: end;
width: 30%;
}
}
.tags {
width: 100%;
margin: 30px 0;
&__wrapper {
width: 100%;
height: 100%;
display: flex;
flex-direction: column;
align-items: start;
&__input {
position: relative;
&__title {
top: -40%;
left: 2px;
position: absolute;
font-size: 8px;
font-family: "Montserrat", sans-serif;
color: rgb(100, 100, 100);
}
}
&__list {
width: 100%;
// height: 100%;
flex-wrap: wrap;
display: flex;
justify-content: start;
align-items: center;
margin-top: 5px;
&__item {
padding: 0 5px;
border-radius: 5px;
border: 1px solid rgb(100, 100, 100);
margin: 1px 3px;
position: relative;
span {
font-size: 13px;
font-family: "Montserrat", sans-serif;
color: rgb(100, 100, 100);
}
&:hover span {
visibility: hidden;
}
&:hover i {
visibility: visible;
}
i {
visibility: hidden;
position: absolute;
top: 50%;
left: 50%;
transform: translate(-50%, -50%);
color: rgb(239, 73, 73);
cursor: pointer;
}
}
}
}
}
.content {
}

View File

@ -1,188 +1,171 @@
.main { .main {
width: 100%; width: 100%;
height: 100%; height: 100%;
padding: 4% 8%; padding: 4% 8%;
} }
.wrapper { .wrapper {
width: 100%; width: 100%;
height: 100%; height: 100%;
} }
.header { .header {
// text-align: right; display: flex;
justify-content: space-between;
width: 100%;
margin-bottom: 25px;
&__listInput {
display: flex; display: flex;
justify-content: space-between; align-items: center;
width: 100%; gap: 15px;
height: 8%; &__date {
&__listInput { width: 100%;
width: 40%; display: flex;
height: 100%; justify-content: space-between;
display: flex; position: relative;
justify-content: space-between; span {
align-items: center; position: absolute;
&__date { font-size: 15px;
width: 100%; font-family: "Montserrat", sans-serif;
height: 100%; top: -25px;
display: flex; left: 0;
justify-content: space-between; }
align-items: center;
position: relative;
span {
position: absolute;
height: 100%;
font-size: 8px;
font-family: "Montserrat", sans-serif;
top: -40%;
left: 2%;
}
input {
display: block;
height: 100%;
font-size: 15px;
font-family: "Montserrat", sans-serif;
border: 1px solid rgb(200, 200, 200);
padding: 1%;
border-radius: 5px;
}
}
&__title {
width: 100%;
height: 100%;
display: flex;
justify-content: space-between;
align-items: center;
position: relative;
span {
position: absolute;
height: 100%;
font-size: 8px;
font-family: "Montserrat", sans-serif;
top: -40%;
left: 2%;
}
input {
display: block;
height: 100%;
font-size: 15px;
font-family: "Montserrat", sans-serif;
border: 1px solid rgb(200, 200, 200);
padding: 1%;
border-radius: 5px;
}
}
} }
&__listBtn { &__title {
display: flex; width: 100%;
justify-content: space-between; display: flex;
width: 30%; justify-content: space-between;
} position: relative;
span {
position: absolute;
font-size: 15px;
font-family: "Montserrat", sans-serif;
top: -25px;
left: 0;
}
}
}
&__listBtn {
display: flex;
gap: 15px;
}
} }
.content { .content {
margin-top: 3%; width: 100%;
height: 85%; height: calc(90% - 100px);
width: 100%; background: #fff;
display: flex; border: 1px solid rgba(255, 255, 255, 0.1);
justify-content: space-between; border-radius: 16px;
&__listQuestion { display: flex;
height: 100%; justify-content: space-between;
width: 25%; margin-bottom: 50px;
&__list { &__listQuestion {
height: 100%; height: 100%;
width: 100%; width: 25%;
&__item { &__list {
height: 12.5%; height: 100%;
width: 100%; width: 100%;
display: flex; &__item {
align-items: center; padding: 15px 10px;
padding: 0 5px; width: 100%;
cursor: pointer; display: flex;
align-items: center;
cursor: pointer;
&:first-child {
border-radius: 16px 0 0 0;
}
&: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);
min-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);
}
}
&:hover { &__list {
background-color: rgba(230, 230, 230, 0.6); overflow-y: auto;
} overflow-x: hidden;
span { width: 100%;
display: block; height: 90%;
font-size: 15px; &::-webkit-scrollbar {
font-family: "Montserrat", sans-serif; width: 7px;
} }
} &::-webkit-scrollbar-thumb {
&__item:not(:last-child) { background-color: rgb(200, 200, 200);
border-bottom: 1px solid rgb(220, 220, 220); }
} &__item {
} padding: 1%;
} width: 100%;
&__newForm { border-bottom: 1px solid rgb(220, 220, 220);
box-shadow: 0 0 5px 1px rgb(220, 220, 220); display: flex;
height: 100%; justify-content: space-between;
width: 75%; align-items: center;
&__title { &:hover {
padding: 1%; background-color: rgba(230, 230, 230, 0.6);
width: 100%; cursor: pointer;
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);
}
}
}
} }
&__answer {
width: 90%;
overflow-x: auto;
&::-webkit-scrollbar {
height: 7px;
border: 1px solid rgb(200, 200, 200);
}
&::-webkit-scrollbar-thumb {
background-color: rgb(200, 200, 200);
}
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,64 +1,65 @@
.main { .main {
width: 100%; width: 100%;
height: 100%; height: 100%;
} }
.wrapper { .wrapper {
width: 100%; width: 100%;
height: 100%; height: 100%;
display: flex; display: flex;
justify-content: center; justify-content: center;
align-items: center; align-items: center;
} }
.profile { .profile {
width: 70%; width: 70%;
height: 70%; height: 70%;
box-shadow: 0 0 5px 1px rgb(200, 200, 200); background: #fff;
padding: 1.5%; border: 1px solid rgba(0, 0, 0, 0.1);
position: relative; border-radius: 16px;
&__linkAdmin { padding: 1.5%;
position: absolute; position: relative;
bottom: 103%; &__linkAdmin {
left: 10px; position: absolute;
span { bottom: 103%;
font-size: 15px; left: 10px;
font-family: "Montserrat", sans-serif; span {
color: rgb(100, 100, 100); font-size: 15px;
cursor: pointer; font-family: "Montserrat", sans-serif;
&:hover { color: rgb(100, 100, 100);
text-decoration: underline; cursor: pointer;
color: rgb(66, 68, 189); &:hover {
} text-decoration: underline;
} color: rgb(66, 68, 189);
}
} }
&__wrapper { }
&__header { &__wrapper {
width: 100%; &__header {
height: 15%; width: 100%;
display: flex; height: 15%;
justify-content: space-between; display: flex;
align-items: center; justify-content: space-between;
margin-bottom: 10px; align-items: center;
&__btn { margin-bottom: 10px;
height: 100%; &__btn {
width: 35%; height: 100%;
display: flex; display: flex;
justify-content: space-between; align-items: center;
align-items: center; gap: 15px;
} }
h3 { h3 {
font-size: 25px; font-size: 25px;
font-family: "Montserrat", sans-serif; font-family: "Montserrat", sans-serif;
color: rgb(100, 100, 100); color: rgb(100, 100, 100);
} }
}
&__body {
width: 100%;
height: 85%;
&__item {
padding: 5px;
}
}
} }
} &__body {
width: 100%;
height: 85%;
&__item {
padding: 5px;
}
}
}
}

View File

@ -0,0 +1,170 @@
.main {
width: 100%;
height: 100%;
}
.wrapper {
width: 100%;
height: 100%;
display: flex;
justify-content: center;
align-items: center;
}
.loading__wrapper {
width: 100%;
height: 80%;
display: flex;
justify-content: center;
align-items: center;
&__body {
// margin-top: 100px;
span {
}
}
}
.answers {
width: 70%;
height: 70%;
background: #fff;
border: 1px solid rgba(255, 255, 255, 0.1);
border-radius: 16px;
padding: 1.5%;
&__linkAdmin {
position: absolute;
bottom: 103%;
left: 10px;
span {
font-size: 15px;
font-family: "Montserrat", sans-serif;
color: rgb(100, 100, 100);
cursor: pointer;
&:hover {
text-decoration: underline;
color: rgb(66, 68, 189);
}
}
}
&__wrapper {
width: 100%;
height: 100%;
&__header {
width: 100%;
height: 15%;
display: flex;
justify-content: start;
align-items: center;
margin-bottom: 10px;
display: flex;
justify-content: space-between;
align-items: center;
h3 {
font-size: 25px;
font-family: "Montserrat", sans-serif;
color: rgb(100, 100, 100);
display: block;
}
}
&__body {
width: 100%;
height: 85%;
overflow-y: auto;
border-bottom: 1px solid rgb(200, 200, 200);
&::-webkit-scrollbar {
width: 7px;
}
&::-webkit-scrollbar-thumb {
background-color: rgb(200, 200, 200);
}
&__column {
font-size: 15px;
width: 100%;
font-family: "Montserrat", sans-serif;
padding: 5px 30px 5px 5px;
border-bottom: 1px solid rgb(200, 200, 200);
&__wrapper {
display: flex;
justify-content: space-between;
align-items: center;
width: 90%;
}
}
&__item {
padding: 0 30px 0 5px;
// border-bottom: 1px solid rgb(200, 200, 200);
font-size: 15px;
font-family: "Montserrat", sans-serif;
height: 20%;
width: 100%;
display: flex;
justify-content: space-between;
align-items: center;
&:hover {
background-color: rgba(230, 230, 230, 0.6);
}
&__wrapper {
display: flex;
justify-content: space-between;
align-items: center;
width: 90%;
height: 100%;
&__title {
cursor: pointer;
&:hover {
text-decoration: underline;
}
}
&__token {
}
}
&__btn {
width: 10%;
display: flex;
justify-content: space-around;
align-items: center;
padding: 0 5px;
font-size: 13px;
i {
display: block;
cursor: pointer;
}
i:first-child:hover {
color: rgb(42, 64, 187);
}
i:last-child:hover {
color: rgb(199, 73, 73);
}
}
}
}
}
}
.myModal {
&__item {
padding: 5px;
border-bottom: 1px solid rgb(200, 200, 200);
font-size: 15px;
font-family: "Montserrat", sans-serif;
cursor: pointer;
&__question {
&__text {
font-size: 15px;
font-family: "Montserrat", sans-serif;
}
&__comment {
font-size: 11px;
font-family: "Montserrat", sans-serif;
font-style: italic;
color: rgb(200, 200, 200);
}
}
&__answer {
&__text {
font-size: 15px;
font-family: "Montserrat", sans-serif;
}
}
}
}

View File

@ -0,0 +1,159 @@
.main {
width: 100%;
height: 100%;
display: flex;
justify-content: center;
align-items: center;
}
.wrapper {
width: 90%;
height: 90%;
}
.header {
width: 100%;
// height: 10%;
&__wrapper {
width: 100%;
height: 100%;
background: #fff;
border: 1px solid rgba(255, 255, 255, 0.1);
border-bottom: none;
border-radius: 5px 5px 0 0;
padding: 0 5px;
&__article {
width: 100%;
height: 100%;
display: flex;
justify-content: space-between;
align-items: center;
&__title {
width: 20%;
height: 75%;
display: flex;
border-right: 1px solid rgb(180, 180, 180);
justify-content: center;
align-items: center;
position: relative;
&__name {
position: absolute;
top: -20%;
left: 3px;
font-size: 8px;
font-family: "Montserrat", sans-serif;
}
&__text {
font-size: 15px;
font-family: "Montserrat", sans-serif;
}
}
&__tags {
width: 60%;
height: 75%;
display: flex;
justify-content: start;
align-items: center;
flex-wrap: wrap;
padding: 5px;
position: relative;
overflow-y: auto;
// &::-webkit-scrollbar {
// width: 10px;
// }
// &::-webkit-scrollbar-thumb {
// background-color: rgb(200, 200, 200);
// }
// &::-webkit-scrollbar-button:single-button {
// background-color: #bbbbbb;
// display: block;
// border-style: solid;
// height: 10px;
// width: 16px;
// }
&__item {
padding: 0 5px;
border-radius: 5px;
border: 1px solid rgb(100, 100, 100);
margin: 1px 3px;
span {
}
}
}
&__owner {
width: 20%;
height: 75%;
border-left: 1px solid rgb(180, 180, 180);
display: flex;
justify-content: center;
align-items: center;
position: relative;
&__name {
position: absolute;
top: -20%;
right: 3px;
font-size: 8px;
font-family: "Montserrat", sans-serif;
}
&__text {
font-size: 15px;
font-family: "Montserrat", sans-serif;
}
}
}
&__tags {
width: 100%;
height: 50%;
display: flex;
justify-content: start;
align-items: center;
margin-top: 20px;
&__item {
padding: 0 5px;
border-radius: 5px;
border: 1px solid rgb(100, 100, 100);
margin: 1px 3px;
span {
}
}
}
}
}
.content {
width: 100%;
height: 90%;
&__wrapper {
width: 100%;
height: 100%;
background: #fff;
border: 1px solid rgba(255, 255, 255, 0.1);
padding: 5px;
border-radius: 0 0 5px 5px;
overflow: auto;
&::-webkit-scrollbar {
width: 7px;
}
&::-webkit-scrollbar {
height: 7px;
}
&::-webkit-scrollbar-thumb {
background-color: rgb(200, 200, 200);
}
&__article {
max-width: 100%;
height: 100%;
}
}
}
// .image {
// width: 400px;
// height: 200px;
// border: 4px solid red
// img {
// width: 400px;
// height: 200px;
// aspect-ratio: 1000/700;
// }
// }

View File

@ -1,74 +1,77 @@
.main { .main {
width: 100%; width: 100%;
height: 100%; height: 100%;
} }
.wrapper { .wrapper {
width: 100%; width: 100%;
height: 100%; height: 100%;
display: flex; display: flex;
justify-content: center; justify-content: center;
align-items: center; align-items: center;
flex-direction: column; flex-direction: column;
} }
.form { .form {
overflow-y: auto; overflow-y: auto;
width: 70%; width: 70%;
height: 80%; height: 80%;
border: 2px solid rgb(220, 220, 220); background: #fff;
&::-webkit-scrollbar { border: 1px solid rgba(255, 255, 255, 0.1);
width: 7px; border-radius: 16px 16px 0 0;
border-left: 1px solid rgb(200, 200, 200); &::-webkit-scrollbar {
} width: 7px;
&::-webkit-scrollbar-thumb { border-left: 1px solid rgb(200, 200, 200);
background-color: rgb(200, 200, 200); }
} &::-webkit-scrollbar-thumb {
&__header { background-color: rgb(200, 200, 200);
border-bottom: 1px solid rgb(220, 220, 220); }
width: 100%; &__header {
height: 10%; border-bottom: 1px solid rgb(220, 220, 220);
display: flex; width: 100%;
justify-content: space-between;
align-items: center;
padding: 10px 20px;
&__title {
font-size: 17px;
font-weight: bold;
font-family: "Montserrat", sans-serif;
color: rgb(100, 100, 100);
}
&__id {
}
}
&__content {
width: 100%;
height: 80%;
}
&__unexists {
width: 100%;
height: 100%;
display: flex;
justify-content: center;
align-items: center;
span {
display: block;
font-size: 18px;
font-family: "Montserrat", sans-serif;
font-style: italic;
color: rgb(200, 200, 200);
}
}
}
.footer {
width: 70%;
height: 10%; height: 10%;
display: flex; display: flex;
justify-content: space-between; justify-content: space-between;
align-items: center; align-items: center;
padding: 10px 20px; padding: 10px 20px;
border: 2px solid rgb(220, 220, 220); &__title {
border-top: none; font-size: 17px;
font-weight: bold;
font-family: "Montserrat", sans-serif;
color: rgb(100, 100, 100);
}
&__id {
}
}
&__content {
width: 100%;
height: 80%;
}
&__unexists {
width: 100%;
height: 100%;
display: flex;
justify-content: center;
align-items: center;
span {
display: block;
font-size: 18px;
font-family: "Montserrat", sans-serif;
font-style: italic;
color: rgb(200, 200, 200);
}
}
}
.footer {
background: #fff;
border: 1px solid rgba(255, 255, 255, 0.1);
border-radius: 0 0 16px 16px;
width: 70%;
height: 10%;
display: flex;
justify-content: space-between;
align-items: center;
padding: 8px 20px;
border-top: none;
} }

View File

@ -1,6 +1,7 @@
import React from "react"; import React from "react";
import classes from "../assets/styles/components/answerModal.module.scss"; import classes from "../assets/styles/components/answerModal.module.scss";
import MyButton from "./MyButton.jsx" import MyButton from "./MyButton.jsx";
import MarkDowm from "./MarkDown.jsx";
const AnswerModal = ({ const AnswerModal = ({
cleanStates, cleanStates,
@ -10,6 +11,8 @@ const AnswerModal = ({
addFormBlock, addFormBlock,
question, question,
file, file,
video,
setVideo,
listTypeAnswer, listTypeAnswer,
optionAnswer, optionAnswer,
setOptionAnswer, setOptionAnswer,
@ -22,11 +25,13 @@ const AnswerModal = ({
setQuestion, setQuestion,
setComment, setComment,
setCurrentTypeAnswer, setCurrentTypeAnswer,
setFile setFile,
removeOptionAnswer,
addFiles
}) => { }) => {
return ( return (
<div class="modal fade myModal" className={classes.myModal} id="answerModal" tabIndex="-1" aria-labelledby="exampleModalLabel" data-bs-backdrop="static" aria-hidden="true"> <div class="modal fade modal-lg myModal" className={classes.myModal} id="answerModal" 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-dialog myModal__dialog" className={classes.myModal__dialog}>
<div class="modal-content" className={classes.myModal__dialog__content}> <div class="modal-content" className={classes.myModal__dialog__content}>
<div class="modal-header" className={classes.myModal__dialog__content__header}> <div class="modal-header" className={classes.myModal__dialog__content__header}>
@ -40,37 +45,48 @@ const AnswerModal = ({
<div class="modal-body" className={classes.myModal__dialog__content__body}> <div class="modal-body" className={classes.myModal__dialog__content__body}>
<div className={classes.myModal__dialog__content__body__answer}> <div className={classes.myModal__dialog__content__body__answer}>
<span className={classes.myModal__dialog__content__body__answer__title}>Вопрос</span> <span className={classes.myModal__dialog__content__body__answer__title}>Вопрос</span>
<textarea className={classes.myModal__dialog__content__body__answer__text} value={question} onChange={event => setQuestion(event.target.value)}></textarea> <MarkDowm mkValue={question} setMkValue={setQuestion}></MarkDowm>
<label for="fileUpload" className={classes.myModal__dialog__content__body__answer__fileLabel}>Добавить файл</label>
<label for="fileUpload" className={classes.myModal__dialog__content__body__answer__listFile}>Файлы : {file ? file.map(item => `${item.name}; `) : Нет}</label>
<input <input
id="fileUpload"
type="file" type="file"
multiple multiple
accept="image/*,image/jpeg,video/mp4,video/x-m4v,video/*" accept="image/*,image/jpeg,video/mp4,video/x-m4v,video/*"
className={classes.myModal__dialog__content__body__answer__file} className={classes.myModal__dialog__content__body__answer__file}
value={file} onChange={e => addFiles(e.target.files)}
onChange={event => setFile(event.target.value)} ></input>
></input>
</div> </div>
<div className={classes.myModal__dialog__content__body__comment}> <div className={classes.myModal__dialog__content__body__comment}>
<span className={classes.myModal__dialog__content__body__comment__title}>Комментарий</span> <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> <textarea className={classes.myModal__dialog__content__body__comment__text} value={comment} onChange={event => setComment(event.target.value)}></textarea>
</div> </div>
<div className={classes.myModal__dialog__content__body__video}>
<span className={classes.myModal__dialog__content__body__video__title}>Видео</span>
<input type="text" className={classes.myModal__dialog__content__body__video__link} placeholder="Ссылка на видео" value={video} onChange={(e) => setVideo(e.target.value)}/>
</div>
{[3, 4, 5].find(item => item === currentTypeAnswer) ? <div className={classes.myModal__dialog__content__body__answerOptions}> {[3, 4, 5].find(item => item === currentTypeAnswer) ? <div className={classes.myModal__dialog__content__body__answerOptions}>
<span className={classes.myModal__dialog__content__body__answerOptions__title}>Варианты ответа</span> <span className={classes.myModal__dialog__content__body__answerOptions__title}>Варианты ответа</span>
<div className={classes.myModal__dialog__content__body__answerOptions__list}> <div className={classes.myModal__dialog__content__body__answerOptions__list}>
{optionAnswer.map((item, i) => {optionAnswer.map((item, i) =>
<span key={i} className={classes.myModal__dialog__content__body__answerOptions__list__answer}>{item.id}) {item.text}</span> <span key={i} className={classes.myModal__dialog__content__body__answerOptions__list__answer}>
{item.id}) {item.text}
<i class="fa-solid fa-xmark" onClick={() => removeOptionAnswer(item.id)}></i>
</span>
)} )}
</div> </div>
<input <input
type="text" type="text"
className={classes.myModal__dialog__content__body__answerOptions__text} className={classes.myModal__dialog__content__body__answerOptions__text}
value={currentOptionAnswer} value={currentOptionAnswer}
onChange={event => setCurrentOptionAnswer(event.target.value)}/> onChange={event => setCurrentOptionAnswer(event.target.value)}
/>
<MyButton <MyButton
text={'Добавить'} text={'Добавить'}
click={addOptionAnswer} click={addOptionAnswer}
backgroundColor={'rgb(200, 200, 200)'} class={"main__white"}
otherStyle={{padding: '1% 2%'}}/> otherStyle={{padding: '1% 2%'}}
/>
</div> : <div></div>} </div> : <div></div>}
<div className={classes.myModal__dialog__content__body__mandatory}> <div className={classes.myModal__dialog__content__body__mandatory}>
<span className={classes.myModal__dialog__content__body__mandatory__title}>Обязательный вопрос</span> <span className={classes.myModal__dialog__content__body__mandatory__title}>Обязательный вопрос</span>

View File

@ -0,0 +1,34 @@
import React from "react";
import classes from "../assets/styles/components/checkModal.module.scss";
import MyButton from "./MyButton.jsx";
const CheckModal = ({postfix, message, action}) => {
return (
<div class="modal fade" tabindex="-1" id={`checkModal${postfix}`} tabIndex="-1" aria-labelledby="exampleModalLabel" data-bs-backdrop="static" aria-hidden="true">
<div class="modal-dialog">
<div class="modal-content">
<div class="modal-header">
{/* <h5 class="modal-title">Modal title</h5> */}
<button type="button" class="btn-close" data-bs-dismiss="modal" aria-label="Close" onClick={action.cancel}></button>
</div>
<div class="modal-body">
<p>{message}</p>
</div>
<div class="modal-footer">
<MyButton text={"Отмена"} dismiss={"modal"} class={"main__white"} click={action.cancel}/>
<MyButton
text={"Да, удалить"}
backgroundColor={"rgb(229, 87, 87)"}
otherStyle={{color: "white"}}
dismiss={"modal"}
click={action.execute}
/>
</div>
</div>
</div>
</div>
)
}
export default CheckModal;

View File

@ -0,0 +1,28 @@
import React, { useState, useEffect, useContext } from "react";
import classes from "../assets/styles/components/defaultModal.module.scss";
import MyButton from "./MyButton.jsx";
const DefaultModal = ({
name,
btn,
BodyModal
}) => {
return (
<div class="modal fade myModal" className={classes.main} id={`${name}`} tabIndex="-1" aria-labelledby="exampleModalLabel" data-bs-backdrop="static" aria-hidden="true">
<div class="modal-dialog modal-dialog-centered myModal__dialog" className={classes.main__dialog}>
<div class="modal-content" className={classes.main__dialog__content}>
{BodyModal}
<div class="modal-footer myModal__dialog__content__footer" className={classes.myModal__dialog__content__footer}>
{btn.map(((item, i) =>
<MyButton text={item.text} dismiss={item.dismiss} class={item.class} click={item.action} key={i}/>
))}
</div>
</div>
</div>
</div>
)
}
export default DefaultModal;

View File

@ -1,13 +1,44 @@
import React, { useState } from "react"; import React, { useState } from "react";
import classes from "../assets/styles/generatingFormFields.module.scss"; import classes from "../assets/styles/generatingFormFields.module.scss";
import MarkdownEditor from "@uiw/react-markdown-editor";
import { parseTotalLinkVideo } from "../hooks/sundry/parseLinkVideo";
const GeneratingFormFields = ({listBlock, listTypeAnswer, answers, updateAnswersForm}) => { const GeneratingFormFields = ({listBlock, listTypeAnswer, answers, updateAnswersForm}) => {
const getUint8Array = (binary) => {
let bytesArray = []
let len = binary.length
for (let i = 0; i < len; i++) {
bytesArray.push(binary.charCodeAt(i))
}
return [new Uint8Array(bytesArray)]
}
return ( return (
listBlock.map((item, i) => listBlock.map((item, i) =>
<div className={classes.item} key={i}> <div className={classes.item} key={i}>
<div className={classes.item__question}> <div className={classes.item__question}>
<p className={classes.item__question__text}>{i + 1}) {item.question}</p> <p className={classes.item__question__text}><MarkdownEditor.Markdown source={`${i + 1}) ${item.question}`}/></p>
<p className={classes.item__question__comment}>{item.comment}</p> <p className={classes.item__question__comment}>{item.comment}</p>
<div className={classes.item__question__video}>
{item.video ?
parseTotalLinkVideo(item.video).check ?
<iframe
width="300"
height="150"
src={parseTotalLinkVideo(item.video).parseLink}
frameborder="0"
allow="accelerometer; autoplay; clipboard-write; encrypted-media; gyroscope; picture-in-picture; web-share"
referrerpolicy="strict-origin-when-cross-origin"
allowfullscreen
></iframe> :
<span className={classes.item__question__video__error}>
Данный сервис неподдерживает использование их видео.
</span> :
<span></span>}
</div>
</div> </div>
<div className={classes.item__answer}> <div className={classes.item__answer}>
{ {
@ -18,6 +49,13 @@ const GeneratingFormFields = ({listBlock, listTypeAnswer, answers, updateAnswers
updateAnswersForm: updateAnswersForm ? updateAnswersForm : false updateAnswersForm: updateAnswersForm ? updateAnswersForm : false
}) })
} }
{
item.file.map(f => <a className={classes.item__answer__file} download={f.name} href={
URL.createObjectURL(
new Blob(getUint8Array(atob(f.binary)), { type: f.type })
)
}>{f.name}</a>)
}
</div> </div>
</div> </div>
) )

View File

@ -0,0 +1,15 @@
import React from "react";
import classes from "../assets/styles/components/loading.module.scss";
const Loading = () => {
return (
<div className={classes.loading__wrapper}>
<div class="spinner-border text-dark" className={classes.loading__wrapper__body} role="status">
<span class="visually-hidden">Загрузка...</span>
</div>
</div>
)
}
export default Loading;

View File

@ -0,0 +1,19 @@
import React from 'react';
import MarkdownEditor from '@uiw/react-markdown-editor';
import classes from "../assets/styles/components/markDown.module.scss"
import '@mdxeditor/editor/style.css';
const MarkDown = ({mkValue, setMkValue}) => {
return (
<div className={classes.main} data-color-mode="light">
<MarkdownEditor
value={mkValue}
onChange={(value, viewUpdate) => setMkValue(value)}
enablePreview={false}
visibleDragbar={false}
/>
</div>
)
}
export default MarkDown;

View File

@ -4,15 +4,16 @@ import classes from "../assets/styles/components/myButton.module.scss"
const MyButton = (props) => { const MyButton = (props) => {
return ( return (
<div className={classes.main}> <div className={classes.main} style={props.mainStyle}>
<button <button
type="button" type="button"
class={props.class} className={classes[props.class]}
onClick={props.click} onClick={props.click}
data-bs-target={props.target} data-bs-target={props.target}
data-bs-toggle={props.toggle} data-bs-toggle={props.toggle}
data-bs-dismiss={props.dismiss} data-bs-dismiss={props.dismiss}
style={{backgroundColor: props.backgroundColor, ...props.otherStyle}}> style={{backgroundColor: props.backgroundColor, ...props.otherStyle}}
>
{props.text} {props.text}
</button> </button>
</div> </div>

View File

@ -1,16 +1,28 @@
import React from "react"; import React from "react";
import classes from "../assets/styles/components/myInput.module.scss" import { InputMask } from "primereact/inputmask";
import classes from "../assets/styles/components/myInput.module.scss";
const MyInput = (props) => { const MyInput = (props) => {
return ( return (
<div className={classes.main} style={{...props.otherMainStyle}}> <div className={classes.main} style={{...props.otherMainStyle}}>
<input {
type={props.type} props.mask ?
placeholder={props.placeholder} <InputMask
style={{...props.otherInputStyle}} type={props.type}
onChange={(e) => props.change(e.target.value)} placeholder={props.placeholder}
value={props.value} style={{...props.otherInputStyle}}
/> onChange={(e) => props.change(e.target.value)}
value={props.value}
mask={props.mask}
/> :
<input
type={props.type}
placeholder={props.placeholder}
style={{...props.otherInputStyle}}
onChange={(e) => props.change(e.target.value)}
value={props.value}
/>
}
</div> </div>
) )
} }

View File

@ -0,0 +1,11 @@
import React from "react";
import QRCode from "react-qr-code";
const MyQrCode = ({value}) => {
return (
<QRCode value={value}/>
)
}
export default MyQrCode

View File

@ -10,9 +10,15 @@ const NavBar = ({navigate, auth, setAuth}) => {
<div className={classes.menu}> <div className={classes.menu}>
<div className={classes.menu__authorized}> <div className={classes.menu__authorized}>
<span onClick={() => navigate("/")}>Главная</span> <span onClick={() => navigate("/")}>Главная</span>
{auth ? {
auth.is_admin ? <span onClick={() => navigate("/forms")}>Мои формы</span> : <span></span> : auth ?
<span></span>} auth.is_admin ?
<><span onClick={() => navigate("/forms")}>Мои формы</span>
<span onClick={() => navigate("/admin")}>Админ панель</span>
<span onClick={() => navigate("/articles")}>Мои статьи</span></> :
<span></span> :
<span></span>
}
</div> </div>
</div> </div>
<div className={classes.profile}> <div className={classes.profile}>

View File

@ -0,0 +1,57 @@
import React, { Component } from 'react';
import { CKEditor } from '@ckeditor/ckeditor5-react';
import ClassicEditor from '@ckeditor/ckeditor5-build-classic';
const TextEditor = ({data, setData}) => {
function uploadAdapter(loader) {
return {
upload: () => {
return new Promise(async (resolve, reject) => {
try {
const file = await loader.file;
const buff = await file.arrayBuffer()
const bytesArray = new Uint8Array(buff)
const bytesString = btoa(String.fromCharCode.apply(null, bytesArray))
resolve({
default: `data:image/jpg;base64, ${bytesString}`
});
} catch (error) {
reject("Hello");
}
});
}
}
}
function uploadPlugin(editor) {
editor.plugins.get("FileRepository").createUploadAdapter = (loader) => {
return uploadAdapter(loader);
};
}
return (
<div className="App">
<h2>Редактор статьи</h2>
<CKEditor
editor={ ClassicEditor }
data={data}
config = {{
extraPlugins: [uploadPlugin],
mediaEmbed: {previewsInData: true }
}}
onReady={ editor => {
// You can store the "editor" and use when it is needed.
console.log( 'Editor is ready to use!', editor );
} }
onChange={ ( event, editor ) => {
setData(editor.getData())
}}
onBlur={ ( event, editor ) => {}}
onFocus={ ( event, editor ) => {}}
/>
</div>
);
}
export default TextEditor

View File

@ -0,0 +1,14 @@
import React from "react";
import classes from "../../assets/styles/components/bodyModal/editToken.module.scss";
import MyInput from "../MyInput.jsx";
const EditToken = ({titleToken, setTitleToken}) => {
return (
<div className={classes.main}>
<MyInput type={"text"} value={titleToken} change={setTitleToken} placeholder={"Название"}/>
</div>
)
}
export default EditToken

View File

@ -0,0 +1,142 @@
import React, {useState, useRef} from "react";
import classes from "../../assets/styles/components/bodyModal/editUserAdminPanel.module.scss";
import MyInput from "../../components/MyInput.jsx";
const EditUserAdminPanel = ({
id,
email,
firstName,
isAdmin,
isTeacher,
lastName,
login,
phone,
pass,
setEmail,
setFirstName,
setIsAdmin,
setIsTeacher,
setLastName,
setLogin,
setPhone,
setPass
}) => {
return (
<div className={classes.main}>
<MyInput
type={"text"}
value={login}
placeholder={"Логин"}
change={setLogin}
otherMainStyle={{width: "100%", height: "100%", marginTop: "1%"}}
otherInputStyle={{width: "100%", height: "10%"}}
/>
<MyInput
type={"text"}
mask={"9 (999) 999-99-99"}
value={phone}
placeholder={"Телефон"}
change={setPhone}
otherMainStyle={{width: "100%", height: "100%", marginTop: "1%"}}
otherInputStyle={{width: "100%", height: "10%"}}
/>
<MyInput
type={"text"}
value={firstName}
placeholder={"Имя"}
change={setFirstName}
otherMainStyle={{width: "100%", height: "100%", marginTop: "1%"}}
otherInputStyle={{width: "100%", height: "10%"}}
/>
<MyInput
type={"text"}
value={lastName}
placeholder={"Фамилия"}
change={setLastName}
otherMainStyle={{width: "100%", height: "100%", marginTop: "1%"}}
otherInputStyle={{width: "100%", height: "10%"}}
/>
<MyInput
type={"text"}
value={email}
placeholder={"email"}
change={setEmail}
otherMainStyle={{width: "100%", height: "100%", marginTop: "1%"}}
otherInputStyle={{width: "100%", height: "10%"}}
/>
<MyInput
type={"text"}
value={pass}
placeholder={"Пароль"}
change={setPass}
otherMainStyle={{width: "100%", height: "100%", marginTop: "1%"}}
otherInputStyle={{width: "100%", height: "10%"}}
/>
<div className={classes.main__status}>
<div className={classes.main__status__admin}>
<div className={classes.main__status__admin__text}>Администратор</div>
<div className={classes.main__status__admin__checkbox}>
<div class="form-check">
<input
class="form-check-input"
type="radio"
name="admin"
id="choiceYes"
value="1"
checked={isAdmin}
onChange={() => setIsAdmin(true)}
/>
<label class="form-check-label" for="choiceYes">Да</label>
</div>
<div class="form-check">
<input
class="form-check-input"
type="radio"
name="admin"
id="choiceNo"
value="2"
checked={!isAdmin}
onChange={() => setIsAdmin(false)}
/>
<label class="form-check-label" for="choiceNo">Нет</label>
</div>
</div>
</div>
<div className={classes.main__status__teacher}>
<div className={classes.main__status__admin__text}>Учитель</div>
<div className={classes.main__status__admin__checkbox}>
<div class="form-check">
<input
class="form-check-input"
type="radio"
name="teacher"
id="choiceYes"
value="1"
checked={isTeacher}
onChange={() => setIsTeacher(true)}
/>
<label class="form-check-label" for="choiceYes">Да</label>
</div>
<div class="form-check">
<input
class="form-check-input"
type="radio"
name="teacher"
id="choiceNo"
value="2"
checked={!isTeacher}
onChange={() => setIsTeacher(false)}
/>
<label class="form-check-label" for="choiceNo">Нет</label>
</div>
</div>
</div>
</div>
</div>
)
}
export default EditUserAdminPanel;

View File

@ -0,0 +1,13 @@
import React from "react";
import classes from "../../assets/styles/components/bodyModal/qrCodeToken.module.scss";
import MyQrCode from "../MyQrCode.jsx";
const QrCodeToken = ({ value }) => {
return (
<div className={classes.main}>
<MyQrCode value={`http://minerva.krbl.ru/forms/${value}`} />
</div>
);
};
export default QrCodeToken;

View File

@ -2,7 +2,7 @@ import React, { useState } from "react";
import classes from "../../assets/styles/components/typeAnswer/dropDownList.module.scss" import classes from "../../assets/styles/components/typeAnswer/dropDownList.module.scss"
const DropDownList = ({postfix, optionAnswer, answers, updateAnswersForm}) => { const DropDownList = ({postfix, optionAnswer, answers, updateAnswersForm}) => {
console.log(optionAnswer) // console.log(optionAnswer)
return ( return (
<div className={classes.main}> <div className={classes.main}>
<select <select

View File

@ -1,18 +0,0 @@
import axios from "axios";
async function listUsersApi(token) {
try {
const response = await axios.get(`https://api.minerva.krbl.ru/auth/manage/users`,
{
headers: {
"Authorization": `Token ${token}`
}
})
return response
}
catch (e) {
return e
}
};
export {listUsersApi}

View File

@ -0,0 +1,36 @@
import axios from "axios";
async function getAnswersApi(token, formToken) {
try {
const response = await axios.get(`https://api.minerva.krbl.ru/formBuilder/edit/${formToken}/answers`,
{
headers: {
"Authorization": `Token ${token}`
}
})
return response
}
catch (e) {
return e.response
}
};
async function removeAnswerFormApi(token, formId, answerId) {
try {
const response = await axios.delete(`https://api.minerva.krbl.ru/formBuilder/edit/${formId}/answers`,
{
headers: {
"Authorization": `Token ${token}`
},
data: {
"answer_id": answerId
}
})
return response
}
catch (e) {
return e.response
}
};
export { getAnswersApi, removeAnswerFormApi }

144
src/hooks/api/articleApi.js Normal file
View File

@ -0,0 +1,144 @@
import axios from "axios";
async function createArticleApi(token, data) {
try {
const response = await axios.post(`https://api.minerva.krbl.ru/articles/new`,
{
"tags": [],
"title": "Новая статья"
},
{
headers: {
"Authorization": `Token ${token}`
}
})
return response
}
catch (e) {
return e.response
}
};
async function getListArticlesApi(token) {
try {
const response = await axios.get(`https://api.minerva.krbl.ru/articles/list`,
{
headers: {
"Authorization": `Token ${token}`
}
})
return response
}
catch (e) {
return e.response
}
};
async function getArticleApi(token, articleId) {
try {
const response = await axios.get(`https://api.minerva.krbl.ru/articles/${articleId}/view`,
{
headers: {
"Authorization": `Token ${token}`
}
})
return response
}
catch (e) {
return e.response
}
};
async function editTagsApi(token, articleId, tags, flag) {
try {
const response = await axios.post(`https://api.minerva.krbl.ru/articles/edit/${articleId}/tags`,
{
"delete_flag": flag,
"tags": tags,
},
{
headers: {
"Authorization": `Token ${token}`
}
})
return response
}
catch (e) {
return e.response
}
};
async function editTitleArticleApi(token, articleId, title) {
console.log('set')
try {
const response = await axios.post(`https://api.minerva.krbl.ru/articles/edit/${articleId}/setTitle`,
{
"title": title,
},
{
headers: {
"Authorization": `Token ${token}`
}
})
return response
}
catch (e) {
return e.response
}
};
async function addArticleApi(token, articleId, content) {
console.log('add')
try {
const response = await axios.post(`https://api.minerva.krbl.ru/articles/edit/${articleId}/add`,
{
"data": content,
},
{
headers: {
"Authorization": `Token ${token}`
}
})
return response
}
catch (e) {
return e.response
}
};
async function editArticleApi(token, articleId, content) {
try {
const response = await axios.post(`https://api.minerva.krbl.ru/articles/edit/${articleId}/set`,
{
"order": 0,
"data": content,
},
{
headers: {
"Authorization": `Token ${token}`
}
})
return response
}
catch (e) {
return e.response
}
};
export {
createArticleApi,
getArticleApi,
getListArticlesApi,
editTagsApi,
editTitleArticleApi,
addArticleApi,
editArticleApi
}

View File

@ -7,7 +7,7 @@ async function logIn(login, password) {
return response return response
} }
catch (e) { catch (e) {
return e return e.response
} }
}; };
@ -32,7 +32,8 @@ async function completeRegistration(data) {
async function verifyUserApi(token=false) { async function verifyUserApi(token=false) {
if (token) { if (token) {
try { try {
const response = await axios.get("https://api.minerva.krbl.ru/auth/me", { const response = await axios.get("https://api.minerva.krbl.ru/auth/me",
{
headers: { headers: {
"Authorization": `Token ${token}`, "Authorization": `Token ${token}`,
}, },
@ -40,7 +41,7 @@ async function verifyUserApi(token=false) {
return response return response
} }
catch(e) { catch(e) {
return e return e.response
} }
} }
return false return false

View File

@ -11,7 +11,7 @@ async function listFormBlockApi(token, formId) {
return response return response
} }
catch (e) { catch (e) {
return e return e.response
} }
} }
@ -29,15 +29,16 @@ async function addFormBlockApi(token, formId, data) {
return response return response
} }
catch (e) { catch (e) {
return e return e.response
} }
}; };
async function updateBlockApi(token, blockId, data) { async function updateBlockApi(token, formId, data, order) {
try { try {
const response = await axios.post(`https://api.minerva.krbl.ru/formBuilder/edit/${blockId}/set`, const response = await axios.post(`https://api.minerva.krbl.ru/formBuilder/edit/${formId}/set`,
{ {
"data": data order: order,
data: data
}, },
{ {
headers: { headers: {
@ -47,7 +48,26 @@ async function updateBlockApi(token, blockId, data) {
return response return response
} }
catch (e) { catch (e) {
return e return e.response
}
};
async function removeBlockApi(token, formId, order) {
try {
const response = await axios.post(`https://api.minerva.krbl.ru/formBuilder/edit/${formId}/set`,
{
order: order,
delete_flag: true
},
{
headers: {
"Authorization": `Token ${token}`,
}
})
return response
}
catch (e) {
return e.response
} }
}; };
@ -66,7 +86,7 @@ async function updateOrderBlockApi(token, formId, data) {
return response return response
} }
catch (e) { catch (e) {
return e return e.response
} }
} }
@ -84,7 +104,7 @@ async function saveFormApi(token) {
return response return response
} }
catch (e) { catch (e) {
return e return e.response
} }
}; };
@ -99,7 +119,7 @@ async function listFormBlockByTokenApi(token, formToken) {
return response return response
} }
catch (e) { catch (e) {
return e return e.response
} }
}; };
@ -111,16 +131,20 @@ async function saveAnswersApi(token, formToken, data) {
"Authorization": `Token ${token}` "Authorization": `Token ${token}`
} }
}) })
console.log("saveAnswersApi", response)
return response return response
} }
catch (e) { catch (e) {
return e return e.response
} }
}; };
async function getAnswersApi(token, formToken) { async function getBlockApi(token, formId, order) {
try { try {
const response = await axios.get(`https://api.minerva.krbl.ru/formBuilder/edit/${formToken}/answers`, const response = await axios.post(`https://api.minerva.krbl.ru/formBuilder/edit/${formId}/get`,
{
"order": order
},
{ {
headers: { headers: {
"Authorization": `Token ${token}` "Authorization": `Token ${token}`
@ -129,8 +153,18 @@ async function getAnswersApi(token, formToken) {
return response return response
} }
catch (e) { catch (e) {
return e return e.response
} }
}; };
export { addFormBlockApi, listFormBlockApi, saveFormApi, updateBlockApi, updateOrderBlockApi, listFormBlockByTokenApi, saveAnswersApi, getAnswersApi } export {
addFormBlockApi,
listFormBlockApi,
saveFormApi,
updateBlockApi,
updateOrderBlockApi,
listFormBlockByTokenApi,
saveAnswersApi,
removeBlockApi,
getBlockApi
}

View File

@ -11,7 +11,7 @@ async function listFormsApi(token) {
return response return response
} }
catch (e) { catch (e) {
return e return e.response
} }
}; };
@ -26,10 +26,11 @@ async function createFormApi(token) {
"Authorization": `Token ${token}`, "Authorization": `Token ${token}`,
} }
}) })
console.log("createFormApi", response)
return response return response
} }
catch (e) { catch (e) {
return e return e.response
} }
}; };
@ -43,7 +44,7 @@ async function removeFormApi(token, formId) {
return response return response
} }
catch (e) { catch (e) {
return e return e.response
} }
}; };
@ -61,13 +62,16 @@ async function updateTitleFormApi(token, formId, title) {
return response return response
} }
catch (e) { catch (e) {
return e return e.response
} }
}; };
async function newFormTokenApi(token, formId) { async function newFormTokenApi(token, formId, name) {
try { try {
const response = await axios.post(`https://api.minerva.krbl.ru/formBuilder/edit/${formId}/access/new`, {}, const response = await axios.post(`https://api.minerva.krbl.ru/formBuilder/edit/${formId}/access/new`,
{
"name": name
},
{ {
headers: { headers: {
"Authorization": `Token ${token}` "Authorization": `Token ${token}`
@ -76,7 +80,7 @@ async function newFormTokenApi(token, formId) {
return response return response
} }
catch (e) { catch (e) {
return e return e.response
} }
}; };
@ -88,11 +92,59 @@ async function listFormsByTokenApi(token, formId) {
"Authorization": `Token ${token}` "Authorization": `Token ${token}`
} }
}) })
console.log(response)
return response return response
} }
catch (e) { catch (e) {
return e return e.response
} }
}; };
export { listFormsApi, createFormApi, removeFormApi, updateTitleFormApi, newFormTokenApi, listFormsByTokenApi }; async function removeTokenFormApi(token, formId, formToken) {
try {
const response = await axios.post(`https://api.minerva.krbl.ru/formBuilder/edit/${formId}/access/revoke`,
{
"token_id": formToken
},
{
headers: {
"Authorization": `Token ${token}`
}
})
return response
}
catch (e) {
return e.response
}
};
async function editTokenFormApi(token, formId, formToken, name) {
try {
const response = await axios.post(`https://api.minerva.krbl.ru/formBuilder/edit/${formId}/access/revoke`,
{
"token_id": formToken,
"is_active": true,
"name": name
},
{
headers: {
"Authorization": `Token ${token}`
}
})
return response
}
catch (e) {
return e.response
}
};
export {
listFormsApi,
createFormApi,
removeFormApi,
updateTitleFormApi,
newFormTokenApi,
listFormsByTokenApi,
removeTokenFormApi,
editTokenFormApi
};

View File

@ -6,8 +6,9 @@ async function editUserApi(token, data) {
{ {
"email": data.email, "email": data.email,
"first_name": data.first_name, "first_name": data.first_name,
"id": "6617fec3f603c43e1719d3c8", "id": data.id,
"is_admin": data.is_admin, "is_admin": data.is_admin,
"is_teacher": data.is_teacher,
"last_name": data.last_name, "last_name": data.last_name,
"login": data.login, "login": data.login,
"phone": data.phone "phone": data.phone
@ -17,15 +18,97 @@ async function editUserApi(token, data) {
"Authorization": `Token ${token}`, "Authorization": `Token ${token}`,
}, },
}) })
console.log(response)
return response return response
} }
catch(e) { catch(e) {
console.log(e) return e.response
return e }
} };
// return false
async function getListUserApi(token) {
try {
const response = await axios.get(`https://api.minerva.krbl.ru/auth/manage/users`,
{
headers: {
"Authorization": `Token ${token}`
}
})
return response
}
catch (e) {
return e.response
}
};
async function addUserApi(token, data) {
try {
const response = await axios.post(`https://api.minerva.krbl.ru/auth/manage/add`,
{
"email": data.email,
"first_name": data.first_name,
// "id": data.id,
"is_admin": data.is_admin,
"is_teacher": data.is_teacher,
"last_name": data.last_name,
"login": data.login,
"phone": data.phone
},
{
headers: {
"Authorization": `Token ${token}`
}
})
return response
}
catch (e) {
return e.response
}
};
async function removeUserApi(token, login) {
try {
const response = await axios.post("https://api.minerva.krbl.ru/auth/manage/delete",
{
"login": login
},
{
headers: {
"Authorization": `Token ${token}`,
},
})
return response
}
catch(e) {
return e.response
}
};
async function editUserPass(token, login, pass) {
try {
const response = await axios.post("https://api.minerva.krbl.ru/auth/manage/updatePassword",
{
"login": login,
"password": pass
},
{
headers: {
"Authorization": `Token ${token}`,
},
})
return response
}
catch(e) {
return e.response
}
} }
export {editUserApi} export {
editUserApi,
getListUserApi,
addUserApi,
removeUserApi,
editUserPass
}

View File

@ -0,0 +1,20 @@
import { verifyUserApi } from "../api/enterAccountApi";
async function verifyUser(cookies, user, setUser) {
const response = await verifyUserApi(cookies.token);
if (response) {
if (response.status === 200) {
setUser(response.data);
return response
}
else {
console.log(response)
}
}
}
export {
verifyUser
}

View File

@ -0,0 +1,75 @@
const serviceVideo = {
"www.youtube.com": parseYTlinkVideo,
"youtu.be": parseMobileYTlinkVideo,
"vk.com": parseVKlinkVideo,
"rutube.ru": parseRTlinkVideo
}
function parseYTlinkVideo(link) {
const startIndexIdVideo = link.indexOf("watch?v=") + 8;
const endIndexIdVideo = link.indexOf("&");
const idVideo = endIndexIdVideo === -1 ?
link.slice(startIndexIdVideo) :
link.slice(startIndexIdVideo, endIndexIdVideo);
return `https://www.youtube.com/embed/${idVideo}`
};
function parseMobileYTlinkVideo(link) {
const startIndexIdVideo = 17;
const endIndexIdVideo = link.indexOf("?");
const idVideo = endIndexIdVideo === -1 ?
link.slice(startIndexIdVideo) :
link.slice(startIndexIdVideo, endIndexIdVideo);
return `https://www.youtube.com/embed/${idVideo}`
};
function parseVKlinkVideo(link) {
let idVideo = "";
if (link.indexOf("video?z=video") !== -1) {
const startIndexIdVideo = link.indexOf("video?z=video") + 13;
const endIndexIdVideo = link.lastIndexOf("%");
idVideo = link.slice(startIndexIdVideo, endIndexIdVideo);
}
else if (link.indexOf("video") !== -1) {
const startIndexIdVideo = link.indexOf("video") + 5;
idVideo = link.slice(startIndexIdVideo)
}
const oid = idVideo.slice(0, idVideo.indexOf("_"));
const id = idVideo.slice(idVideo.indexOf("_") + 1);
return `https://vk.com/video_ext.php?oid=${oid}&id=${id}&hd=2`
};
function parseRTlinkVideo(link) {
const startIndexIdVideo = link.indexOf("video/") + 6;
const endIndexIdVideo = link.lastIndexOf("?r=a");
const idVideo = endIndexIdVideo === -1 ?
link.slice(startIndexIdVideo) :
link.slice(startIndexIdVideo, endIndexIdVideo);
return `https://rutube.ru/play/embed/${idVideo}`
};
function parseTotalLinkVideo(link) {
const serviceLink = {
check: false,
parseLink: ""
};
if (link) {
for (let key of Object.keys(serviceVideo)) {
if (link.indexOf(key) !== -1) {
serviceLink.parseLink = serviceVideo[key](link);
serviceLink.check = true;
}
}
}
return serviceLink
};
export { parseYTlinkVideo, parseVKlinkVideo, parseTotalLinkVideo }

View File

@ -13,11 +13,13 @@ function responseDataToListBlock(data) {
} }
for (let param of block.data) { for (let param of block.data) {
if (Array.isArray(param.Value) && param.Value.length) { if (Array.isArray(param.Value) && param.Value.length) {
newParam[param.Key] = param.Value.map(item => ({ newParam[param.Key] = param.Value.map(item => ({
id: item[0].Value, id: item.find(obj => obj.Key === "id") ? item.find(obj => obj.Key === "id").Value : null,
text: item[1].Value text: item.find(obj => obj.Key === "text") ? item.find(obj => obj.Key === "text").Value : null,
name: item.find(obj => obj.Key === "name") ? item.find(obj => obj.Key === "name").Value : null,
type: item.find(obj => obj.Key === "type") ? item.find(obj => obj.Key === "type").Value : null,
binary: item.find(obj => obj.Key === "binary") ? item.find(obj => obj.Key === "binary").Value : null,
})) }))
} }
else { else {
@ -47,6 +49,11 @@ function dateTimeParse(date) {
} }
return newDate return newDate
};
function numberingBlocks(list, key, increase = 0) {
list.forEach((value, i) => list[i][key] = i + increase)
return list
} }
export { responseDataToListBlock, dateTimeParse } export { responseDataToListBlock, dateTimeParse, numberingBlocks }

View File

@ -1,67 +1,258 @@
import React, { useState } from "react"; import React, { useState, useEffect, useContext } from "react";
import { useCookies } from "react-cookie";
import { useNavigate } from "react-router-dom"; import { useNavigate } from "react-router-dom";
import classes from "../assets/styles/adminPanel.module.scss"; import classes from "../assets/styles/adminPanel.module.scss";
import { UserData } from "../context";
import { getListUserApi, editUserApi, addUserApi, removeUserApi, editUserPass } from "../hooks/api/profileApi.js";
import { verifyUser } from "../hooks/services/profile.js";
import DefaultModal from "../components/DefaultModal.jsx";
import EditUserAdminPanel from "../components/bodyModal/EditUserAdminPanel.jsx";
import MyButton from "../components/MyButton.jsx";
import CheckModal from "../components/CheckModal.jsx";
const AdminPanel = () => { const AdminPanel = () => {
const [token, setToken] = useState("");
const navigate = useNavigate(); const navigate = useNavigate();
const [cookies, setCookies] = useCookies(["user"]);
const {user, setUser, userRef} = useContext(UserData);
const [listUser, setListUser] = useState([]);
const [users, setUsers] = useState([ const [email, setEmail] = useState("");
{ const [firstName, setFirstName] = useState("");
"email": "senya.bogachev@mail.ru", const [id, setId] = useState(false);
"first_name": "Арсений", const [isAdmin, setIsAdmin] = useState(false);
"id": "1", const [isTeacher, setIsTeacher] = useState(false);
"is_admin": false, const [lastName, setLastName] = useState("");
"last_name": "Богачев", const [login, setLogin] = useState("");
"login": "Ars", const [phone, setPhone] = useState("");
"phone": "89110128244" const [pass, setPass] = useState("");
},
{ useEffect(() => {
"email": "dasha@mail.ru", async function getListUser() {
"first_name": "Дарья", const responseListUser = await getListUserApi(cookies.token);
"id": "1",
"is_admin": false, if (
"last_name": "Утешева", responseListUser.status === 200
"login": "dasha", ) {
"phone": "89111032339" setListUser(responseListUser.data)
}, }
{ else if (
"email": "kirik@mail.ru", responseListUser.status === 401
"first_name": "Кирилл", ) {
"id": "string", // navigate("/enter")
"is_admin": false, setUser(false)
"last_name": "Жердев", }
"login": "Kirik", else {
"phone": "89110045673" console.log(responseListUser)
}
} }
])
getListUser()
}, []);
function clearState() {
setEmail("")
setFirstName("")
setId("")
setIsAdmin("")
setIsTeacher("")
setLastName("")
setLogin("")
setPhone("")
setPass("")
};
async function addUser() {
const currentState = {
email: email,
first_name: firstName,
id: id,
is_admin: isAdmin,
is_teacher: isTeacher,
last_name: lastName,
login: login,
phone: phone
}
const response = await addUserApi(cookies.token, currentState)
if (response.status === 200) {
setListUser([...listUser, currentState])
if (pass) {
await editUserPass(cookies.token, login, pass);
}
}
else if (
response.status === 401
) {
// navigate("/enter")
setUser(false)
}
else {
console.log(response)
}
clearState()
};
async function editUser() {
const currentState = {
email: email,
first_name: firstName,
id: id,
is_admin: isAdmin,
is_teacher: isTeacher,
last_name: lastName,
login: login,
phone: phone
};
const response = await editUserApi(cookies.token, currentState);
if (response.status === 200) {
setListUser(listUser.map(item => item.id === id ? currentState : item));
if (pass) {
await editUserPass(cookies.token, login, pass);
};
}
else if (
response.status === 401
) {
// navigate("/enter")
setUser(false)
}
else {
console.log(response)
}
clearState();
};
function openUser(user) {
setEmail(user.email)
setFirstName(user.first_name)
setId(user.id)
setIsAdmin(user.is_admin)
setIsTeacher(user.is_teacher)
setLastName(user.last_name)
setLogin(user.login)
setPhone(user.phone)
}
async function removeUser(login) {
const response = await removeUserApi(cookies.token, login)
if (response.status === 200) {
setListUser(listUser.filter(item => item.login !== login))
}
else if (
response.status === 401
) {
// navigate("/enter")
setUser(false)
}
else {
console.log(response)
}
}
return ( return (
<div className={classes.main}> <div className={classes.main}>
<div className={classes.wrapper}> <div className={classes.wrapper}>
<div className={classes.admin}> <div className={classes.admin}>
<div className={classes.admin__linkProfile}> {/* <div className={classes.admin__linkProfile}>
<span onClick={() => navigate("/profile")}>Профиль</span> <span onClick={() => navigate("/profile")}>Профиль</span>
</div> </div> */}
<div className={classes.admin__wrapper}> <div className={classes.admin__wrapper}>
<div className={classes.admin__wrapper__header}> <div className={classes.admin__wrapper__header}>
<h3>Пользователи</h3> <h3>Пользователи</h3>
<MyButton
text={"Создать пользователя"}
class={"main__green"}
toggle={"modal"}
target={"#adminModal"}
/>
</div> </div>
<div className={classes.admin__wrapper__body}> <div className={classes.admin__wrapper__body}>
<div className={classes.admin__wrapper__body__columns}> <div className={classes.admin__wrapper__body__columns}>
<div className={classes.admin__wrapper__body__columns__item}>Логин</div> <div className={classes.admin__wrapper__body__columns__wrapper}>
<div className={classes.admin__wrapper__body__columns__item}>Фамилия</div> <div className={classes.admin__wrapper__body__columns__item}>Логин</div>
<div className={classes.admin__wrapper__body__columns__item}>Email</div> <div className={classes.admin__wrapper__body__columns__item}>Фамилия</div>
<div className={classes.admin__wrapper__body__columns__item}>Email</div>
<div className={classes.admin__wrapper__body__columns__item}>Учитель</div>
<div className={classes.admin__wrapper__body__columns__item}>Администратор</div>
</div>
</div> </div>
<div className={classes.admin__wrapper__body__users}> <div className={classes.admin__wrapper__body__users}>
{users.map(item => {listUser.map(item =>
<div className={classes.admin__wrapper__body__users__item}> <div className={classes.admin__wrapper__body__users__item} key={item.id}>
<div className={classes.admin__wrapper__body__users__item__link}>{item.login}</div> <div className={classes.admin__wrapper__body__users__item__wrapper}>
<div className={classes.admin__wrapper__body__users__item__info}>{item.last_name}</div> <div
<div className={classes.admin__wrapper__body__users__item__info}>{item.email}</div> className={classes.admin__wrapper__body__users__item__link}
data-bs-toggle="modal" data-bs-target={`#adminModal`}
onClick={() => openUser(item)}
>
{item.login}
</div>
<div className={classes.admin__wrapper__body__users__item__info}>
{item.last_name ? item.last_name : '-'}
</div>
<div className={classes.admin__wrapper__body__users__item__info}>
{item.email ? item.email : '-'}
</div>
<div className={classes.admin__wrapper__body__users__item__info}>
{
item.is_teacher ?
<i class="fa-solid fa-circle-check" style={{color: "rgb(150, 209, 158)"}}></i> :
<i class="fa-solid fa-circle-xmark" style={{color: "rgb(235, 130, 130)"}}></i>
}
</div>
<div className={classes.admin__wrapper__body__users__item__info}>
{
item.is_admin ?
<i class="fa-solid fa-circle-check" style={{color: "rgb(150, 209, 158)"}}></i> :
<i class="fa-solid fa-circle-xmark" style={{color: "rgb(235, 130, 130)"}}></i>}
</div>
<div className={classes.admin__wrapper__body__users__item__remove}>
<i class="fa-solid fa-trash" data-bs-toggle="modal" data-bs-target={`#checkModal${item.id}`}></i>
</div>
<CheckModal
postfix={item.id}
message={`Вы хотетите удалить пользователя <${item.login}>?`}
action={{
execute: () => removeUser(item.login),
cancel: () => {}
}}
/>
</div>
</div> </div>
)} )}
<DefaultModal
name={"adminModal"}
BodyModal={
<EditUserAdminPanel
id={id}
email={email}
firstName={firstName}
isAdmin={isAdmin}
isTeacher={isTeacher}
lastName={lastName}
login={login}
phone={phone}
setEmail={setEmail}
setFirstName={setFirstName}
setIsAdmin={setIsAdmin}
setIsTeacher={setIsTeacher}
setLastName={setLastName}
setLogin={setLogin}
setPhone={setPhone}
pass={pass}
setPass={setPass}
/>
}
btn={[
{text: id ? "Изменить" : "Сохранить", dismiss: "modal", class: "main__green", action: id ? () => editUser() : () => addUser()},
{text: "Отмена", dismiss: "modal", class: "main__white", action: () => clearState()}
]}
/>
</div> </div>
</div> </div>
</div> </div>

View File

@ -3,13 +3,18 @@ import { useCookies } from "react-cookie";
import { useNavigate, useParams } from 'react-router-dom'; import { useNavigate, useParams } from 'react-router-dom';
import { answersData } from "../context"; import { answersData } from "../context";
import classes from "../assets/styles/answersForm.module.scss"; import classes from "../assets/styles/answersForm.module.scss";
import { responseDataToListBlock, dateTimeParse } from "../hooks/sundry/parseListBlock"; import { responseDataToListBlock, dateTimeParse } from "../hooks/sundry/parseListBlock.js";
import { listFormBlockApi, getAnswersApi } from "../hooks/api/formApi"; import { listFormBlockApi } from "../hooks/api/formApi.js";
import { listUsersApi } from "../hooks/api/adminApi"; import { getListUserApi } from "../hooks/api/profileApi.js";
import { removeAnswerFormApi, getAnswersApi } from "../hooks/api/answersApi.js";
import Loading from "../components/Loading.jsx";
import CheckModal from "../components/CheckModal.jsx";
import { UserData } from "../context";
const AnswersForm = () => { const AnswersForm = () => {
const { formId } = useParams(); const { formId } = useParams();
const {answersList, setAnswersList} = useContext(answersData); const {answersList, setAnswersList} = useContext(answersData);
const {user, setUser} = useContext(UserData);
const [data, setData] = useState(false); const [data, setData] = useState(false);
const [cookies, _, __] = useCookies(["user"]); const [cookies, _, __] = useCookies(["user"]);
@ -18,9 +23,7 @@ const AnswersForm = () => {
const result = [] const result = []
const responseBlocks = await listFormBlockApi(cookies.token, formId); const responseBlocks = await listFormBlockApi(cookies.token, formId);
const responseAnswers = await getAnswersApi(cookies.token, formId); const responseAnswers = await getAnswersApi(cookies.token, formId);
const listUsers = await listUsersApi(cookies.token); const listUsers = await getListUserApi(cookies.token);
console.log("responseBlocks", responseBlocks)
console.log("responseAnswers", responseAnswers)
if (responseBlocks.status === 200 && responseBlocks.data) { if (responseBlocks.status === 200 && responseBlocks.data) {
const listBlocks = responseDataToListBlock(responseBlocks.data); const listBlocks = responseDataToListBlock(responseBlocks.data);
@ -28,6 +31,7 @@ const AnswersForm = () => {
if (responseAnswers.data) { if (responseAnswers.data) {
for (let item of responseAnswers.data) { for (let item of responseAnswers.data) {
const blocks = { const blocks = {
id: item.id,
user: listUsers.data.find(user => user.id === item.user_id).login, user: listUsers.data.find(user => user.id === item.user_id).login,
date: dateTimeParse(item.date), date: dateTimeParse(item.date),
block: [] block: []
@ -35,7 +39,10 @@ const AnswersForm = () => {
if (item.data) { if (item.data) {
for (let i = 0; i < item.data.length; i++) { for (let i = 0; i < item.data.length; i++) {
blocks.block.push({answers: {id: item.data[i][0].Value, answer: item.data[i][1].Value}, question: listBlocks[i]}) blocks.block.push({
answers: {id: item.data[i].find(obj => obj.Key === "id").Value, answer: item.data[i].find(obj => obj.Key === "answer").Value},
question: listBlocks[i]
})
} }
} }
else { else {
@ -45,20 +52,42 @@ const AnswersForm = () => {
} }
result.push(blocks) result.push(blocks)
} }
console.log("result", result)
} }
setData(result) setData(result)
} }
else if (
response.status === 401
) {
// navigate("/enter")
setUser(false)
}
else { else {
console.log(responseBlocks) console.log(response)
} }
}; };
getForm() getForm()
}, []); }, []);
async function removeAnswerForm(answerId) {
const response = await removeAnswerFormApi(cookies.token, formId, answerId)
if (response.status === 200) {
setData([...data].filter(item => item.id !== answerId))
}
else if (
response.status === 401
) {
// navigate("/enter")
setUser(false)
}
else {
console.log(response)
}
}
return ( return (
<div className={classes.main}> <div className={classes.main}>
<div className={classes.wrapper}> <div className={classes.wrapper}>
@ -69,28 +98,40 @@ const AnswersForm = () => {
</div> </div>
<div className={classes.answers__wrapper__body}> <div className={classes.answers__wrapper__body}>
<div className={classes.answers__wrapper__body__column}> <div className={classes.answers__wrapper__body__column}>
<div>Логин</div> <div className={classes.answers__wrapper__body__column__wrapper}>
<div>Дата</div> <div>Логин</div>
<div>Дата</div>
</div>
</div> </div>
{data ? {data ?
data.map((item, i) => data.map((item, dataIndex) =>
<div className={classes.answers__wrapper__body__item} key={i}> <div className={classes.answers__wrapper__body__item} key={dataIndex}>
<div className={classes.answers__wrapper__body__item__user} data-bs-toggle={"modal"} data-bs-target={`#answersModal${i}`}> <div className={classes.answers__wrapper__body__item__wrapper}>
{item.user} <div className={classes.answers__wrapper__body__item__wrapper__user} data-bs-toggle={"modal"} data-bs-target={`#answersModal${dataIndex}`}>
</div> {item.user}
<div className={classes.answers__wrapper__body__item__date}> </div>
{item.date} <div className={classes.answers__wrapper__body__item__wrapper__date}>
</div> {item.date}
</div>
<i class="fa-solid fa-trash" data-bs-toggle="modal" data-bs-target={`#checkModal${item.id}`}></i>
</div>
<div class="modal fade myModal" className={classes.myModal} id={`answersModal${i}`} tabIndex="-1" aria-labelledby="exampleModalLabel" data-bs-backdrop="static" aria-hidden="true"> <CheckModal
postfix={item.id}
message={`Вы хотетите удалить ответы пользователя <${item.user}>?`}
action={{
execute: () => removeAnswerForm(item.id),
cancel: () => {}
}}
/>
<div class="modal fade myModal" className={classes.myModal} id={`answersModal${dataIndex}`} 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-dialog myModal__dialog" className={classes.myModal__dialog}>
<div class="modal-content" className={classes.myModal__dialog__content}> <div class="modal-content" className={classes.myModal__dialog__content}>
<div class="modal-body" className={classes.myModal__dialog__content__body}> <div class="modal-body" className={classes.myModal__dialog__content__body}>
{item.block.map((block, blockIndex) =>
<div className={classes.myModal__item} key={blockIndex}>
{item.block.map((block, i) =>
<div className={classes.myModal__item} key={i}>
<div className={classes.myModal__item__question}> <div className={classes.myModal__item__question}>
<p className={classes.myModal__item__question__text}>{block.question.question}</p> <p className={classes.myModal__item__question__text}>{block.question.question}</p>
<p className={classes.myModal__item__question__comment}>{block.question.comment}</p> <p className={classes.myModal__item__question__comment}>{block.question.comment}</p>
@ -98,7 +139,7 @@ const AnswersForm = () => {
<div className={classes.myModal__item__answer}> <div className={classes.myModal__item__answer}>
<p className={classes.myModal__item__question__text}>Ответ: { <p className={classes.myModal__item__question__text}>Ответ: {
Array.isArray(block.answers.answer) ? Array.isArray(block.answers.answer) ?
block.answers.answer.map(item => <span>{item}; </span>) : block.answers.answer.map((answer, indexAnswer) => <span key={indexAnswer}>{answer}; </span>) :
block.answers.answer block.answers.answer
}</p> }</p>
</div> </div>
@ -113,11 +154,7 @@ const AnswersForm = () => {
</div> </div>
) )
: :
<div className={classes.loading__wrapper}> <Loading/>}
<div class="spinner-border text-dark" className={classes.loading__wrapper__body} role="status">
<span class="visually-hidden">Загрузка...</span>
</div>
</div>}
</div> </div>
</div> </div>
</div> </div>

138
src/pages/Articles.jsx Normal file
View File

@ -0,0 +1,138 @@
import React, { useState, useContext, useEffect } from "react";
import { useCookies } from "react-cookie";
import { useNavigate } from "react-router-dom";
import classes from "../assets/styles/articles.module.scss";
import MyButton from "../components/MyButton.jsx";
import MyInput from "../components/MyInput.jsx";
import { UserData } from "../context";
import CheckModal from "../components/CheckModal.jsx";
import {
createArticleApi,
getListArticlesApi,
} from "../hooks/api/articleApi.js";
const Articles = () => {
const navigate = useNavigate();
const { user, setUser } = useContext(UserData);
const [stateLoading, setStateLoading] = useState(false);
const [listArticles, setListArticles] = useState([]);
const [cookies, _, __] = useCookies(["user"]);
useEffect(() => {
async function getListArticles() {
const response = await getListArticlesApi(cookies.token);
if (response.status === 200) {
setListArticles(response.data);
} else if (response.status === 401) {
// navigate("/enter")
setUser(false);
} else {
console.log(response);
}
}
getListArticles();
}, []);
async function createArticle() {
const response = await createArticleApi(cookies.token);
if (response.status === 200) {
navigate(`/articles/${response.data.id}/edit`);
} else if (response.status === 401) {
// navigate("/enter")
setUser(false);
} else {
console.log(response);
}
}
return (
<div className={classes.main}>
<div className={classes.wrapper}>
<div className={classes.panel}>
<MyInput placeholder={"Поиск..."} />
<MyButton
click={createArticle}
class={"main__blue"}
otherStyle={{ width: "200px" }}
text={
stateLoading ? (
<div class="spinner-border text-light" role="status">
<span class="visually-hidden">Загрузка...</span>
</div>
) : (
"Создать"
)
}
/>
</div>
<div className={classes.listArticles}>
<div className={classes.listArticles__columns}>
<div className={classes.listArticles__columns__item}>Название</div>
<div className={classes.listArticles__columns__item}>
Дата публикации
</div>
<div className={classes.listArticles__columns__item}>Автор</div>
</div>
<div className={classes.listArticles__forms}>
{listArticles.map((item) => (
<div className={classes.listArticles__forms__item}>
<div
className={classes.listArticles__forms__item__title}
onClick={() => navigate(`/articles/${item.id}/edit`)}
>
{item.title}
</div>
<div className={classes.listArticles__forms__item__date}>
24.06.24
</div>
<div className={classes.listArticles__forms__item__author}>
kuwsh1n
</div>
<i
class="fa-solid fa-ellipsis-vertical"
id="action"
data-bs-toggle="dropdown"
></i>
<ul class="dropdown-menu" aria-labelledby="action">
<li>
<a
class="dropdown-item"
onClick={() => navigate(`/articles/${item.id}/`)}
>
Открыть
</a>
</li>
<li>
<a
class="dropdown-item"
data-bs-toggle="modal"
data-bs-target={`#checkModaltest`}
>
Удалить
</a>
</li>
</ul>
<CheckModal
postfix={"test"}
message={`Вы хотетите удалить статью <Test article>?`}
action={{
execute: () => {},
cancel: () => {},
}}
/>
</div>
))}
</div>
</div>
</div>
</div>
);
};
export default Articles;

View File

@ -23,11 +23,11 @@ const EnterAccount = () => {
const [password, setPassword] = useState(""); const [password, setPassword] = useState("");
const [repiedPassword, setRepiedPassword] = useState(""); const [repiedPassword, setRepiedPassword] = useState("");
const {user, setUser} = useContext(UserData); const { user, setUser } = useContext(UserData);
const [cookies, setCookie, removeCookie] = useCookies(["user"]); const [cookies, setCookie, removeCookie] = useCookies(["user"]);
const [loginError, setLoginError] = useState(false); const [loginError, setLoginError] = useState(false);
const [regsterError, setRegsterError] = useState(false); const [regsterError, setRegsterError] = useState(false);
function cleanState() { function cleanState() {
setEmail(""); setEmail("");
@ -51,81 +51,145 @@ const EnterAccount = () => {
repiedPassword: repiedPassword, repiedPassword: repiedPassword,
}); });
if (response.status === 200) { if (response.status === 200) {
const responseMe = await verifyUserApi(response.data.token) const responseMe = await verifyUserApi(response.data.token);
setUser(responseMe.data); setUser(responseMe.data);
setCookie("token", response.data.token); setCookie("token", response.data.token);
cleanState(); cleanState();
navigate("/"); navigate("/");
} } else {
else { setRegsterError(response);
setRegsterError(response) }
} }
};
async function logInToAccount() { async function logInToAccount() {
const response = await logIn(login, password); const response = await logIn(login, password);
if (response.status === 200) { if (response.status === 200) {
setCookie("token", response.data.token); setCookie("token", response.data.token);
const responseMe = await verifyUserApi(response.data.token) const responseMe = await verifyUserApi(response.data.token);
setUser(responseMe.data) setUser(responseMe.data);
cleanState(); cleanState();
navigate("/") navigate("/");
} } else {
else { setLoginError(true);
setLoginError(true);
}
} }
}
return ( return (
<div className={classes.main}> <div className={classes.main}>
<div className={classes.wrapper}> <div className={classes.wrapper}>
<div className={classes.tabs}> <div className={classes.tabs}>
<ul class="nav nav-tabs"> <ul class="nav nav-tabs">
<li class="nav-item" onClick={() => selectTag("login")}> <li class="nav-item" onClick={() => selectTag("login")}>
<a className={enter === "login" ? "nav-link active" : "nav-link"} aria-current="page">Авторизация</a> <a
</li> className={enter === "login" ? "nav-link active" : "nav-link"}
<li class="nav-item" onClick={() => selectTag("register")}> aria-current="page"
<a className={enter === "register" ? "nav-link active" : "nav-link"}>Регитрация</a> >
</li> Авторизация
</ul> </a>
</li>
<li class="nav-item" onClick={() => selectTag("register")}>
<a
className={
enter === "register" ? "nav-link active" : "nav-link"
}
>
Регитрация
</a>
</li>
</ul>
</div>
<div className={classes.content}>
<div className={classes.content__wrapper}>
{enter === "login" ? (
<div className={classes.content__wrapper__login}>
<div className={classes.content__wrapper__login__header}>
<h3>Войти в аккаунт</h3>
</div> </div>
<div className={classes.content}> <div className={classes.content__wrapper__login__body}>
<div className={classes.content__wrapper}> {loginError ? (
{enter === 'login' ? <div className={classes.content__wrapper__login}> <span
<div className={classes.content__wrapper__login__header}> className={classes.content__wrapper__login__body__error}
<h3>Войти в аккаунт</h3> >
</div> Ошибка. Проверте корректность ввода логина и пароля.
<div className={classes.content__wrapper__login__body}> </span>
{loginError ? <span className={classes.content__wrapper__login__body__error}> ) : (
Ошибка. Проверте корректность ввода логина и пароля. <span
</span> : <span className={classes.content__wrapper__login__body__error}></span>} className={classes.content__wrapper__login__body__error}
<MyInput placeholder={"Логин"} otherMainStyle={{width: "100%", height: "20%"}} otherInputStyle={{width: "100%"}} value={login} change={setLogin}/> ></span>
<MyInput type={"password"} placeholder={"Пароль"} otherMainStyle={{width: "100%", height: "20%"}} otherInputStyle={{width: "100%"}} value={password} change={setPassword}/> )}
</div> <MyInput
<div className={classes.content__wrapper__login__footer}> placeholder={"Логин"}
<MyButton otherMainStyle={{ width: "100%", height: "20%" }}
text={"Войти"} otherInputStyle={{ width: "100%" }}
otherStyle={{height: "50%", width: "20%"}} value={login}
click={logInToAccount} change={setLogin}
/> />
</div> <MyInput
</div> : type={"password"}
<div className={classes.content__wrapper__register}> placeholder={"Пароль"}
<div className={classes.content__wrapper__register__header}> otherMainStyle={{ width: "100%", height: "20%" }}
<h3>Зарегистрировать учетную запись</h3> otherInputStyle={{ width: "100%" }}
</div> value={password}
<div className={classes.content__wrapper__register__body}> change={setPassword}
{regsterError ? <span className={classes.content__wrapper__register__body__error}> />
{regsterError} </div>
</span> : <span className={classes.content__wrapper__register__body__error}></span>} <div className={classes.content__wrapper__login__footer}>
<MyInput placeholder={"Логин"} otherMainStyle={{width: "100%", height: "15%"}} otherInputStyle={{width: "100%"}} value={login} change={setLogin}/> <MyButton
<MyInput type={"password"} placeholder={"Пароль"} otherMainStyle={{width: "100%", height: "15%"}} otherInputStyle={{width: "100%"}} value={password} change={setPassword}/> text={"Войти"}
<MyInput type={'password'} placeholder={"Повторите пароль"} otherMainStyle={{width: "100%", height: "15%"}} otherInputStyle={{width: "100%"}} value={repiedPassword} change={setRepiedPassword}/> class={"main__blue"}
</div> click={logInToAccount}
{/* <div className={classes.content__wrapper__register__body}> />
</div>
</div>
) : (
<div className={classes.content__wrapper__register}>
<div className={classes.content__wrapper__register__header}>
<h3>Создать аккаунт</h3>
</div>
<div className={classes.content__wrapper__register__body}>
{regsterError ? (
<span
className={
classes.content__wrapper__register__body__error
}
>
{regsterError}
</span>
) : (
<span
className={
classes.content__wrapper__register__body__error
}
></span>
)}
<MyInput
placeholder={"Логин"}
otherMainStyle={{ width: "100%", height: "15%" }}
otherInputStyle={{ width: "100%" }}
value={login}
change={setLogin}
/>
<MyInput
type={"password"}
placeholder={"Пароль"}
otherMainStyle={{ width: "100%", height: "15%" }}
otherInputStyle={{ width: "100%" }}
value={password}
change={setPassword}
/>
<MyInput
type={"password"}
placeholder={"Повторите пароль"}
otherMainStyle={{ width: "100%", height: "15%" }}
otherInputStyle={{ width: "100%" }}
value={repiedPassword}
change={setRepiedPassword}
/>
</div>
{/* <div className={classes.content__wrapper__register__body}>
<MyInput placeholder={"Email"} otherMainStyle={{width: "100%", height: "15%"}} otherInputStyle={{width: "100%"}} value={email} change={setEmail}/> <MyInput placeholder={"Email"} otherMainStyle={{width: "100%", height: "15%"}} otherInputStyle={{width: "100%"}} value={email} change={setEmail}/>
<MyInput placeholder={"Номер телефона"} otherMainStyle={{width: "100%", height: "15%"}} otherInputStyle={{width: "100%"}} value={phone} change={setPhone}/> <MyInput placeholder={"Номер телефона"} otherMainStyle={{width: "100%", height: "15%"}} otherInputStyle={{width: "100%"}} value={phone} change={setPhone}/>
<div className={classes.content__wrapper__register__body__fio}> <div className={classes.content__wrapper__register__body__fio}>
@ -136,20 +200,20 @@ const EnterAccount = () => {
<MyInput type={'password'} placeholder={"Пароль"} otherMainStyle={{width: "100%", height: "15%"}} otherInputStyle={{width: "100%"}} value={password} change={setPassword}/> <MyInput type={'password'} placeholder={"Пароль"} otherMainStyle={{width: "100%", height: "15%"}} otherInputStyle={{width: "100%"}} value={password} change={setPassword}/>
<MyInput type={'password'} placeholder={"Повторите пароль"} otherMainStyle={{width: "100%", height: "15%"}} otherInputStyle={{width: "100%"}} value={repiedPassword} change={setRepiedPassword}/> <MyInput type={'password'} placeholder={"Повторите пароль"} otherMainStyle={{width: "100%", height: "15%"}} otherInputStyle={{width: "100%"}} value={repiedPassword} change={setRepiedPassword}/>
</div> */} </div> */}
<div className={classes.content__wrapper__register__footer}> <div className={classes.content__wrapper__register__footer}>
<MyButton <MyButton
text={"Создать"} text={"Создать"}
otherStyle={{ height: "100%", width: "20%" }} class={"main__blue"}
click={() => createUser()} click={() => createUser()}
/> />
</div>
</div>
}
</div>
</div> </div>
</div> </div>
)}
</div>
</div> </div>
); </div>
}; </div>
);
};
export default EnterAccount; export default EnterAccount;

View File

@ -1,128 +1,234 @@
import React, { useState, useContext, useEffect } from "react"; import React, { useState, useContext, useEffect } from "react";
import { useCookies } from "react-cookie"; import { useCookies } from "react-cookie";
import { useNavigate } from 'react-router-dom'; import { useNavigate } from "react-router-dom";
import classes from "../assets/styles/forms.module.scss" import classes from "../assets/styles/forms.module.scss";
import MyButton from "../components/MyButton.jsx"; import MyButton from "../components/MyButton.jsx";
import MyInput from "../components/MyInput.jsx"; import MyInput from "../components/MyInput.jsx";
import { FormsData, UserData } from "../context"; import { FormsData, UserData } from "../context";
import { listFormsApi, createFormApi, removeFormApi, newFormTokenApi, listFormsByTokenApi } from "../hooks/api/listFormsApi.js"; import CheckModal from "../components/CheckModal.jsx";
import { verifyUserApi } from "../hooks/api/enterAccountApi.js";
import {
listFormsApi,
createFormApi,
removeFormApi,
newFormTokenApi,
listFormsByTokenApi,
} from "../hooks/api/listFormsApi.js";
const Forms = () => { const Forms = () => {
const navigate = useNavigate(); const navigate = useNavigate();
const {forms, setForms} = useContext(FormsData); const { forms, setForms } = useContext(FormsData);
const {user, setUser} = useContext(UserData); const { user, setUser, userRef } = useContext(UserData);
const [stateLoading, setStateLoading] = useState(false); const [stateLoading, setStateLoading] = useState(false);
const [searchForm, setSearchForm] = useState("");
const [cookies, _, __] = useCookies(["user"]); const [cookies, setCookies, __] = useCookies(["user"]);
useEffect(() => { useEffect(() => {
async function listForms() { async function listForms() {
const response = await listFormsApi(cookies.token); const responseForms = await listFormsApi(cookies.token);
const responseUser = await verifyUserApi(cookies.token)
if (response.data) { if (responseForms.data && responseUser) {
setForms(response.data); setForms(responseForms.data.filter(item => item.owner_id === responseUser.data.id));
} } else if (responseForms.status === 200 && response.data) {
else if (response.status === 200 && response.data) { setForms([]);
setForms([]); } else if (responseForms.status === 401 || responseUser.status === 401) {
} // navigate("/enter")
else { setUser(false)
console.log(response) } else {
} console.log(responseForms);
}; console.log(responseUser)
}
}
listForms(); listForms();
}, []) }, []);
async function createForm() { async function createForm() {
setStateLoading(true); setStateLoading(true);
const response = await createFormApi(cookies.token); const response = await createFormApi(cookies.token);
console.log("response", response) setStateLoading(false);
setStateLoading(false);
if (response.data) { if (response.data && response.status === 200) {
const token = await newFormTokenApi(cookies.token, response.data.id) const token = await newFormTokenApi(cookies.token, response.data.id, "Админ");
navigate(`/forms/${response.data.id}/edit`);
} else if (
response.status === 401
) {
// navigate("/enter")
setUser(false)
}
else {
console.log(response)
}
}
navigate(`/forms/${response.data.id}/edit`) function editForm(item) {
} navigate(`/forms/${item.id}/edit`);
else { }
console.log(response)
}
};
function editForm(item) { async function removeForm(id) {
navigate(`/forms/${item.id}/edit`); const response = await removeFormApi(cookies.token, id);
};
async function removeForm(id) { if (response.status === 200) {
const response = await removeFormApi(cookies.token, id) setForms([...forms.filter((item) => item.id !== id)]);
}
else if (
response.status === 401
) {
// navigate("/enter")
setUser(false)
}
else {
console.log(response)
}
}
if (response.status === 200) { async function openFormView(formId) {
setForms([...forms.filter(item => item.id !== id)]); const response = await listFormsByTokenApi(cookies.token, formId);
}
else {
console.log(response)
}
};
async function openFormView(formId) { if (response.status === 200 && response.data.tokens) {
const response = await listFormsByTokenApi(cookies.token, formId); navigate(`/forms/${response.data.tokens[0].id}/`);
}
else if (
response.status === 401
) {
// navigate("/enter")
setUser(false)
}
else {
console.log(response)
}
}
if (response.status === 200 && response.data.tokens) { async function copyLinkToFormView(formId) {
navigate(`/forms/${response.data.tokens[0].id}/`); const response = await listFormsByTokenApi(cookies.token, formId);
}
else {
console.log(response)
}
};
async function copyLinkToFormView(formId) { if (response.status === 200 && response.data.tokens) {
const response = await listFormsByTokenApi(cookies.token, formId); navigator.clipboard.writeText(
`${window.location.host}/forms/${response.data.tokens[0].id}/`
);
}
else if (
response.status === 401
) {
// navigate("/enter")
setUser(false)
}
else {
console.log(response)
}
}
if (response.status === 200 && response.data.tokens) { const filterFormsBySearch = () =>
navigator.clipboard.writeText(`http://localhost:3000/forms/${response.data.tokens[0].id}/`) [...forms].filter((item) => item.title.toLowerCase().includes(searchForm));
}
else {
console.log(response)
}
};
return ( return (
<div className={classes.main}> <div className={classes.main}>
<div className={classes.wrapper}> <div className={classes.wrapper}>
<div className={classes.panel}> <div className={classes.panel}>
{/* <MyInput placeholder={'Поиск...'}/> */} <MyInput
<MyButton click={createForm} otherStyle={{width: '200px'}} text={ placeholder={"Поиск..."}
stateLoading ? <div class="spinner-border text-light" role="status"> value={searchForm}
<span class="visually-hidden">Загрузка...</span> change={setSearchForm}
</div> : 'Создать' />
}/> <MyButton
click={createForm}
class={"main__blue"}
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.datetime}</div>
<div className={classes.listForms__forms__item__update}>{item.update}</div> */}
<i class="fa-solid fa-ellipsis-vertical" id="action" data-bs-toggle="dropdown"></i>
<ul class="dropdown-menu" aria-labelledby="action">
<li><a class="dropdown-item" onClick={() => openFormView(item.id)}>Открыть</a></li>
<li><a class="dropdown-item" onClick={() => navigate(`/forms/${item.id}/answers`)}>Ответы</a></li>
<li><a class="dropdown-item" onClick={() => copyLinkToFormView(item.id)}>Скопировать ссылку</a></li>
<li><a class="dropdown-item" onClick={() => removeForm(item.id)}>Удалить</a></li>
</ul>
</div>
)}
</div>
</div>
</div>
</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}>
{filterFormsBySearch().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.datetime}</div>
<div className={classes.listForms__forms__item__update}>{item.update}</div> */}
<i
class="fa-solid fa-ellipsis-vertical"
id="action"
data-bs-toggle="dropdown"
></i>
<ul class="dropdown-menu" aria-labelledby="action">
<li>
<a
class="dropdown-item"
onClick={() => openFormView(item.id)}
>
Открыть
</a>
</li>
<li>
<a
class="dropdown-item"
onClick={() => navigate(`/forms/${item.id}/answers`)}
>
Ответы
</a>
</li>
<li>
<a
class="dropdown-item"
onClick={() => navigate(`/tokens/${item.id}`)}
>
Список токенов
</a>
</li>
<li>
<a
class="dropdown-item"
onClick={() => copyLinkToFormView(item.id)}
>
Скопировать ссылку
</a>
</li>
<li>
<a
class="dropdown-item"
data-bs-toggle="modal"
data-bs-target={`#checkModal${item.id}`}
>
Удалить
</a>
</li>
</ul>
export default Forms; <CheckModal
postfix={item.id}
message={`Вы хотите удалить форму <${item.title}>?`}
action={{
execute: () => removeForm(item.id),
cancel: () => {},
}}
/>
</div>
))}
</div>
</div>
</div>
</div>
);
};
export default Forms;

View File

@ -16,8 +16,8 @@ const Home = () => {
Кажется вы попали на общую страницу. Кажется вы попали на общую страницу.
</div> </div>
<div className={classes.header__bottom}> <div className={classes.header__bottom}>
Чтобы перейти к форме введите токен ниже или снова перейдите по Чтобы перейти к форме введите токен ниже <br /> или снова перейдите
ссылке. по ссылке.
</div> </div>
</div> </div>
<div className={classes.content}> <div className={classes.content}>
@ -41,6 +41,7 @@ const Home = () => {
<div className={classes.content__search__wrapper}> <div className={classes.content__search__wrapper}>
<MyButton <MyButton
text={"Найти форму"} text={"Найти форму"}
class={"main__blue"}
click={() => navigate(`/forms/${token}`)} click={() => navigate(`/forms/${token}`)}
/> />
</div> </div>

178
src/pages/NewArticle.jsx Normal file
View File

@ -0,0 +1,178 @@
import React, { useState, useContext, useEffect } from "react";
import { useNavigate, useLocation, useParams } from 'react-router-dom';
import { useCookies } from "react-cookie";
import classes from "../assets/styles/newArticle.module.scss";
import MyButton from "../components/MyButton.jsx";
import Loading from "../components/Loading.jsx";
import MyInput from "../components/MyInput.jsx";
import TextEditor from "../components/TextEditor.jsx";
import { UserData } from "../context/index.js";
import { getArticleApi, editTagsApi, editTitleArticleApi, editArticleApi, addArticleApi } from "../hooks/api/articleApi.js";
const NewArticle = () => {
const navigate = useNavigate();
const location = useLocation();
const { articleId } = useParams();
const {user, setUser} = useContext(UserData)
const [loading, setLoading] = useState(false);
const [title, setTitle] = useState("");
const [tags, setTags] = useState([]);
const [newTag, setNewTag] = useState("")
const [ownerId, setOwnerId] = useState("");
const [contentArticle, setContentArticle] = useState('');
const [blocks, setBlocks] = useState('');
const [cookies, _, __] = useCookies(["user"]);
useEffect(() => {
async function getArticle() {
const response = await getArticleApi(cookies.token, articleId)
if (response.status === 200) {
setTitle(response.data.article.title)
setTags(response.data.article.tags ? response.data.article.tags : [])
setOwnerId(response.data.article.owner_id)
setContentArticle(response.data.blocks ? response.data.blocks[0].data : '')
setBlocks(response.data.blocks ? response.data.blocks : false)
}
else if (
response.status === 401
) {
// navigate("/enter")
setUser(false)
}
else {
console.log(response)
}
}
getArticle()
}, [])
async function addTag() {
if (newTag.length > 0 && !tags.find(item => item === newTag)) {
const response = await editTagsApi(cookies.token, articleId, [newTag], false)
if (response.status === 200) {
setTags([...tags, newTag])
setNewTag("")
}
else if (
response.status === 401
) {
// navigate("/enter")
setUser(false)
}
else {
console.log(response)
}
}
}
async function removeTag(item, index) {
const response = await editTagsApi(cookies.token, articleId, [item], true)
if (response.status === 200) {
const cTags = [...tags]
cTags.splice(index, 1)
setTags(cTags)
}
else if (
response.status === 401
) {
// navigate("/enter")
setUser(false)
}
else {
console.log(response)
}
}
async function saveArticle() {
const responseTitle = await editTitleArticleApi(cookies.token, articleId, title)
const responseContentArticle = blocks ?
await editArticleApi(cookies.token, articleId, contentArticle) :
await addArticleApi(cookies.token, articleId, contentArticle)
if (responseTitle.status === 200 && responseContentArticle.status === 200) {
navigate("/articles")
}
else if (
responseTitle.status === 401 || responseContentArticle.status === 401
) {
// navigate("/enter")
setUser(false)
}
else {
console.log(responseTitle)
console.log(responseContentArticle)
}
}
return (
<div className={classes.main}>
<div className={classes.wrapper}>
<div className={classes.header}>
<div className={classes.header__listInput}>
<div className={classes.header__listInput__date}>
<span>Дата создания</span>
<MyInput type={"datetime-local"}/>
</div>
<div className={classes.header__listInput__title}>
<span>Название статьи</span>
<MyInput type={"text"} value={title} change={setTitle}/>
</div>
</div>
<div className={classes.header__listBtn}>
{/* <MyButton text={'Предпросмотр'} class={"main__white"}/> */}
<MyButton text={'Опубликовать'} class={"main__green"} click={() => saveArticle()}/>
</div>
</div>
<div className={classes.tags}>
<div className={classes.tags__wrapper}>
<div className={classes.tags__wrapper__input}>
<span className={classes.tags__wrapper__input__title}>Добавить тэг</span>
<MyInput
type={"text"}
otherMainStyle={{border: "1px solid rgb(180, 180, 180)", borderRadius: "5px"}}
otherInputStyle={{border: "0px solid rgb(180, 180, 180)", width: "85%"}}
value={newTag}
change={setNewTag}
/>
<MyButton
type={"button"}
text={<i class="fa-solid fa-arrow-right"></i>}
class={"main__transparent"}
mainStyle={
{
position: "absolute",
right: "0",
top: "0",
height: "100%",
}
}
otherStyle={{borderRadius: "0 5px 5px 0", padding: "3px 7px 0 7px"}}
click={() => addTag()}
/>
</div>
<div className={classes.tags__wrapper__list}>
{tags ? tags.map((item, i) =>
<div className={classes.tags__wrapper__list__item} key={i}>
<span>#{item}</span>
<i class="fa-solid fa-circle-xmark" onClick={() => removeTag(item, i)}></i>
</div>
) : <></>}
</div>
</div>
</div>
<div className={classes.content}>
<TextEditor data={contentArticle} setData={setContentArticle}/>
</div>
</div>
</div>
)
}
export default NewArticle;

View File

@ -1,321 +1,470 @@
import React, { useState, useContext, useEffect } from "react"; import React, { useState, useContext, useEffect } from "react";
import { useNavigate, useLocation, useParams } from 'react-router-dom'; import { useNavigate, useLocation, useParams } from "react-router-dom";
import { useCookies } from "react-cookie"; import { useCookies } from "react-cookie";
import classes from "../assets/styles/newForm.module.scss"; import classes from "../assets/styles/newForm.module.scss";
import MyButton from "../components/MyButton.jsx"; import MyButton from "../components/MyButton.jsx";
import AnswerModal from "../components/AnswerModal.jsx"; import AnswerModal from "../components/AnswerModal.jsx";
import PreviewModal from "../components/PreviewModal.jsx" import PreviewModal from "../components/PreviewModal.jsx";
import Loading from "../components/Loading.jsx";
import { FormsData, TypeAnswerData } from "../context"; import { FormsData, TypeAnswerData } from "../context";
import { listFormsApi, updateTitleFormApi } from "../hooks/api/listFormsApi.js"; import { listFormsApi, updateTitleFormApi } from "../hooks/api/listFormsApi.js";
import { saveFormApi, addFormBlockApi, listFormBlockApi, updateBlockApi, updateOrderBlockApi } from "../hooks/api/formApi.js"; import { UserData } from "../context";
import { responseDataToListBlock } from "../hooks/sundry/parseListBlock.js"; import {
saveFormApi,
addFormBlockApi,
listFormBlockApi,
updateBlockApi,
updateOrderBlockApi,
removeBlockApi,
getBlockApi,
} from "../hooks/api/formApi.js";
import {
responseDataToListBlock,
numberingBlocks,
} from "../hooks/sundry/parseListBlock.js";
import MyInput from "../components/MyInput.jsx";
const NewForm = () => { const NewForm = () => {
const navigate = useNavigate(); const navigate = useNavigate();
const location = useLocation(); const location = useLocation();
const { formId } = useParams(); const { formId } = useParams();
const [dragElem, setDragElem] = useState(null); const {user, setUser} = useContext(UserData);
const [dropElem, setDropElem] = useState(null); const [dragElem, setDragElem] = useState(null);
const [dropElem, setDropElem] = useState(null);
const [loading, setLoading] = useState(false);
const [cookies, _, __] = useCookies(["user"]); const [cookies, _, __] = useCookies(["user"]);
const {forms, setForms} = useContext(FormsData);
const {listTypeAnswer, setListTypeAnswer} = useContext(TypeAnswerData);
const nextID = (list, param) => { const { forms, setForms } = useContext(FormsData);
return list.length ? list.at(-1)[param] + 1 : 1 const { listTypeAnswer, setListTypeAnswer } = useContext(TypeAnswerData);
};
const [question, setQuestion] = useState("");
const [comment, setComment] = useState("");
const [mandatory, setMandatory] = useState(false);
const [optionAnswer, setOptionAnswer] = useState([]);
const [file, setFile] = useState([]);
const [currentTypeAnswer, setCurrentTypeAnswer] = useState("");
const [currentOptionAnswer, setCurrentOptionAnswer] = useState("");
const [datetime, setDatetime] = useState(""); const nextID = (list, param) => {
const [titleForm, setTitleForm] = useState(""); return list.length ? list.at(-1)[param] + 1 : 1;
};
const [question, setQuestion] = useState("");
const [comment, setComment] = useState("");
const [mandatory, setMandatory] = useState(false);
const [optionAnswer, setOptionAnswer] = useState([]);
const [file, setFile] = useState([]);
const [video, setVideo] = useState("");
const [currentTypeAnswer, setCurrentTypeAnswer] = useState("");
const [currentOptionAnswer, setCurrentOptionAnswer] = useState("");
const [listBlock, setListBlock] = useState([]); const [datetime, setDatetime] = useState("");
const [newTitleForm, setNewTitleForm] = useState("");
const [oldTitleFrom, setOldTitleForm] = useState("");
const [stateModal, setStateModal] = useState(false); const [listBlock, setListBlock] = useState([]);
function removeAnswerByForm(id) { const [stateModal, setStateModal] = useState(false);
setListBlock([...listBlock.filter(item => item.id !== id)]);
};
function cleanStates() { function cleanStates() {
setStateModal(false) setStateModal(false);
setQuestion(""); setQuestion("");
setComment(""); setComment("");
setOptionAnswer([]) setOptionAnswer([]);
setFile([]); setFile([]);
setCurrentTypeAnswer(""); setVideo("");
setCurrentOptionAnswer("") setCurrentTypeAnswer("");
setMandatory(false); setCurrentOptionAnswer("");
}; setMandatory(false);
}
useEffect(() => { useEffect(() => {
async function totalData() { async function totalData() {
const responseForms = await listFormsApi(cookies.token); const responseForms = await listFormsApi(cookies.token);
const responseBlock = await listFormBlockApi(cookies.token, formId); const responseBlock = await listFormBlockApi(cookies.token, formId);
if (responseBlock.status === 200 && responseBlock.data && responseForms.status === 200) { if (
setListBlock(responseDataToListBlock(responseBlock.data)); responseBlock.status === 200 &&
setTitleForm(responseForms.data.find(item => item.id === formId).title); responseBlock.data &&
} responseForms.status === 200
else { ) {
setListBlock([]) console.log(responseDataToListBlock(responseBlock.data))
setTitleForm("Новыя форма") setListBlock(responseDataToListBlock(responseBlock.data));
} const title = responseForms.data.find(
}; (item) => item.id === formId
).title;
totalData(); setNewTitleForm(title);
}, []); setOldTitleForm(title);
}
function addOptionAnswer(text) { else if (responseForms.status === 401 || responseBlock.status === 401) {
setOptionAnswer([...optionAnswer, { // navigate("/enter")
id: nextID(optionAnswer, "id"), setUser(false)
text: currentOptionAnswer }
}]); else {
setCurrentOptionAnswer(""); setListBlock([]);
}; setNewTitleForm("Новыя форма");
setOldTitleForm("Новыя форма");
function editAnswerByForm(id) { }
const obj = listBlock.find(item => item.id === id); setLoading(true);
setQuestion(obj.question);
setComment(obj.comment);
setFile(obj.file);
setCurrentTypeAnswer(obj.typeAnswer);
setOptionAnswer(obj.optionAnswer);
setMandatory(obj.mandatory);
setStateModal(obj.id);
};
async function updateBlock() {
const data = {
id: stateModal,
question: question,
comment: comment,
file: file,
mandatory: mandatory,
optionAnswer: optionAnswer,
typeAnswer: currentTypeAnswer,
}
console.log(stateModal)
const response = await updateBlockApi(cookies.token, stateModal, data);
if (response.status === 200) {
setListBlock(listBlock.map(item => {
if (item.id === stateModal) {
item = data
}
return item
}))
}
else {
console.log(response)
}
cleanStates();
};
async function addFormBlock() {
const newBlock = {
question: question,
typeAnswer: currentTypeAnswer,
comment: comment,
mandatory: mandatory,
optionAnswer: optionAnswer,
file: file
}
const response = await addFormBlockApi(cookies.token, formId, newBlock)
if (response.status === 200) {
setListBlock([...listBlock, {...newBlock, order: nextID(listBlock, "order")}]);
}
else {
console.log(response)
}
cleanStates();
};
async function updateTitleForm() {
const response = await updateTitleFormApi(cookies.token, formId, titleForm);
if (response.status === 200) {
console.log(response)
}
else {
console.log(response)
}
};
async function updateOrderBlock() {
const response = await updateOrderBlockApi(cookies.token, formId, {
new: dropElem,
old: dragElem
})
if (response.status === 200) {
const currentElem = listBlock[dragElem];
const tListBlock = [...listBlock];
if (dragElem > dropElem) {
tListBlock.splice(dropElem, 0, currentElem);
tListBlock.splice(dragElem + 1, 1);
}
else {
tListBlock.splice(dropElem + 1, 0, currentElem);
tListBlock.splice(dragElem, 1);
};
setListBlock(tListBlock);
}
} }
function updateFormByForms() { totalData();
updateFormByFormsApi(location.state.id, "Новая форма", listBlock) }, []);
.then((resolve, _) => {
console.log(resolve); function addOptionAnswer() {
setForms( setOptionAnswer(
forms.map(item => { numberingBlocks(
if (item.id === location.state.id) { [
item.title = "Новая форма", ...optionAnswer,
item.questions = listBlock {
} id: nextID(optionAnswer, "id"),
return item text: currentOptionAnswer,
}) },
); ],
cleanStates(); "id",
navigate("/forms"); 1
}) )
);
setCurrentOptionAnswer("");
}
function removeOptionAnswer(id) {
setOptionAnswer(
numberingBlocks(
[...optionAnswer].filter((answer) => answer.id !== id),
"id",
1
)
);
}
function editAnswerByForm(id) {
const obj = listBlock.find((item) => item.id === id);
setQuestion(obj.question);
setComment(obj.comment);
setFile(obj.file);
setVideo(obj.video);
setCurrentTypeAnswer(obj.typeAnswer);
setOptionAnswer(obj.optionAnswer);
setMandatory(obj.mandatory);
setStateModal(obj.id);
console.log(obj.file.map(item => item.name))
}
async function updateBlock() {
const data = {
question: question,
comment: comment,
file: file,
video: video,
mandatory: mandatory,
optionAnswer: optionAnswer,
typeAnswer: currentTypeAnswer,
}; };
function saveForm() { const response = await updateBlockApi(
// saveFormApi("Новая форма", listBlock) cookies.token,
// .then((resolve, reject) => { formId,
// console.log(resolve); data,
// setForms( listBlock.find((item) => item.id === stateModal).order
// [...forms, { );
// id: nextID(forms),
// title: "Новая форма",
// questions: listBlock,
// answers: []
// }]
// );
cleanStates();
navigate("/forms");
// })
// .catch(error => console.log(error));
};
return ( if (response.status === 200) {
<div className={classes.main}> const responseBlock = await listFormBlockApi(cookies.token, formId);
<div className={classes.wrapper}>
<div className={classes.header}>
<div className={classes.header__listInput}>
<div className={classes.header__listInput__date}>
<span>Дедлайн выполнения</span>
<input type="datetime-local" value={datetime} onChange={event => setDatetime(event.target.value)}/>
</div>
<div className={classes.header__listInput__title}>
<span>Название формы</span>
<input type="text" value={titleForm} onChange={event => setTitleForm(event.target.value)}/>
</div>
<MyButton
text={<i class="fa-solid fa-floppy-disk"></i>}
click={() => updateTitleForm()}
otherStyle={{paddingLeft: "0.9rem", paddingRight: "0.9rem"}}>
</MyButton>
</div>
<div className={classes.header__listBtn}>
<MyButton text={'Предпросмотр'} backgroundColor={'rgb(225, 225, 225)'} toggle={"modal"} target={"#previewModal"}/>
<MyButton text={'Опубликовать'} click={location.state ? updateFormByForms : saveForm}/>
</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="#answerModal" onClick={() => setCurrentTypeAnswer(item.id)} key={i}>
<span>{item.text}</span>
</div>
)}
</div>
</div>
<PreviewModal listBlock={listBlock} listTypeAnswer={listTypeAnswer}/> if (responseBlock.status === 200) {
setListBlock(responseDataToListBlock(responseBlock.data));
} else {
setListBlock([]);
}
}
else if (
response.status === 401
) {
// navigate("/enter")
setUser(false)
}
else {
console.log(response)
}
<AnswerModal cleanStates();
stateModal={stateModal} }
currentTypeAnswer={currentTypeAnswer}
question={question}
comment={comment}
mandatory={mandatory}
optionAnswer={optionAnswer}
setOptionAnswer={setOptionAnswer}
file={file}
currentOptionAnswer={currentOptionAnswer}
listTypeAnswer={listTypeAnswer}
setQuestion={setQuestion}
setComment={setComment}
setFile={setFile}
setCurrentOptionAnswer={setCurrentOptionAnswer}
setMandatory={setMandatory}
cleanStates={cleanStates}
addFormBlock={addFormBlock}
addOptionAnswer={addOptionAnswer}
updateBlock={updateBlock}
setCurrentTypeAnswer={setCurrentTypeAnswer}/>
<div className={classes.content__newForm}> async function removeBlock(order) {
<div className={classes.content__newForm__title}> const response = await removeBlockApi(cookies.token, formId, order);
<h3>Новая форма</h3>
</div> if (response.status === 200) {
<div className={classes.content__newForm__list}> setListBlock([...listBlock.filter((item) => item.order !== order)]);
{listBlock.map((item, i) => }
<div else if (
className={classes.content__newForm__list__item} response.status === 401
key={i} ) {
id={item.order} // navigate("/enter")
draggable="true" setUser(false)
onDragOver={(event) => {event.preventDefault()}} }
onDragStart={(event) => { else {
if (event.target.id) { console.log(response)
setDragElem(Number(event.target.id)) }
} }
}}
onDragEnter={(event) => { async function addFiles(newFile) {
if (event.target.id) { const fileData = []
setDropElem(Number(event.target.id))
} for (let item of newFile) {
}} let binary = ""
onDrop={() => updateOrderBlock()} const buff = await item.arrayBuffer()
> const bytesArray = new Uint8Array(buff)
<div className={classes.content__newForm__list__item__answer}> let len = bytesArray.byteLength;
<span>{item.question}</span>
<span>{listTypeAnswer.map(typeItem => { for (let i = 0; i < len; i++) {
if (typeItem.id === item.typeAnswer) { binary += String.fromCharCode( bytesArray[ i ] );
return typeItem.text }
}
})}</span> fileData.push({
</div> name: item.name,
<div className={classes.content__newForm__list__item__btn}> type: item.type,
<i class="fa-solid fa-pen" data-bs-toggle="modal" data-bs-target="#answerModal" onClick={() => {editAnswerByForm(item.id)}}></i> binary: btoa(binary)
<i class="fa-solid fa-trash" onClick={() => removeAnswerByForm(item.id)}></i> })
</div>
</div> setFile([...file, ...fileData])
)} }
</div> }
</div>
</div> async function addFormBlock() {
const newBlock = {
question: question,
typeAnswer: currentTypeAnswer,
comment: comment,
mandatory: mandatory,
optionAnswer: optionAnswer,
file: file,
video: video,
};
const response = await addFormBlockApi(cookies.token, formId, newBlock);
if (response.status === 200) {
const responseBlock = await listFormBlockApi(cookies.token, formId);
if (responseBlock.status === 200) {
setListBlock(responseDataToListBlock(responseBlock.data));
} else {
setListBlock([]);
}
}
else if (
response.status === 401
) {
// navigate("/enter")
setUser(false)
}
else {
console.log(response)
}
cleanStates();
}
// async function updateTitleForm() {
// const response = await updateTitleFormApi(cookies.token, formId, newTitleForm);
// if (response.status === 200) {
// console.log(response)
// }
// else {
// console.log(response)
// }
// };
async function updateOrderBlock() {
const response = await updateOrderBlockApi(cookies.token, formId, {
new: dropElem,
old: dragElem,
});
if (response.status === 200) {
const currentElem = listBlock[dragElem];
const tListBlock = [...listBlock];
if (dragElem > dropElem) {
tListBlock.splice(dropElem, 0, currentElem);
tListBlock.splice(dragElem + 1, 1);
} else {
tListBlock.splice(dropElem + 1, 0, currentElem);
tListBlock.splice(dragElem, 1);
}
setListBlock(numberingBlocks(tListBlock, "order"));
}
else if (
response.status === 401
) {
// navigate("/enter")
setUser(false)
}
else {
console.log(response)
}
}
async function saveForm() {
if (oldTitleFrom !== newTitleForm) {
const response = await updateTitleFormApi(cookies.token, formId, newTitleForm);
if (
response.status === 401
) {
// navigate("/enter")
setUser(false)
}
else {
console.log(response)
}
}
cleanStates();
navigate("/forms");
}
return (
<div className={classes.main}>
<div className={classes.wrapper}>
<div className={classes.header}>
<div className={classes.header__listInput}>
{/* <div className={classes.header__listInput__date}>
<span>Дедлайн выполнения</span>
<MyInput
type={"datetime-local"}
value={datetime}
change={setDatetime}
/>
</div> */}
<div className={classes.header__listInput__title}>
<span>Название формы</span>
<MyInput
type={"text"}
value={newTitleForm}
change={setNewTitleForm}
/>
</div> </div>
</div>
<div className={classes.header__listBtn}>
<MyButton
text={"Предпросмотр"}
class={"main__white"}
toggle={"modal"}
target={"#previewModal"}
/>
<MyButton
text={"Опубликовать"}
class={"main__blue"}
click={location.state ? updateFormByForms : saveForm}
/>
</div>
</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="#answerModal"
onClick={() => setCurrentTypeAnswer(item.id)}
key={i}
>
<span>{item.text}</span>
</div>
))}
</div>
</div>
export default NewForm; <PreviewModal listBlock={listBlock} listTypeAnswer={listTypeAnswer} />
<AnswerModal
stateModal={stateModal}
currentTypeAnswer={currentTypeAnswer}
question={question}
comment={comment}
mandatory={mandatory}
optionAnswer={optionAnswer}
setOptionAnswer={setOptionAnswer}
file={file}
video={video}
setVideo={setVideo}
currentOptionAnswer={currentOptionAnswer}
listTypeAnswer={listTypeAnswer}
setQuestion={setQuestion}
setComment={setComment}
setFile={setFile}
setCurrentOptionAnswer={setCurrentOptionAnswer}
setMandatory={setMandatory}
cleanStates={cleanStates}
addFormBlock={addFormBlock}
addOptionAnswer={addOptionAnswer}
updateBlock={updateBlock}
setCurrentTypeAnswer={setCurrentTypeAnswer}
removeOptionAnswer={removeOptionAnswer}
addFiles={addFiles}
/>
<div className={classes.content__newForm}>
<div className={classes.content__newForm__title}>
<h3>Новая форма</h3>
</div>
<div className={classes.content__newForm__list}>
{loading ? (
listBlock.map((item, i) => (
<div
className={classes.content__newForm__list__item}
key={i}
id={item.order}
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={() => updateOrderBlock()}
>
<div
className={classes.content__newForm__list__item__answer}
>
<span>{item.question}</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="#answerModal"
onClick={() => {
editAnswerByForm(item.id);
}}
></i>
<i
class="fa-solid fa-trash"
onClick={() => removeBlock(item.order)}
></i>
</div>
</div>
))
) : (
<Loading />
)}
</div>
</div>
</div>
</div>
</div>
);
};
export default NewForm;

View File

@ -33,6 +33,12 @@ const Profile = () => {
setFirst_name(response.data.first_name); setFirst_name(response.data.first_name);
setLast_name(response.data.last_name); setLast_name(response.data.last_name);
} }
else if (
response.status === 401
) {
// navigate("/enter")
setUser(false)
}
else { else {
console.log(response) console.log(response)
} }
@ -68,7 +74,9 @@ const Profile = () => {
last_name: last_name, last_name: last_name,
email: email, email: email,
phone: phone, phone: phone,
is_admin: user.is_admin is_admin: user.is_admin,
is_teacher: user.is_teacher,
id: user.id
}) })
if (response.status === 200) { if (response.status === 200) {
@ -81,7 +89,16 @@ const Profile = () => {
is_admin: user.is_admin is_admin: user.is_admin
}) })
setEdit(!edit) setEdit(!edit)
} }
else if (
response.status === 401
) {
navigate("/enter")
setUser(false);
}
else {
console.log(response)
}
} }
}; };
@ -108,19 +125,19 @@ const Profile = () => {
<span>Редактировать <i class="fa-solid fa-pen"></i></span> : <span>Редактировать <i class="fa-solid fa-pen"></i></span> :
<span>Сохранить <i class="fa-solid fa-floppy-disk"></i></span> <span>Сохранить <i class="fa-solid fa-floppy-disk"></i></span>
} }
backgroundColor={edit ? "rgb(200, 200, 200)" : ""} class={edit ? "main__white" : "main__green"}
click={() => editUser()}/> : <div>Запросите изменение профиля у администратора</div>} click={() => editUser()}/> : <div>Запросите изменение профиля у администратора</div>}
<MyButton <MyButton
text={ text={
<span>Выйти</span> <span>Выйти</span>
} }
backgroundColor={edit ? "rgb(252, 151, 151)" : ""} class={"main__red"}
click={() => exitAccount()}/> click={() => exitAccount()}/>
</div> </div>
</div> </div>
<div className={classes.profile__wrapper__body}> <div className={classes.profile__wrapper__body}>
{Object.keys(user).map(key => key !== "is_admin" ? <div className={classes.profile__wrapper__body__item}> {Object.keys(user).map(key => key !== "is_admin" && key !== "is_teacher" && key !== "id" ? <div className={classes.profile__wrapper__body__item}>
<div class="input-group mb-3"> <div class="input-group mb-3">
<span class="input-group-text">{key}</span> <span class="input-group-text">{key}</span>
<input <input

239
src/pages/TokensForm.jsx Normal file
View File

@ -0,0 +1,239 @@
import React, { useState, useEffect, useContext } from "react";
import { useNavigate, useParams } from "react-router-dom";
import { useCookies } from "react-cookie";
import classes from "../assets/styles/tokensForm.module.scss";
import {
listFormsByTokenApi,
newFormTokenApi,
removeTokenFormApi,
editTokenFormApi,
} from "../hooks/api/listFormsApi";
import MyButton from "../components/MyButton.jsx";
import { UserData } from "../context/index.js";
import DefaultModal from "../components/DefaultModal.jsx";
import EditToken from "../components/bodyModal/EditToken.jsx";
import QrCodeToken from "../components/bodyModal/QrCodeToken.jsx";
import QRCode from "qrcode";
const TokensForm = () => {
const { formId } = useParams();
const [tokens, setTokens] = useState([]);
const [currentIdToken, setCurrentIdToken] = useState(false);
const [loading, setLoading] = useState(true);
const [titleToken, setTitleToken] = useState("");
const { user, setUser } = useContext(UserData);
const [qrCodeUrl, setQrcodeUrl] = useState("");
const [cookies, _, __] = useCookies(["user"]);
useEffect(() => {
async function listFormsByToken() {
const response = await listFormsByTokenApi(cookies.token, formId);
if (response.status === 200) {
setTokens(
response.data.tokens.filter(
(item, i) => item.is_active === true && i !== 0
)
);
setLoading(false);
} else if (response.status === 401) {
// navigate("/enter")
setUser(false);
} else {
console.log(response);
}
}
listFormsByToken();
}, []);
async function newFormToken() {
const response = await newFormTokenApi(cookies.token, formId, titleToken);
if (response.status === 200) {
setTokens([...tokens, response.data.token]);
} else if (response.status === 401) {
// navigate("/enter")
setUser(false);
} else {
console.log(response);
}
}
// async function editTokenForm(tokenForm) {
// const response = await editTokenFormApi(cookies.token, formId, tokenForm, titleToken)
// if (response.status === 200) {
// setTokens([])
// }
// else if (
// response.status === 401
// ) {
// // navigate("/enter")
// setUser(false);
// }
// else {
// console.log(response)
// }
// }
async function removeTokenForm(tokenForm) {
const response = await removeTokenFormApi(cookies.token, formId, tokenForm);
if (response.status === 200) {
setTokens(tokens.filter((item) => item.id !== tokenForm));
} else if (response.status === 401) {
// navigate("/enter")
setUser(false);
} else {
console.log(response);
}
}
return (
<div className={classes.main}>
<div className={classes.wrapper}>
<div className={classes.answers}>
<div className={classes.answers__wrapper}>
<div className={classes.answers__wrapper__header}>
<h3>Токены формы</h3>
<MyButton
text={"Создать"}
class={"main__green"}
click={() => {
setTitleToken("");
setCurrentIdToken(false);
}}
target={"#tokenModal"}
toggle={"modal"}
/>
</div>
<div className={classes.answers__wrapper__body}>
<div className={classes.answers__wrapper__body__column}>
<div
className={classes.answers__wrapper__body__column__wrapper}
>
<div>Название</div>
<div>Токен</div>
</div>
</div>
{tokens ? (
tokens.map((item, i) => (
<div className={classes.answers__wrapper__body__item} key={i}>
<div
className={classes.answers__wrapper__body__item__wrapper}
>
<div
className={
classes.answers__wrapper__body__item__wrapper__title
}
>
{item.name}
</div>
<div
className={
classes.answers__wrapper__body__item__wrapper__token
}
>
{item.id}
</div>
</div>
<div className={classes.answers__wrapper__body__item__btn}>
{/* <i
class="fa-solid fa-pen"
data-bs-toggle="modal"
data-bs-target={`#tokenModal`}
onClick={() => {
setTitleToken(item.name)
setCurrentIdToken(item.id)
}}>
</i> */}
<i
class="fa-solid fa-qrcode"
onClick={() => {
QRCode.toDataURL(
`http://minerva.krbl.ru/forms/${item.id}`,
{},
(err, url) => {
setQrcodeUrl(url);
}
);
}}
data-bs-toggle="modal"
data-bs-target={`#qrModal`}
></i>
<i
class="fa-solid fa-trash"
onClick={() => removeTokenForm(item.id)}
></i>
</div>
<DefaultModal
name={"qrModal"}
btn={[
{
text: "Скачать",
class: "main__green",
dismiss: "modal",
action: () => {
const aElem = document.createElement("a");
aElem.href = qrCodeUrl;
aElem.download = `${item.name}.png`;
aElem.click();
},
},
{
text: "Отмена",
class: "main__white",
dismiss: "modal",
action: () => {},
},
]}
BodyModal={<QrCodeToken value={item.id} />}
/>
</div>
))
) : (
<div className={classes.loading__wrapper}>
<div
class="spinner-border text-dark"
className={classes.loading__wrapper__body}
role="status"
>
<span class="visually-hidden">Загрузка...</span>
</div>
</div>
)}
<DefaultModal
name={"tokenModal"}
btn={[
{
text: "Сохранить",
class: "main__green",
dismiss: "modal",
action: () => newFormToken(),
},
{
text: "Отмена",
class: "main__white",
dismiss: "modal",
action: () => {},
},
]}
BodyModal={
<EditToken
titleToken={titleToken}
setTitleToken={setTitleToken}
/>
}
/>
</div>
</div>
</div>
</div>
</div>
);
};
export default TokensForm;

87
src/pages/ViewArticle.jsx Normal file
View File

@ -0,0 +1,87 @@
import React, { useState, useContext, useEffect, useLocation } from "react";
import { useNavigate, useParams } from "react-router-dom";
import { useCookies } from "react-cookie";
import classes from "../assets/styles/viewArticle.module.scss";
import { FormsData, TypeAnswerData, answersData, UserData } from "../context";
import MyButton from "../components/MyButton.jsx";
import { getArticleApi } from "../hooks/api/articleApi.js";
import { getListUserApi } from "../hooks/api/profileApi.js";
const ViewArticle = () => {
const navigate = useNavigate();
// const location = useLocation();
const { articleId } = useParams();
const {user, setUser} = useContext(UserData)
const [title, setTitle] = useState("");
const [tags, setTags] = useState([]);
const [newTag, setNewTag] = useState("")
const [owner, setOwner] = useState("");
const [contentArticle, setContentArticle] = useState('');
const [cookies, _, __] = useCookies(["user"]);
useEffect(() => {
async function getArticle() {
const response = await getArticleApi(cookies.token, articleId)
const responseUser = await getListUserApi(cookies.token)
if (response.status === 200) {
setTitle(response.data.article.title)
setTags(response.data.article.tags ? response.data.article.tags : [])
setOwner(responseUser.status === 200 ? responseUser.data.find(item => item.id === response.data.article.owner_id).login : response.data.article.owner_id)
setContentArticle(response.data.blocks ? response.data.blocks[0].data : '')
}
else if (
response.status === 401
) {
// navigate("/enter")
setUser(false);
}
else {
console.log(response)
}
}
getArticle()
}, [])
return (
<div className={classes.main}>
<div className={classes.wrapper}>
<div className={classes.header}>
<div className={classes.header__wrapper}>
<div className={classes.header__wrapper__article}>
<div className={classes.header__wrapper__article__title}>
<span className={classes.header__wrapper__article__title__name}>Название</span>
<span className={classes.header__wrapper__article__title__text}>{title}</span>
</div>
<div className={classes.header__wrapper__article__tags}>
{/* <span className={classes.header__wrapper__article__tags__name}>Тэги</span> */}
{tags.map(item => <div className={classes.header__wrapper__tags__item}>
<span>#{item}</span>
</div>)}
</div>
<div className={classes.header__wrapper__article__owner}>
<span className={classes.header__wrapper__article__owner__name}>Автор</span>
<span className={classes.header__wrapper__article__owner__text}>{owner}</span>
</div>
</div>
</div>
</div>
<div className={classes.content}>
<div className={classes.content__wrapper}>
<div className="classes.content__wrapper__article" dangerouslySetInnerHTML={{__html: contentArticle}}>
</div>
</div>
</div>
</div>
</div>
)
}
export default ViewArticle

View File

@ -5,8 +5,7 @@ import classes from "../assets/styles/viewForm.module.scss";
import { FormsData, TypeAnswerData, answersData, UserData } from "../context"; import { FormsData, TypeAnswerData, answersData, UserData } from "../context";
import GeneratingFormFields from "../components/GeneratingFormFields.jsx"; import GeneratingFormFields from "../components/GeneratingFormFields.jsx";
import MyButton from "../components/MyButton.jsx"; import MyButton from "../components/MyButton.jsx";
import { listFormBlockApi, listFormBlockByTokenApi, saveAnswersApi } from "../hooks/api/formApi.js"; import { listFormBlockByTokenApi, saveAnswersApi } from "../hooks/api/formApi.js";
import { listFormsApi } from "../hooks/api/listFormsApi.js";
import { responseDataToListBlock } from "../hooks/sundry/parseListBlock.js"; import { responseDataToListBlock } from "../hooks/sundry/parseListBlock.js";
const ViewForm = () => { const ViewForm = () => {
@ -19,21 +18,28 @@ const ViewForm = () => {
const [cookies, setCookies, __] = useCookies(["user"]); const [cookies, setCookies, __] = useCookies(["user"]);
const [questions, setQuestions] = useState([]); const [questions, setQuestions] = useState([]);
const [answers, setAnswers] = useState([]); const [answers, setAnswers] = useState([]);
const [responseStatus, setResponseStatus] = useState(200);
useEffect(() => { useEffect(() => {
async function getForm() { async function getForm() {
const responseBlocks = await listFormBlockByTokenApi(cookies.token, formId); const responseBlocks = await listFormBlockByTokenApi(cookies.token, formId);
if (responseBlocks.status === 200 && responseBlocks.data) { if (responseBlocks.status === 200 && responseBlocks.data.blocks) {
const listBlocks = responseDataToListBlock(responseBlocks.data.blocks); const listBlocks = responseDataToListBlock(responseBlocks.data.blocks);
setQuestions(listBlocks) setQuestions(listBlocks);
setAnswers(listBlocks.map(item => ( setAnswers(listBlocks.map(item => (
{id: item.id, answer: []} {id: item.id, answer: []}
))) )));
}
else if (
responseBlocks.status === 401
) {
// navigate("/enter")
setUser(false);
} }
else { else {
console.log(responseBlocks) setResponseStatus(responseBlocks.status)
} }
}; };
@ -41,7 +47,6 @@ const ViewForm = () => {
}, []); }, []);
function updateAnswersForm(value, id) { function updateAnswersForm(value, id) {
console.log(value)
setAnswers( setAnswers(
answers.map((item, i) => { answers.map((item, i) => {
if (id === i) { if (id === i) {
@ -53,7 +58,6 @@ const ViewForm = () => {
}; };
async function saveAnswers() { async function saveAnswers() {
console.log(answers)
const response = await saveAnswersApi(cookies.token, formId, answers); const response = await saveAnswersApi(cookies.token, formId, answers);
if (response.status === 200) { if (response.status === 200) {
@ -66,11 +70,20 @@ const ViewForm = () => {
setAnswers([]); setAnswers([]);
navigate("/forms"); navigate("/forms");
} }
else if (
response.status === 401
) {
// navigate("/enter")
setUser(false);
}
else {
console.log(response)
}
} }
return ( return (
<div className={classes.main}> <div className={classes.main}>
{questions ? {questions.length ?
<div className={classes.wrapper}> <div className={classes.wrapper}>
<div className={classes.form}> <div className={classes.form}>
<div className={classes.form__header}> <div className={classes.form__header}>
@ -83,8 +96,8 @@ const ViewForm = () => {
</div> </div>
</div> </div>
<div className={classes.footer}> <div className={classes.footer}>
<MyButton text={"Отправить"} click={saveAnswers}/> <MyButton text={"Отправить"} class={"main__green"} click={saveAnswers}/>
<MyButton text={"Отмена"} backgroundColor={"rgb(180, 180, 180)"} click={() => { <MyButton text={"Отмена"} class={"main__white"} click={() => {
setAnswers([]); setAnswers([]);
navigate("/"); navigate("/");
}}/> }}/>
@ -93,7 +106,11 @@ const ViewForm = () => {
<div className={classes.wrapper}> <div className={classes.wrapper}>
<div className={classes.form}> <div className={classes.form}>
<div className={classes.form__unexists}> <div className={classes.form__unexists}>
<span>Форма с идентификатором #{formId} отсутствует.</span> <span>
{responseStatus === 200 ?
`Форма с идентификатором #${formId} пустая.` :
`Форма с идентификатором #${formId} отсутствует.`}
</span>
</div> </div>
</div> </div>
</div>} </div>}

View File

@ -1,25 +1,36 @@
const protectedUrl = { const protectedUrl = {
notAuthorized: [ notAuthorized: [
"/forms", new RegExp("/forms"),
"/forms/edit", new RegExp("/forms/\\w+/edit"),
"/profile" new RegExp("/profile"),
new RegExp("/forms/\\w+/answers"),
new RegExp("/tokens/\\w+"),
new RegExp("/admin"),
new RegExp("/articles"),
new RegExp("/articles/\\w+/edit"),
], ],
Authorized: [ Authorized: [
"/enter" "/enter"
], ],
notRights: [ notRights: [
"/forms" new RegExp("/forms"),
new RegExp("/forms/\\w+/edit"),
new RegExp("/forms/\\w+/answers"),
new RegExp("/tokens/\\w+"),
new RegExp("/admin"),
new RegExp("/articles"),
new RegExp("/articles/\\w+/edit"),
] ]
} }
function globalRender(url, user, navigate) { function globalRender(url, user, navigate) {
if (!user && protectedUrl.notAuthorized.some(item => item === url)) { if (!user && protectedUrl.notAuthorized.some(item => url.match(item) ? url.match(item)[0] === url : false)) {
navigate("/enter") navigate("/enter")
} }
else if (user && protectedUrl.Authorized.some(item => item === url)) { else if (user && protectedUrl.Authorized.some(item => item === url)) {
navigate("/") navigate("/")
} }
else if (user && protectedUrl.notRights.some(item => item === url)) { else if (user && protectedUrl.notRights.some(item => url.match(item) ? url.match(item)[0] === url : false)) {
if (!user.is_admin) { if (!user.is_admin) {
navigate("/") navigate("/")
} }

View File

@ -9,6 +9,10 @@ import Profile from "../pages/Profile.jsx";
import ViewForm from "../pages/ViewForm.jsx"; import ViewForm from "../pages/ViewForm.jsx";
import AdminPanel from '../pages/AdminPanel.jsx'; import AdminPanel from '../pages/AdminPanel.jsx';
import AnswersForm from '../pages/AnswersForm.jsx'; import AnswersForm from '../pages/AnswersForm.jsx';
import TokensForm from '../pages/TokensForm.jsx';
import Articles from '../pages/Articles.jsx';
import NewArticle from '../pages/NewArticle.jsx';
import ViewArticle from '../pages/ViewArticle.jsx';
const router = createBrowserRouter([ const router = createBrowserRouter([
{ {
@ -45,6 +49,22 @@ const router = createBrowserRouter([
{ {
path: "/forms/:formId/answers", path: "/forms/:formId/answers",
element: <AnswersForm/> element: <AnswersForm/>
},
{
path: "/tokens/:formId",
element: <TokensForm/>
},
{
path: "/articles",
element: <Articles/>
},
{
path: "/articles/:articleId/edit",
element: <NewArticle/>
},
{
path: "/articles/:articleId/",
element: <ViewArticle/>
} }
] ]
} }

View File

@ -1,5 +1,6 @@
const HtmlWebpackPlugin = require("html-webpack-plugin"); const HtmlWebpackPlugin = require("html-webpack-plugin");
const MiniCssExtractPlugin = require("mini-css-extract-plugin"); const MiniCssExtractPlugin = require("mini-css-extract-plugin");
// const Inputmask = require('inputmask');
const path = require("path"); const path = require("path");
module.exports = (env) => { module.exports = (env) => {