quiz
This commit is contained in:
17
src/App.js
17
src/App.js
@ -1,4 +1,4 @@
|
||||
import React, { Suspense, lazy } from 'react'
|
||||
import React, { Suspense, lazy, useEffect, useState } from 'react'
|
||||
import { BrowserRouter as Router, Route, Switch } from 'react-router-dom'
|
||||
import { useSelector } from 'react-redux'
|
||||
import { selectAuth } from './redux/outstaffingSlice'
|
||||
@ -6,6 +6,7 @@ import 'bootstrap/dist/css/bootstrap.min.css'
|
||||
import './fonts/stylesheet.css'
|
||||
import { ProtectedRoute } from './components/ProtectedRoute/ProtectedRoute'
|
||||
|
||||
|
||||
import { YMInitializer } from 'react-yandex-metrika'
|
||||
|
||||
import AuthPageForDevelopers from './pages/AuthPageForDevelopers'
|
||||
@ -16,6 +17,13 @@ import CalendarPage from './pages/CalendarPage'
|
||||
import ReportPage from './pages/ReportFormPage.js'
|
||||
import FormPage from './pages/FormPage.js'
|
||||
import SingleReportPage from './pages/SingleReportPage'
|
||||
import { Highlighter } from './pages/CodeSnippetPage'
|
||||
import { QuizPage } from './pages/quiz/QuizPage'
|
||||
import { InterjacentPage } from './pages/quiz/InterjacentPage'
|
||||
import { QuizTestPage } from './pages/quiz/QuizTestPage'
|
||||
import { Instruction } from './components/features/quiz/Instructions'
|
||||
import { InstructionPage } from './pages/quiz/InstructionPage'
|
||||
import {ResultPage} from "./pages/quiz/ResultPage";
|
||||
|
||||
const App = (props) => {
|
||||
const isAuth = useSelector(selectAuth)
|
||||
@ -44,6 +52,11 @@ const App = (props) => {
|
||||
/>
|
||||
<ProtectedRoute exact path='/report' component={ReportPage} />
|
||||
<ProtectedRoute path='/report/:id' component={SingleReportPage} />
|
||||
<ProtectedRoute path='/quiz' component={QuizPage} />
|
||||
<ProtectedRoute path='/quiz-interjacent' component={InterjacentPage} />
|
||||
<ProtectedRoute path='/quiz-test' component={QuizTestPage} />
|
||||
<ProtectedRoute path='/quiz-instruction' component={InstructionPage} />
|
||||
<ProtectedRoute path='/quiz-result' component={ResultPage} />
|
||||
<ProtectedRoute component={() => <div>Page not found</div>} />
|
||||
</Switch>
|
||||
</Router>
|
||||
@ -63,3 +76,5 @@ const App = (props) => {
|
||||
}
|
||||
|
||||
export default App
|
||||
|
||||
|
||||
|
@ -91,7 +91,7 @@ export const AuthBox = ({ title, roleChangeLink }) => {
|
||||
{isLoading ? <Loader /> : 'Войти'}
|
||||
</button>
|
||||
|
||||
<button className='auth-box__form-btn--role auth-box__auth-link'>
|
||||
<button className='auth-box__form-btn--role auth-box__auth-link' >
|
||||
<Link to={roleChangeLink}>Для разработчиков</Link>
|
||||
</button>
|
||||
</div>
|
||||
|
@ -18,12 +18,15 @@ import { Footer } from '../Footer/Footer'
|
||||
|
||||
import './candidate.scss'
|
||||
import { getRole } from '../../redux/roleSlice'
|
||||
import { CodeSnippetlighter } from '../../pages/CodeSnippetPage'
|
||||
import { useState } from 'react'
|
||||
|
||||
const Candidate = () => {
|
||||
const history = useHistory()
|
||||
const { id: candidateId } = useParams()
|
||||
const dispatch = useDispatch()
|
||||
const role = useSelector(getRole)
|
||||
const [activeSnippet, setActiveSnippet] = useState(true)
|
||||
|
||||
useEffect(() => {
|
||||
window.scrollTo(0, 0)
|
||||
@ -117,8 +120,11 @@ const Candidate = () => {
|
||||
<div className='candidate__main'>
|
||||
<div className='row'>
|
||||
<div className='col-12 col-xl-4'>
|
||||
<Sidebar candidate={currentCandidateObj} position />
|
||||
<Sidebar candidate={currentCandidateObj} position activeSnippet={activeSnippet} setActiveSnippet={setActiveSnippet}/>
|
||||
</div>
|
||||
{
|
||||
activeSnippet ?
|
||||
(
|
||||
<div className='col-12 col-xl-8'>
|
||||
<div className='candidate__main-description'>
|
||||
<img src={rectangle} alt='' />
|
||||
@ -141,8 +147,44 @@ const Candidate = () => {
|
||||
</button>
|
||||
</Link> */}
|
||||
<SkillSection skillsArr={skillValues} />
|
||||
|
||||
</div>
|
||||
</div>
|
||||
) :
|
||||
(
|
||||
// <div className="col-12 col-xl-8">
|
||||
// <CodeSnippetlighter />
|
||||
// </div>
|
||||
<div className="col-12 col-xl-8">
|
||||
<div className="candidate__works works">
|
||||
<div className="works__body">
|
||||
<div className="works__item item-works">
|
||||
<div className="item-works__body">
|
||||
<Link to="/" className="item-works__link">Vuetifyis.com</Link>
|
||||
<div className="item-works__text">Forked from peluprvi/vuetifyjs.com <br /> Vuetifyjs.com documentation</div>
|
||||
<div className="item-works__mark">Angular</div>
|
||||
</div>
|
||||
</div>
|
||||
<div className="works__item item-works">
|
||||
<div className="item-works__body">
|
||||
<Link to="/" className="item-works__link">Vuetifyis.com</Link>
|
||||
<div className="item-works__text">Forked from peluprvi/vuetifyjs.com <br /> Vuetifyjs.com documentation</div>
|
||||
<div className="item-works__mark">Angular</div>
|
||||
</div>
|
||||
</div>
|
||||
<div className="works__item item-works">
|
||||
<div className="item-works__body">
|
||||
<Link to="/" className="item-works__link">Vuetifyis.com</Link>
|
||||
<div className="item-works__text">Forked from peluprvi/vuetifyjs.com <br /> Vuetifyjs.com documentation</div>
|
||||
<div className="item-works__mark item-works__mark_yellow">Laravel</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
)
|
||||
}
|
||||
|
||||
</div>
|
||||
</div>
|
||||
<Footer />
|
||||
|
@ -1,3 +1,4 @@
|
||||
@use 'sass:math';
|
||||
.candidate {
|
||||
&__title {
|
||||
margin-top: 60px;
|
||||
@ -291,3 +292,85 @@
|
||||
.candidate + .logout-button {
|
||||
top: 80px !important;
|
||||
}
|
||||
|
||||
|
||||
.candidate__works{
|
||||
@media (min-width: 576px) {
|
||||
padding: 0 0 45px 0;
|
||||
}
|
||||
}
|
||||
.works__body{
|
||||
display: flex;
|
||||
flex-wrap: wrap;
|
||||
margin: 0 -15px -30px;
|
||||
}
|
||||
.item-works{
|
||||
display: flex;
|
||||
flex-direction: column;
|
||||
padding: 0 15px;
|
||||
margin: 0 0 30px 0;
|
||||
flex: 0 1 50%;
|
||||
position: relative;
|
||||
|
||||
@media (max-width: 761px) {
|
||||
flex: 0 1 100%;
|
||||
}
|
||||
|
||||
&__body{
|
||||
padding: 17px;
|
||||
border: 2px solid #54b611;
|
||||
border-radius: 10px;
|
||||
|
||||
&::before{
|
||||
content: "";
|
||||
position: absolute;
|
||||
top: -2px;
|
||||
left: 25px;
|
||||
height: 5px;
|
||||
width: 29px;
|
||||
border-radius: 3px;
|
||||
background-color: #54b611;
|
||||
}
|
||||
}
|
||||
|
||||
&__link{
|
||||
color: #0350dc;
|
||||
font-family: "GT Eesti Pro Display";
|
||||
font-size: 16px;
|
||||
font-weight: 400;
|
||||
letter-spacing: normal;
|
||||
line-height: math.div(36,16);
|
||||
text-decoration: underline;
|
||||
display: block;
|
||||
margin-bottom: 17px;
|
||||
}
|
||||
&__text{
|
||||
margin-bottom: 37px;
|
||||
color: #000000;
|
||||
font-family: "GT Eesti Pro Display - Thin";
|
||||
font-size: 12px;
|
||||
font-weight: 400;
|
||||
line-height: math.div(18,12);
|
||||
}
|
||||
&__mark{
|
||||
font-size: 13px;
|
||||
display: flex;
|
||||
align-items: center;
|
||||
column-gap: 7px;
|
||||
line-height: math.div(36,13);
|
||||
|
||||
&::before{
|
||||
border-radius: 50%;
|
||||
content: "";
|
||||
width: 13px;
|
||||
height: 13px;
|
||||
background-color: #73c141;
|
||||
}
|
||||
&_yellow{
|
||||
&::before{
|
||||
background-color: #e09f14;
|
||||
}
|
||||
}
|
||||
|
||||
}
|
||||
}
|
@ -6,6 +6,9 @@ import { LEVELS, SKILLS } from '../constants/constants'
|
||||
|
||||
import maleBig from '../../images/medium_male_big.png'
|
||||
import './candidateSidebar.scss'
|
||||
import { Highlighter } from '../../App'
|
||||
import { useState } from 'react'
|
||||
import { useEffect } from 'react'
|
||||
|
||||
const getYearsString = (years) => {
|
||||
let yearsString
|
||||
@ -21,7 +24,12 @@ const getYearsString = (years) => {
|
||||
return `${years} ${yearsString}`
|
||||
}
|
||||
|
||||
const CandidateSidebar = ({ candidate, position }) => {
|
||||
const CandidateSidebar = ({ candidate, position, setActiveSnippet, activeSnippet }) => {
|
||||
|
||||
const showSnippet = () => {
|
||||
setActiveSnippet((prev)=>!prev)
|
||||
}
|
||||
|
||||
return (
|
||||
<div className='candidate-sidebar'>
|
||||
<div className='candidate-sidebar__info'>
|
||||
@ -50,13 +58,22 @@ const CandidateSidebar = ({ candidate, position }) => {
|
||||
Отчёты
|
||||
</button>
|
||||
</Link>
|
||||
{/* <Link to={`/candidate/${candidate.id}/code`}> */}
|
||||
<button
|
||||
className='candidate-sidebar__select'
|
||||
onClick={showSnippet}
|
||||
>
|
||||
{activeSnippet ? "Показать": "Скрыть"}
|
||||
</button>
|
||||
{/* </Link> */}
|
||||
<div className='candidate-sidebar__achievements'>
|
||||
{candidate &&
|
||||
candidate.achievements &&
|
||||
candidate.achievements.map((item) => {
|
||||
return <Achievement achievement={item.achievement} />
|
||||
return <Achievement key={item.id} achievement={item.achievement} />
|
||||
})}
|
||||
</div>
|
||||
|
||||
</div>
|
||||
</div>
|
||||
)
|
||||
|
@ -25,8 +25,6 @@ export const HeaderQuiz = ({header}) => {
|
||||
})
|
||||
}, [dispatch])
|
||||
|
||||
console.log('render header quiz')
|
||||
|
||||
return (
|
||||
<div className="header-quiz">
|
||||
<div className="header-quiz__container">
|
||||
|
@ -9,7 +9,7 @@ import {fetchGet} from "../../../server/server";
|
||||
|
||||
export const Instruction = () => {
|
||||
|
||||
const [countQuestions, setCountQuestions] = useState()
|
||||
const [countQuestions, setCountQuestions] = useState(null)
|
||||
const test = useSelector(selectedTest)
|
||||
|
||||
useEffect(async () => {
|
||||
@ -26,21 +26,26 @@ export const Instruction = () => {
|
||||
return (
|
||||
<div className="instruction">
|
||||
<div className="instruction__container">
|
||||
<h3 className="instruction__title quiz-title_h3">Инструкция к тесту</h3>
|
||||
<div className="instruction__text">
|
||||
Lorem ipsum dolor sit amet, consectetur adipisicing elit, sed do eiusmod tempoLorem ipsum dolor sit
|
||||
amet,Lo
|
||||
rem ipsum dolor sit amet, consectetur adipisicing elit, sed do eiusmod tempo consectetur adipisicing e
|
||||
lit, sed do eiusmod tempo
|
||||
</div>
|
||||
<Link to="/quiz-test" className='instruction__btn quiz-btn quiz-btn_restriction'>Далее</Link>
|
||||
<div className="instruction__info">
|
||||
<div className="instruction__icon">
|
||||
<img src={comment} alt=""/>
|
||||
</div>
|
||||
<div className="instruction__text instruction__text_info">Количество вопросов в
|
||||
тесте: <span>{countQuestions}</span></div>
|
||||
</div>
|
||||
{!countQuestions ? <h2>Loading...</h2> :
|
||||
<>
|
||||
<h3 className="instruction__title quiz-title_h3">Инструкция к тесту</h3>
|
||||
<div className="instruction__text">
|
||||
Lorem ipsum dolor sit amet, consectetur adipisicing elit, sed do eiusmod tempoLorem ipsum dolor sit
|
||||
amet,Lo
|
||||
rem ipsum dolor sit amet, consectetur adipisicing elit, sed do eiusmod tempo consectetur adipisicing
|
||||
e
|
||||
lit, sed do eiusmod tempo
|
||||
</div>
|
||||
<Link to="/quiz-test" className='instruction__btn quiz-btn quiz-btn_restriction'>Далее</Link>
|
||||
<div className="instruction__info">
|
||||
<div className="instruction__icon">
|
||||
<img src={comment} alt=""/>
|
||||
</div>
|
||||
<div className="instruction__text instruction__text_info">Количество вопросов в
|
||||
тесте: <span>{countQuestions}</span></div>
|
||||
</div>
|
||||
</>
|
||||
}
|
||||
</div>
|
||||
</div>
|
||||
)
|
||||
|
@ -0,0 +1,37 @@
|
||||
import React, {useEffect, useState} from 'react';
|
||||
import {useDispatch, useSelector} from "react-redux";
|
||||
import {fetchResultTest, selectedTest, selectResult} from "../../../redux/quizSlice";
|
||||
import {fetchGet} from "../../../server/server";
|
||||
|
||||
|
||||
export const Results = () => {
|
||||
const result = useSelector(selectResult)
|
||||
const test = useSelector(selectedTest)
|
||||
const [maxScore, setMaxScore] = useState('')
|
||||
const dispatch = useDispatch()
|
||||
|
||||
useEffect(async () => {
|
||||
dispatch(fetchResultTest(test.uuid))
|
||||
const response = await fetchGet({
|
||||
link: `${process.env.REACT_APP_API_URL}/api/user-questionnaire/get-points-number?user_questionnaire_uuid=${test.uuid}`,
|
||||
Origin: `${process.env.REACT_APP_BASE_URL}`,
|
||||
}
|
||||
)
|
||||
setMaxScore(response.sum_point)
|
||||
}, [])
|
||||
|
||||
return (
|
||||
<div className={'result _container'}>
|
||||
{
|
||||
!result ? <h1 style={{display: "block"}}>Ожидайте результата...</h1> :
|
||||
|
||||
<div className="result__body">
|
||||
<div className="result__text">Благодарим за прохождение теста</div>
|
||||
<div className="result__text">Ваш Результат: <span
|
||||
className="result__score">{result.score}</span> из {maxScore} </div>
|
||||
</div>
|
||||
}
|
||||
</div>
|
||||
);
|
||||
};
|
||||
|
||||
|
@ -29,8 +29,6 @@ export const TaskQuiz = () => {
|
||||
const id = localStorage.getItem('id');
|
||||
const [questions, setQuestions] = useState([])
|
||||
|
||||
console.log("render task");
|
||||
|
||||
useEffect(async () => {
|
||||
const response = await fetchGet({
|
||||
link: `${process.env.REACT_APP_API_URL}/api/question/get-questions?uuid=${dataTest.uuid}`,
|
||||
@ -50,12 +48,12 @@ export const TaskQuiz = () => {
|
||||
if (checkedValues.length || inputValue) {
|
||||
switch (questions[index].question_type_id) {
|
||||
case '3':
|
||||
// await dispatch(fetchUserAnswersMany(checkedValues))
|
||||
await dispatch(fetchUserAnswersMany(checkedValues))
|
||||
break;
|
||||
case '2':
|
||||
case '1':
|
||||
case '4':
|
||||
// await dispatch(fetchUserAnswerOne(checkedValues))
|
||||
await dispatch(fetchUserAnswerOne(checkedValues))
|
||||
break;
|
||||
default:
|
||||
break;
|
||||
@ -70,7 +68,7 @@ export const TaskQuiz = () => {
|
||||
setInputValue('')
|
||||
} else {
|
||||
history.push(`/quiz-result`)
|
||||
// alert("Тест пройден!")
|
||||
alert("Тест пройден!")
|
||||
}
|
||||
|
||||
} else {
|
||||
@ -133,7 +131,6 @@ export const TaskQuiz = () => {
|
||||
answer={answer}
|
||||
/>
|
||||
))
|
||||
|
||||
}
|
||||
<div className="form-task__buttons">
|
||||
{questions.length !== index + 1 &&
|
||||
|
0
src/fonts/muller_extrabold_demo.ttf
Executable file → Normal file
0
src/fonts/muller_extrabold_demo.ttf
Executable file → Normal file
BIN
src/images/comment.jpg
Normal file
BIN
src/images/comment.jpg
Normal file
Binary file not shown.
After Width: | Height: | Size: 1.8 KiB |
@ -32,4 +32,4 @@ h1 {
|
||||
max-width: 100%;
|
||||
flex: initial;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
21
src/pages/CodeSnippetPage.js
Normal file
21
src/pages/CodeSnippetPage.js
Normal file
@ -0,0 +1,21 @@
|
||||
import { useEffect, useState } from "react";
|
||||
import { Prism as SyntaxHighlighter } from 'react-syntax-highlighter';
|
||||
import { a11yDark } from 'react-syntax-highlighter/dist/esm/styles/prism';
|
||||
|
||||
export const CodeSnippetlighter = () => {
|
||||
const [codeString, setCodeString] = useState(``)
|
||||
|
||||
useEffect(()=>{
|
||||
fetch('/code.txt')
|
||||
.then((r) => r.text())
|
||||
.then(text => {
|
||||
setCodeString(text)
|
||||
})
|
||||
}, [])
|
||||
|
||||
return (
|
||||
<SyntaxHighlighter language={"javascript"} style={a11yDark} wrapLongLines={false} customStyle={{fontSize:14}} showLineNumbers={true}>
|
||||
{codeString}
|
||||
</SyntaxHighlighter>
|
||||
);
|
||||
};
|
1
src/pages/code.txt
Normal file
1
src/pages/code.txt
Normal file
@ -0,0 +1 @@
|
||||
Hellow
|
@ -0,0 +1,23 @@
|
||||
import {Link, Redirect} from "react-router-dom"
|
||||
import {HeaderPageTestsQuiz} from "../../components/features/quiz/HeaderPageTests"
|
||||
import {Results} from "../../components/features/quiz/Results";
|
||||
import {useSelector} from "react-redux";
|
||||
import {selectedTest} from "../../redux/quizSlice";
|
||||
import React from "react";
|
||||
|
||||
|
||||
export const ResultPage = () => {
|
||||
|
||||
const test = useSelector(selectedTest)
|
||||
|
||||
if (!test) {
|
||||
return <Redirect to={'/quiz'}/>
|
||||
}
|
||||
|
||||
return (
|
||||
<>
|
||||
<HeaderPageTestsQuiz isVisibilityButton={false}/>
|
||||
<Results/>
|
||||
</>
|
||||
)
|
||||
}
|
@ -8,7 +8,7 @@ export const withAuthRedirect =
|
||||
.then((res) => {
|
||||
if (res.status && res.status == 401) {
|
||||
localStorage.clear()
|
||||
logout()
|
||||
logout && logout()
|
||||
history.push(role === 'ROLE_DEV' ? '/authdev' : '/auth')
|
||||
}
|
||||
|
||||
@ -16,7 +16,7 @@ export const withAuthRedirect =
|
||||
})
|
||||
.catch((err) => {
|
||||
localStorage.clear()
|
||||
logout()
|
||||
logout && logout()
|
||||
history.push(role === 'ROLE_DEV' ? '/authdev' : '/auth')
|
||||
})
|
||||
}
|
||||
|
@ -49,6 +49,7 @@ export const fetchAuth = async ({
|
||||
|
||||
response.json().then((resJSON) => {
|
||||
localStorage.setItem('auth_token', resJSON.access_token)
|
||||
localStorage.setItem('id', resJSON.id)
|
||||
localStorage.setItem(
|
||||
'access_token_expired_at',
|
||||
resJSON.access_token_expired_at
|
||||
@ -104,7 +105,8 @@ export const fetchPost = withAuthRedirect(async (link, body) => {
|
||||
headers: {
|
||||
Authorization: `Bearer ${localStorage.getItem('auth_token')}`,
|
||||
'Content-Type': 'application/json',
|
||||
Origin: `http://localhost`
|
||||
//Origin: `http://localhost:3000`
|
||||
Origin: `${process.env.REACT_APP_BASE_URL}`,
|
||||
},
|
||||
body: JSON.stringify(body)
|
||||
})
|
||||
|
@ -3,6 +3,8 @@ import outstaffingReducer from '../redux/outstaffingSlice';
|
||||
import loaderReducer from '../redux/loaderSlice';
|
||||
import roleReducer from '../redux/roleSlice';
|
||||
import reportReducer from '../redux/reportSlice';
|
||||
import quizSlice from '../redux/quizSlice';
|
||||
|
||||
|
||||
export const store = configureStore({
|
||||
reducer: {
|
||||
@ -10,5 +12,10 @@ export const store = configureStore({
|
||||
loader: loaderReducer,
|
||||
role: roleReducer,
|
||||
report: reportReducer,
|
||||
quiz: quizSlice,
|
||||
},
|
||||
middleware: getDefaultMiddleware =>
|
||||
getDefaultMiddleware({
|
||||
serializableCheck: false,
|
||||
}),
|
||||
});
|
||||
|
Reference in New Issue
Block a user