Merge pull request #40 from apuc/profilePage

Profile page
This commit is contained in:
Z1chi 2023-01-16 21:24:18 +03:00 committed by GitHub
commit 42e2d0e181
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23
93 changed files with 7302 additions and 25990 deletions

View File

@ -1,2 +1,2 @@
REACT_APP_API_URL = https://dev.itguild.info REACT_APP_API_URL = https://dev.itguild.info/api
REACT_APP_BASE_URL = https://dev.itguild.info REACT_APP_BASE_URL = https://dev.itguild.info/api

29159
package-lock.json generated

File diff suppressed because it is too large Load Diff

View File

@ -12,7 +12,7 @@
"@testing-library/user-event": "^12.8.3", "@testing-library/user-event": "^12.8.3",
"@typescript-eslint/eslint-plugin": "^4.5.0", "@typescript-eslint/eslint-plugin": "^4.5.0",
"@typescript-eslint/parser": "^4.5.0", "@typescript-eslint/parser": "^4.5.0",
"axios": "^0.21.1", "axios": "^0.24.0",
"babel-eslint": "^10.1.0", "babel-eslint": "^10.1.0",
"babel-jest": "^26.6.0", "babel-jest": "^26.6.0",
"babel-loader": "8.1.0", "babel-loader": "8.1.0",
@ -65,7 +65,7 @@
"react-phone-input-2": "^2.14.0", "react-phone-input-2": "^2.14.0",
"react-redux": "^7.2.4", "react-redux": "^7.2.4",
"react-refresh": "^0.8.3", "react-refresh": "^0.8.3",
"react-router-dom": "^5.2.0", "react-router-dom": "^6.2.1",
"react-select": "^4.3.1", "react-select": "^4.3.1",
"react-syntax-highlighter": "^15.4.5", "react-syntax-highlighter": "^15.4.5",
"react-yandex-metrika": "^2.6.0", "react-yandex-metrika": "^2.6.0",
@ -84,7 +84,8 @@
"webpack": "4.44.2", "webpack": "4.44.2",
"webpack-dev-server": "3.11.1", "webpack-dev-server": "3.11.1",
"webpack-manifest-plugin": "2.2.0", "webpack-manifest-plugin": "2.2.0",
"workbox-webpack-plugin": "5.1.4" "workbox-webpack-plugin": "5.1.4",
"react-router": "latest"
}, },
"scripts": { "scripts": {
"start": "node scripts/start.js", "start": "node scripts/start.js",

View File

@ -1,5 +1,5 @@
<!DOCTYPE html> <!DOCTYPE html>
<html lang="en"> <html lang="en" style="height: 100%">
<head> <head>
<meta charset="utf-8" /> <meta charset="utf-8" />
<meta name="viewport" content="width=device-width, initial-scale=1" /> <meta name="viewport" content="width=device-width, initial-scale=1" />
@ -14,7 +14,7 @@
<link rel="shortcut icon" href="favicon.ico" /> <link rel="shortcut icon" href="favicon.ico" />
</head> </head>
<body> <body style="min-height: 100%">
<!-- <noscript>You need to enable JavaScript to run this app.</noscript> --> <!-- <noscript>You need to enable JavaScript to run this app.</noscript> -->
<div id="root"></div> <div id="root"></div>
<!-- <script type="text/javascript"> <!-- <script type="text/javascript">

View File

@ -1,66 +1,66 @@
import React from 'react' import React from 'react'
import { BrowserRouter as Router, Route, Switch } from 'react-router-dom' import {BrowserRouter as Router, Route, Routes, Navigate} from 'react-router-dom';
import 'bootstrap/dist/css/bootstrap.min.css'
import './fonts/stylesheet.css'
import { ProtectedRoute } from './components/ProtectedRoute/ProtectedRoute'
import AuthPageForDevelopers from './pages/AuthPageForDevelopers' import AuthForPartners from "./pages/AuthForPartners/AuthForPartners";
import AuthPageForPartners from './pages/AuthPageForPartners' import AuthForDevelopers from "./pages/AuthForDevelopers/AuthForDevelopers";
import HomePage from './pages/HomePage' import HomePage from './pages/HomePage'
import CandidatePage from './pages/CandidatePage' import CandidatePage from './pages/CandidatePage'
import CalendarPage from './pages/CalendarPage' import CalendarPage from './pages/CalendarPage'
import ReportPage from './pages/ReportFormPage.js' import ReportPage from './pages/ReportFormPage.js'
import ProfileCalendarPage from './pages/ProfileCalendarPage.js'
import FormPage from './pages/FormPage.js' import FormPage from './pages/FormPage.js'
import SingleReportPage from './pages/SingleReportPage' import SingleReportPage from './pages/SingleReportPage'
import { QuizPage } from './pages/quiz/QuizPage' import {QuizPage} from './pages/quiz/QuizPage'
import { InterjacentPage } from './pages/quiz/InterjacentPage' import {InterjacentPage} from './pages/quiz/InterjacentPage'
import { QuizTestPage } from './pages/quiz/QuizTestPage' import {QuizTestPage} from './pages/quiz/QuizTestPage'
import { InstructionPage } from './pages/quiz/InstructionPage' import {InstructionPage} from './pages/quiz/InstructionPage'
import { ResultPage } from './pages/quiz/ResultPage' import {ResultPage} from './pages/quiz/ResultPage'
import {Profile} from './pages/Profile/Profile.js'
import {Summary} from './pages/Summary/Summary'
import './fonts/stylesheet.css'
import 'bootstrap/dist/css/bootstrap.min.css'
const App = () => { const App = () => {
return ( return (
<> <>
<h1>IT Аутстаффинг в России</h1> <h1>IT Аутстаффинг в России</h1>
<Router> <Router>
<Switch>
<Route path='/authdev' exact> <Routes>
<AuthPageForDevelopers /> <Route exact path='/' element={<HomePage/>}/>
<Route exact path='/authdev' element={<AuthForDevelopers/>}/>
<Route exact path='/auth' element={<AuthForPartners/>}/>
<Route exact path='/candidate/:id' element={<CandidatePage/>}/>
<Route exact path='/candidate/:id/form' element={<FormPage/>}/>
<Route path='/:userId/calendar' element={<CalendarPage/>}/>
<Route exact path='/report' element={<ReportPage/>}/>
<Route path='/report/:id' element={<SingleReportPage/>}/>
<Route exact path='quiz'>
<Route index element={<QuizPage/>}/>
<Route exact path='interjacent' element={<InterjacentPage/>}/>
<Route exact path='test' element={<QuizTestPage/>}/>
<Route exact path='instruction' element={<InstructionPage/>}/>
<Route exact path='result' element={<ResultPage/>}/>
</Route> </Route>
<Route path='/auth' exact>
<AuthPageForPartners /> <Route exact path='profile'>
<Route index element={<Profile/>}/>
<Route exact path='calendar' element={<ProfileCalendarPage/>}/>
<Route exact path='summary' element={<Summary/>}/>
</Route> </Route>
<ProtectedRoute exact path='/' component={HomePage} />
<ProtectedRoute <Route path="*" element={<Navigate to="/" replace/>}/>
exact </Routes>
path='/candidate/:id'
component={CandidatePage}
/>
<ProtectedRoute path='/:userId/calendar' component={CalendarPage} />
<ProtectedRoute
exact
path='/candidate/:id/form'
component={FormPage}
/>
<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> </Router>
</> </>
) )
} };
export default App export default App

48
src/api/request.js Normal file
View File

@ -0,0 +1,48 @@
// import axios from 'axios';
// import {getToken, urlHasParams} from "../helper";
//
//
//
// const instance = axios.create({
// baseURL: process.env.REACT_APP_API_URL,
// validateStatus(status) {
// return status;
// },
// });
//
// export default function request(url, {method = 'get', params, data, headers} = {}) {
// const fullHeaders = {...headers, ...getToken()};
// let urWithParams = urlHasParams(url);
//
//
// return instance
// .request({
// url: urWithParams,
// method,
// params,
// data,
// headers: {...fullHeaders},
// })
// .then(response => new Promise(resolve => {
// console.log(response, 1)
// if(response.data.redirect || response.status === 401) {
//
// // window.location.href = "/auth"
// }
// return resolve(response)
// }))
// .then(response => new Promise(resolve => resolve(response.data)))
// }
//
// function RequestError(code, msg, data) {
// const description = msg ? `- ${msg}` : '';
//
// this.name = 'RequestError';
// this.message = `API returned: ${code}${description}.`;
// this.code = code;
// this.description = msg;
// this.data = data;
// }
//
// RequestError.prototype = Object.create(Error.prototype);
// RequestError.prototype.constructor = RequestError;

View File

@ -6,7 +6,7 @@ export const Achievement = ({ achievement }) => {
return ( return (
<div className='achievement'> <div className='achievement'>
<div className='achievement__icon'> <div className='achievement__icon'>
<img src={achievement.img} /> <img src={achievement.img} alt='achievement' />
</div> </div>
<div className='achievement__popup'> <div className='achievement__popup'>
<div className='achievement__title'>{achievement.title}</div> <div className='achievement__title'>{achievement.title}</div>

View File

@ -1,31 +1,71 @@
import React, { useState } from 'react' import React, {useState} from 'react'
import { Link } from 'react-router-dom' import {Link} from 'react-router-dom'
import { useDispatch, useSelector } from 'react-redux' import {useDispatch, useSelector} from 'react-redux'
import {withSwalInstance} from 'sweetalert2-react'
import swal from 'sweetalert2'
import {Loader} from '../Loader/Loader'
import {auth, setUserInfo} from '../../redux/outstaffingSlice'
import {loading} from '../../redux/loaderSlice'
import {setRole} from '../../redux/roleSlice'
import {selectIsLoading} from '../../redux/loaderSlice'
import { auth, setUserInfo } from '../../redux/outstaffingSlice'
import { loading } from '../../redux/loaderSlice'
import ellipse from '../../images/ellipse.png' import ellipse from '../../images/ellipse.png'
import { Loader } from '../Loader/Loader'
import { fetchAuth } from '../../server/server'
import { setRole } from '../../redux/roleSlice'
import { selectIsLoading } from '../../redux/loaderSlice'
import './authBox.scss' import './authBox.scss'
import {useRequest} from "../../hooks/useRequest";
import { withSwalInstance } from 'sweetalert2-react'
import swal from 'sweetalert2'
const SweetAlert = withSwalInstance(swal)
export const AuthBox = ({ title, altTitle, roleChangeLink }) => { const SweetAlert = withSwalInstance(swal);
const dispatch = useDispatch()
const isLoading = useSelector(selectIsLoading) export const AuthBox = ({title, altTitle, roleChangeLink}) => {
const dispatch = useDispatch();
const [username, setUsername] = useState('') const {apiRequest} = useRequest();
const [password, setPassword] = useState('')
const [error, setError] = useState(null) const isLoading = useSelector(selectIsLoading);
const [username, setUsername] = useState('');
const [password, setPassword] = useState('');
const [error, setError] = useState(null);
const submitHandler = () => {
if (!isLoading) {
dispatch(loading(true));
apiRequest('/user/login',
{
method: 'POST',
data: JSON.stringify({
username,
password
})
}).then((res) => {
if (!res.access_token) {
setError('Некорректные данные для входа');
dispatch(loading(false))
} else {
localStorage.setItem('auth_token', res.access_token);
localStorage.setItem('id', res.id);
localStorage.setItem('cardId', res.card_id);
localStorage.setItem(
'access_token_expired_at',
res.access_token_expired_at
);
dispatch(auth(true));
dispatch(setUserInfo(res));
dispatch(loading(false));
dispatch(setRole('ROLE_PARTNER'))
}
})
}
};
return ( return (
<div className='auth-box'> <div className='auth-box'>
@ -33,7 +73,7 @@ export const AuthBox = ({ title, altTitle, roleChangeLink }) => {
Войти в <span>систему</span> Войти в <span>систему</span>
</h2> </h2>
<div className='auth-box__title'> <div className='auth-box__title'>
<img src={ellipse} alt='' /> <img src={ellipse} alt=''/>
<span>{title}</span> <span>{title}</span>
</div> </div>
<form className='auth-box__form'> <form className='auth-box__form'>
@ -69,30 +109,12 @@ export const AuthBox = ({ title, altTitle, roleChangeLink }) => {
<div className='auth-box__form-buttons'> <div className='auth-box__form-buttons'>
<button <button
className='auth-box__form-btn' className='auth-box__form-btn'
onClick={ onClick={(e) => {
!isLoading e.preventDefault();
? (e) => { submitHandler()
e.preventDefault() }}
dispatch(loading(true))
fetchAuth({
username,
password,
dispatch: (resJSON) => {
dispatch(auth(true))
dispatch(setUserInfo(resJSON))
dispatch(loading(false))
dispatch(setRole('ROLE_PARTNER'))
},
catchError: () => {
setError('Некорректные данные для входа')
dispatch(loading(false))
}
})
}
: () => {}
}
> >
{isLoading ? <Loader /> : 'Войти'} {isLoading ? <Loader/> : 'Войти'}
</button> </button>
<Link to={roleChangeLink}> <Link to={roleChangeLink}>
@ -104,4 +126,4 @@ export const AuthBox = ({ title, altTitle, roleChangeLink }) => {
</form> </form>
</div> </div>
) )
} };

View File

@ -1,57 +1,29 @@
import React, { useEffect, useState } from 'react' import React, {useEffect, useState} from 'react'
import { useDispatch, useSelector } from 'react-redux' import {useSelector} from 'react-redux'
import { selectCurrentCandidate, auth } from '../../redux/outstaffingSlice' import {selectCurrentCandidate} from '../../redux/outstaffingSlice'
import { Link, useHistory, useParams } from 'react-router-dom' import {Link} from 'react-router-dom'
import calendarMale from '../../images/medium_male.png'
import rectangle from '../../images/rectangle_secondPage.png'
import CalendarComponent from './CalendarComponent'
import { currentMonth } from './calendarHelper'
import { Footer } from '../Footer/Footer'
import { fetchReportList } from '../../server/server' import CalendarComponent from './CalendarComponent'
import { getRole } from '../../redux/roleSlice' import {currentMonth} from './calendarHelper'
import {Footer} from '../Footer/Footer'
import rectangle from '../../images/rectangle_secondPage.png'
import './calendar.scss' import './calendar.scss'
const getDateParamString = ({ paramName, value }) => { const Calendar = ({onSelect}) => {
return value ? `${paramName}=${value}` : ''
}
const Calendar = ({ onSelect }) => { const candidateForCalendar = useSelector(selectCurrentCandidate);
const dispatch = useDispatch()
const candidateForCalendar = useSelector(selectCurrentCandidate)
const role = useSelector(getRole)
const { userId } = useParams()
const [month, setMonth] = useState('')
const [fromDate, setFromDate] = useState(null)
const [toDate, setToDate] = useState(null)
const history = useHistory() const [month, setMonth] = useState('');
// useEffect(() => {
// fetchReportList({
// link: `${
// process.env.REACT_APP_API_URL
// }/api/reports/index?user_id=${userId}${getDateParamString({
// paramName: 'fromDate',
// value: fromDate
// })}${getDateParamString({
// paramName: 'toDate',
// value: toDate
// })}`,
// history,
// role,
// logout: () => {}
// })
// }, [])
useEffect(() => { useEffect(() => {
setMonth(currentMonth) setMonth(currentMonth)
}, [month]) }, [month]);
const { name, skillsName, photo } = candidateForCalendar const {name, skillsName, photo} = candidateForCalendar;
const abbreviatedName = name && name.substring(0, name.lastIndexOf(' ')) const abbreviatedName = name && name.substring(0, name.lastIndexOf(' '));
return ( return (
<section className='calendar'> <section className='calendar'>
@ -61,12 +33,12 @@ const Calendar = ({ onSelect }) => {
</h2> </h2>
<div className='col-12 col-xl-12 d-flex justify-content-between align-items-center flex-column flex-sm-row'> <div className='col-12 col-xl-12 d-flex justify-content-between align-items-center flex-column flex-sm-row'>
<div className='calendar__info'> <div className='calendar__info'>
<img className='calendar__info-img' src={photo} alt='img' /> <img className='calendar__info-img' src={photo} alt='img'/>
<h3 className='calendar__info-name'>{abbreviatedName}</h3> <h3 className='calendar__info-name'>{abbreviatedName}</h3>
</div> </div>
<div className='calendar__title'> <div className='calendar__title'>
<h3 className='calendar__title-text'>{skillsName} разработчик</h3> <h3 className='calendar__title-text'>{skillsName} разработчик</h3>
<img className='calendar__title-img' src={rectangle} alt='img' /> <img className='calendar__title-img' src={rectangle} alt='img'/>
</div> </div>
<div> <div>
<Link to='/report'> <Link to='/report'>
@ -78,15 +50,15 @@ const Calendar = ({ onSelect }) => {
<div className='row'> <div className='row'>
<div className='col-12 col-xl-12'> <div className='col-12 col-xl-12'>
<CalendarComponent onSelect={onSelect} /> <CalendarComponent onSelect={onSelect}/>
<p className='calendar__hours'> <p className='calendar__hours'>
{month} : <span> 60 часов </span> {month} : <span> 60 часов </span>
</p> </p>
</div> </div>
</div> </div>
<Footer /> <Footer/>
</section> </section>
) )
} };
export default Calendar export default Calendar

View File

@ -1,9 +1,9 @@
.calendar { .calendar {
margin-bottom: 40px; margin-bottom: 40px;
font-family: 'LabGrotesque', sans-serif;
&__profile { &__profile {
color: #282828; color: #282828;
font-family: 'GT Eesti Pro Display';
font-size: 3.4em; font-size: 3.4em;
font-weight: 700; font-weight: 700;
font-style: normal; font-style: normal;
@ -29,7 +29,6 @@
height: 112px; height: 112px;
&-name { &-name {
font-family: 'GT Eesti Pro Display';
font-size: 2em; font-size: 2em;
font-weight: 400; font-weight: 400;
font-style: normal; font-style: normal;
@ -53,7 +52,6 @@
&__title { &__title {
&-text { &-text {
font-family: 'GT Eesti Pro Display';
font-size: 2.7em; font-size: 2.7em;
font-weight: 700; font-weight: 700;
font-style: normal; font-style: normal;
@ -66,7 +64,6 @@
&__btn { &__btn {
width: 280px; width: 280px;
height: 62px; height: 62px;
box-shadow: 6px 5px 20px rgba(82, 151, 34, 0.21);
border-radius: 31px; border-radius: 31px;
background-color: #ffffff; background-color: #ffffff;
background-image: linear-gradient(to top, #6aaf5c 0%, #52b709 100%), background-image: linear-gradient(to top, #6aaf5c 0%, #52b709 100%),
@ -83,10 +80,15 @@
font-size: 1.6em; font-size: 1.6em;
letter-spacing: normal; letter-spacing: normal;
text-align: center; text-align: center;
transition: all 0.3s ease;
&:hover {
box-shadow: 6px 5px 20px rgb(87 98 80 / 21%);
transform: scale(1.02);
}
} }
&__hours { &__hours {
font-family: 'GT Eesti Pro Display';
font-size: 2.2em; font-size: 2.2em;
font-weight: 600; font-weight: 600;
font-style: normal; font-style: normal;
@ -96,7 +98,6 @@
margin-left: 68px; margin-left: 68px;
span { span {
font-family: 'GT Eesti Pro Display';
font-weight: 100; font-weight: 100;
font-style: normal; font-style: normal;
letter-spacing: normal; letter-spacing: normal;

View File

@ -1,18 +1,19 @@
.calendar-component { .calendar-component {
position: relative; position: relative;
margin-top: 80px; margin-top: 30px;
margin-bottom: 60px; margin-bottom: 60px;
background-color: #f9f9f9; background-color: #f9f9f9;
padding-left: 68px; padding-left: 68px;
padding-right: 54px; padding-right: 54px;
padding-top: 48px; padding-top: 48px;
padding-bottom: 94px; padding-bottom: 94px;
font-family: 'LabGrotesque', sans-serif;
&__header { &__header {
display: flex; display: flex;
h3 { h3 {
font-family: 'GT Eesti Pro Display';
font-size: 2.5em; font-size: 2.5em;
font-weight: 400; font-weight: 400;
font-style: normal; font-style: normal;
@ -33,7 +34,6 @@
span { span {
color: #18586e; color: #18586e;
font-family: 'GT Eesti Pro Display';
font-size: 1.6em; font-size: 1.6em;
font-weight: 500; font-weight: 500;
font-style: normal; font-style: normal;
@ -62,7 +62,6 @@
p { p {
color: #398208; color: #398208;
font-family: 'GT Eesti Pro Display';
font-size: 25px; font-size: 25px;
font-weight: 400; font-weight: 400;
font-style: normal; font-style: normal;
@ -87,13 +86,17 @@
border: 1px solid #c4c4c4; border: 1px solid #c4c4c4;
background-color: #ffffff; background-color: #ffffff;
margin-top: 20px; margin-top: 20px;
font-family: 'GT Eesti Pro Display';
font-size: 1.2em; font-size: 1.2em;
font-weight: 100; font-weight: 100;
font-style: normal; font-style: normal;
letter-spacing: normal; letter-spacing: normal;
line-height: normal; line-height: normal;
text-align: center; text-align: center;
a {
text-decoration: none;
color: #000000;
}
} }
} }
} }
@ -250,7 +253,11 @@
} }
.before { .before {
background-color: #f0f7e0 !important; background-color: #e5f9b6 !important;
}
.pass {
background-color: #f7d7c9 !important;
} }
.today { .today {
@ -261,3 +268,7 @@
.selected { .selected {
background-color: #f9f9c3 !important; background-color: #f9f9c3 !important;
} }
.block {
pointer-events: none;
}

View File

@ -20,6 +20,16 @@ export function calendarHelper(value) {
return calendar; return calendar;
} }
export function getReports(value) {
const startDay = value.clone().startOf('month').startOf('week').startOf('day');
const reportsStart = `${new Date(startDay).getFullYear()}-${new Date(startDay).getMonth() + 1}-${new Date(startDay).getDate()}`
const endDay = value.clone().endOf('month').endOf('week');
const reportsEnd = `${new Date(endDay).getFullYear()}-${new Date(endDay).getMonth() + 1}-${new Date(endDay).getDate()}`
const getReports = `fromDate=${reportsStart}&toDate=${reportsEnd}`
return getReports;
}
export function currentMonth() { export function currentMonth() {
const currentMonth = moment().format('MMMM'); const currentMonth = moment().format('MMMM');

View File

@ -1,43 +1,41 @@
import React, {useEffect} from 'react' import React, {useEffect, useState} from 'react'
import {useHistory, useParams, Link} from 'react-router-dom' import {useParams, Link, useNavigate} from 'react-router-dom'
import {useSelector, useDispatch} from 'react-redux' import {useSelector, useDispatch} from 'react-redux'
import {
currentCandidate, import SkillSection from '../SkillSection/SkillSection'
selectCurrentCandidate, import Sidebar from '../CandidateSidebar/CandidateSidebar'
auth import {Footer} from '../Footer/Footer'
} from '../../redux/outstaffingSlice'
import {currentCandidate, selectCurrentCandidate,} from '../../redux/outstaffingSlice'
import {useRequest} from "../../hooks/useRequest";
import {createMarkup} from "../../helper";
import arrow from '../../images/right-arrow.png' import arrow from '../../images/right-arrow.png'
import rectangle from '../../images/rectangle_secondPage.png' import rectangle from '../../images/rectangle_secondPage.png'
import Sidebar from '../CandidateSidebar/CandidateSidebar'
import SkillSection from '../SkillSection/SkillSection'
import front from '../../images/front_end.png' import front from '../../images/front_end.png'
import back from '../../images/back_end.png' import back from '../../images/back_end.png'
import design from '../../images/design.png' import design from '../../images/design.png'
import {fetchGet} from '../../server/server'
import {Footer} from '../Footer/Footer'
import './candidate.scss' import './candidate.scss'
import {getRole} from '../../redux/roleSlice'
import {useState} from 'react'
const Candidate = () => { const Candidate = () => {
const history = useHistory();
const {id: candidateId} = useParams(); const {id: candidateId} = useParams();
const navigate = useNavigate();
const dispatch = useDispatch(); const dispatch = useDispatch();
const role = useSelector(getRole);
const [activeSnippet, setActiveSnippet] = useState(true); const [activeSnippet, setActiveSnippet] = useState(true);
const {apiRequest} = useRequest();
useEffect(() => { useEffect(() => {
window.scrollTo(0, 0) window.scrollTo(0, 0)
}, []); }, []);
useEffect(() => { useEffect(() => {
fetchGet({ apiRequest(`/profile/${candidateId}`, {
link: `${process.env.REACT_APP_API_URL}/api/profile/${candidateId}`,
params: Number(candidateId), params: Number(candidateId),
history,
role,
logout: () => dispatch(auth(false))
}).then((el) => dispatch(currentCandidate(el))) }).then((el) => dispatch(currentCandidate(el)))
}, [dispatch, candidateId]); }, [dispatch, candidateId]);
@ -79,10 +77,6 @@ const Candidate = () => {
return styles return styles
}; };
function createMarkup(text) {
return {__html: text.split('</p>').join('</p>')}
}
const {header, img, classes} = setStyles(); const {header, img, classes} = setStyles();
return ( return (
@ -100,7 +94,7 @@ const Candidate = () => {
<div className='row'> <div className='row'>
<div className='col-12 candidate__header'> <div className='col-12 candidate__header'>
<div className='candidate__arrow' onClick={() => history.push('/')}> <div className='candidate__arrow' onClick={() => navigate('/')}>
<div className='candidate__arrow-img'> <div className='candidate__arrow-img'>
<img src={arrow} alt=''/> <img src={arrow} alt=''/>
</div> </div>
@ -141,20 +135,13 @@ const Candidate = () => {
: 'Описание отсутствует...'} : 'Описание отсутствует...'}
</p> </p>
)} )}
{/* <Link to={`/candidate/${currentCandidateObj.id}/form`}>
<button type="submit" className='candidate__btn'>
Выбрать к собеседованию
</button>
</Link> */}
<SkillSection skillsArr={skillValues}/> <SkillSection skillsArr={skillValues}/>
</div> </div>
</div> </div>
) : ) :
( (
// <div className="col-12 col-xl-8">
// <CodeSnippetlighter />
// </div>
<div className="col-12 col-xl-8"> <div className="col-12 col-xl-8">
<div className="candidate__works works"> <div className="candidate__works works">
<div className="works__body"> <div className="works__body">

View File

@ -2,7 +2,7 @@ import React from 'react'
import { Link } from 'react-router-dom' import { Link } from 'react-router-dom'
import { Achievement } from '../Achievement/Achievement' import { Achievement } from '../Achievement/Achievement'
import { LEVELS, SKILLS } from '../constants/constants' import { LEVELS, SKILLS } from '../../constants/constants'
import './candidateSidebar.scss' import './candidateSidebar.scss'

View File

@ -1,43 +1,21 @@
import React, { useEffect, useState } from 'react' import React from 'react'
import {useSelector} from 'react-redux'
import {Link} from 'react-router-dom'
import {Loader} from '../Loader/Loader'
import {LEVELS, SKILLS} from '../../constants/constants'
import {selectProfiles, selectFilteredCandidates,} from '../../redux/outstaffingSlice'
import male from '../../images/medium_male.png' import male from '../../images/medium_male.png'
import rectangle from '../../images/rectangle_secondPage.png' import rectangle from '../../images/rectangle_secondPage.png'
import { Link, useHistory } from 'react-router-dom'
import { LEVELS, SKILLS } from '../constants/constants'
import {
selectProfiles,
selectFilteredCandidates,
selectItems,
auth
} from '../../redux/outstaffingSlice'
import { useSelector, useDispatch } from 'react-redux'
import { fetchGet } from '../../server/server'
import { Loader } from '../Loader/Loader'
import { getRole } from '../../redux/roleSlice'
import './description.scss' import './description.scss'
const Description = ({ onLoadMore, isLoadingMore }) => { const Description = ({ onLoadMore, isLoadingMore }) => {
const dispatch = useDispatch()
const [isLoaded, setIsLoaded] = useState(false)
const history = useHistory()
const role = useSelector(getRole)
const candidatesListArr = useSelector(selectProfiles)
const itemsArr = useSelector(selectItems)
const filteredListArr = useSelector(selectFilteredCandidates)
const [allCandidates, getAllCandidates] = useState([])
useEffect(() => { const candidatesListArr = useSelector(selectProfiles);
fetchGet({ const filteredListArr = useSelector(selectFilteredCandidates);
link: `${process.env.REACT_APP_API_URL}/api/profile?limit=`,
params: 1000,
history,
role,
logout: () => dispatch(auth(false))
}).then((p) => {
getAllCandidates(p)
setIsLoaded(true)
})
}, [])
if (!filteredListArr) { if (!filteredListArr) {
return ( return (
@ -95,7 +73,7 @@ const Description = ({ onLoadMore, isLoadingMore }) => {
)) ))
) : ( ) : (
<div className='description__empty'> <div className='description__empty'>
{isLoaded {isLoadingMore
? 'В данный момент в категории нет свободных специалистов' ? 'В данный момент в категории нет свободных специалистов'
: 'Загрузка...'} : 'Загрузка...'}
</div> </div>
@ -229,9 +207,8 @@ const Description = ({ onLoadMore, isLoadingMore }) => {
<div className='col-12'> <div className='col-12'>
<div className='description__footer'> <div className='description__footer'>
<div className='description__footer-btn'> <div className='description__footer-btn'>
{allCandidates && {
candidatesListArr && candidatesListArr &&
candidatesListArr.length !== allCandidates.length &&
filteredListArr.length === 0 ? ( filteredListArr.length === 0 ? (
<button onClick={() => onLoadMore(2)}>Загрузить еще</button> <button onClick={() => onLoadMore(2)}>Загрузить еще</button>
) : null} ) : null}
@ -242,6 +219,6 @@ const Description = ({ onLoadMore, isLoadingMore }) => {
</div> </div>
</section> </section>
) )
} };
export default Description export default Description

View File

@ -1,7 +1,5 @@
import React, { useState } from 'react' import React, { useState } from 'react'
import { fetchPost } from '../../server/server' import {useParams, useNavigate} from 'react-router-dom'
import { auth } from '../../redux/outstaffingSlice'
import { useHistory, useParams, Redirect } from 'react-router-dom'
import { Loader } from '../Loader/Loader' import { Loader } from '../Loader/Loader'
import PhoneInput from 'react-phone-input-2' import PhoneInput from 'react-phone-input-2'
import 'react-phone-input-2/lib/style.css' import 'react-phone-input-2/lib/style.css'
@ -9,59 +7,57 @@ import './form.scss'
import { withSwalInstance } from 'sweetalert2-react' import { withSwalInstance } from 'sweetalert2-react'
import swal from 'sweetalert2' import swal from 'sweetalert2'
import { useSelector, useDispatch } from 'react-redux' import {useRequest} from "../../hooks/useRequest";
import { getRole } from '../../redux/roleSlice'
const SweetAlert = withSwalInstance(swal)
const SweetAlert = withSwalInstance(swal);
const Form = () => { const Form = () => {
const dispatch = useDispatch()
const history = useHistory() const navigate = useNavigate();
const role = useSelector(getRole)
const urlParams = useParams() const urlParams = useParams();
const [status, setStatus] = useState(null) const [status, setStatus] = useState(null);
const [data, setData] = useState({ const [data, setData] = useState({
email: '', email: '',
phone: '', phone: '',
comment: '' comment: ''
}) });
const [isFetching, setIsFetching] = useState(false) const [isFetching, setIsFetching] = useState(false);
const {apiRequest} = useRequest();
const handleChange = (e) => { const handleChange = (e) => {
const { id, value } = e.target const { id, value } = e.target;
setData((prev) => ({ setData((prev) => ({
...prev, ...prev,
[id]: value [id]: value
})) }))
} };
const handleSubmit = (e) => { const handleSubmit = (e) => {
e.preventDefault() e.preventDefault();
setIsFetching(true) setIsFetching(true);
const formData = new FormData() const formData = new FormData();
formData.append('profile_id', urlParams.id) formData.append('profile_id', urlParams.id);
formData.append('email', data.email) formData.append('email', data.email);
formData.append('phone', data.phone) formData.append('phone', data.phone);
formData.append('comment', data.comment) formData.append('comment', data.comment);
fetchPost({ apiRequest('/interview-request/create-interview-request',{
link: `${process.env.REACT_APP_API_URL}/api/interview-request/create-interview-request`, method: 'POST',
params: { params: {
profile_id: urlParams.id, profile_id: urlParams.id,
...data ...data
},
history,
role,
logout: () => dispatch(auth(false))
}).then((res) =>
res.json().then((resJSON) => {
setStatus(resJSON)
setIsFetching(false)
})
)
} }
}).then((res) => {
setStatus(res);
setIsFetching(false)
}
)
};
return ( return (
<> <>
@ -79,8 +75,8 @@ const Form = () => {
setStatus(null) setStatus(null)
} }
: () => { : () => {
setStatus(null) setStatus(null);
history.push(`/candidate/${urlParams.id}`) navigate(`/candidate/${urlParams.id}`)
} }
} }
/> />
@ -135,6 +131,6 @@ const Form = () => {
</div> </div>
</> </>
) )
} };
export default Form export default Form

View File

@ -1,66 +1,64 @@
import React, { useState, useEffect } from 'react' import React, {useState, useEffect} from 'react'
import { useDispatch, useSelector } from 'react-redux' import {useDispatch} from 'react-redux'
import Outstaffing from '../Outstaffing/Outstaffing' import Outstaffing from '../Outstaffing/Outstaffing'
import Description from '../Description/Description' import Description from '../Description/Description'
import { fetchGet } from '../../server/server' import {Footer} from '../Footer/Footer'
import { profiles, tags, auth } from '../../redux/outstaffingSlice'
import { getRole } from '../../redux/roleSlice' import {profiles, tags} from '../../redux/outstaffingSlice'
import { Footer } from '../Footer/Footer'
import { useHistory } from 'react-router-dom' import {useRequest} from "../../hooks/useRequest";
const Home = () => { const Home = () => {
const history = useHistory()
const [isLoadingMore, setIsLoadingMore] = useState(false)
const [index, setIndex] = useState(4)
const dispatch = useDispatch() const [isLoadingMore, setIsLoadingMore] = useState(false);
const role = useSelector(getRole) const [index, setIndex] = useState(4);
const dispatch = useDispatch();
const {apiRequest} = useRequest();
useEffect(() => { useEffect(() => {
setIsLoadingMore(true) setIsLoadingMore(true);
fetchGet({ apiRequest('/profile', {
link: `${process.env.REACT_APP_API_URL}/api/profile?limit=`, params: {limit: 1000},
params: index,
history,
role,
logout: () => dispatch(auth(false))
}).then((profileArr) => { }).then((profileArr) => {
dispatch(profiles(profileArr))
setIsLoadingMore(false)
})
fetchGet({ dispatch(profiles(profileArr));
link: `${process.env.REACT_APP_API_URL}/api/skills/skills-on-main-page`, setIsLoadingMore(false)
history, });
role,
logout: () => dispatch(auth(false)) apiRequest('/skills/skills-on-main-page', {
}).then((skills) => { }).then((skills) => {
if (!skills) { if (!skills) {
return [] return []
} }
const keys = Object.keys(skills)
const values = Object.values(skills) const keys = Object.keys(skills);
const values = Object.values(skills);
const tempTags = values.map((value, index) => const tempTags = values.map((value, index) =>
value.map((val) => { value.map((val) => {
return { id: val.id, value: val.tags, name: keys[index] } return {id: val.id, value: val.tags, name: keys[index]}
}) })
) );
dispatch(tags(tempTags)) dispatch(tags(tempTags))
}) })
}, [dispatch, index])
}, [index]);
const loadMore = (count) => { const loadMore = (count) => {
setIndex((prev) => prev + count) setIndex((prev) => prev + count)
} };
return ( return (
<> <>
<Outstaffing /> <Outstaffing/>
<Description onLoadMore={loadMore} isLoadingMore={isLoadingMore} /> <Description onLoadMore={loadMore} isLoadingMore={isLoadingMore}/>
<Footer /> <Footer/>
</> </>
) )
} };
export default Home export default Home

View File

@ -1,5 +1,6 @@
import SVGLoader from 'react-loader-spinner' import SVGLoader from 'react-loader-spinner'
import './loader.scss' import './loader.scss'
import React from "react";
export const Loader = ({ width = 50, height = 50 }) => { export const Loader = ({ width = 50, height = 50 }) => {
return ( return (
@ -7,4 +8,4 @@ export const Loader = ({ width = 50, height = 50 }) => {
<SVGLoader type='Circles' color='#fff' height={height} width={width} /> <SVGLoader type='Circles' color='#fff' height={height} width={width} />
</div> </div>
) )
} };

View File

@ -4,7 +4,7 @@
display: flex; display: flex;
justify-content: center; justify-content: center;
align-items: center; align-items: center;
position: relative;
&:hover { &:hover {
path { path {
fill: #6aaf5c; fill: #6aaf5c;

View File

@ -1,5 +1,5 @@
import React, {useState} from 'react' import React, {useState} from 'react'
import {useHistory} from 'react-router-dom' import {useNavigate} from 'react-router-dom'
import {useDispatch, useSelector} from 'react-redux' import {useDispatch, useSelector} from 'react-redux'
import {Loader} from '../Loader/Loader' import {Loader} from '../Loader/Loader'
import {auth} from '../../redux/outstaffingSlice' import {auth} from '../../redux/outstaffingSlice'
@ -11,7 +11,7 @@ export const LogoutButton = () => {
const [isLoggingOut, setIsLoggingOut] = useState(false); const [isLoggingOut, setIsLoggingOut] = useState(false);
const dispatch = useDispatch(); const dispatch = useDispatch();
const userRole = useSelector(getRole); const userRole = useSelector(getRole);
const history = useHistory(); const navigate = useNavigate();
return ( return (
<div className='logout-button'> <div className='logout-button'>
@ -21,7 +21,7 @@ export const LogoutButton = () => {
localStorage.clear(); localStorage.clear();
dispatch(auth(false)); dispatch(auth(false));
setIsLoggingOut(false); setIsLoggingOut(false);
history.push(userRole === 'ROLE_DEV' ? '/authdev' : '/auth') navigate(userRole === 'ROLE_DEV' ? '/authdev' : '/auth')
}} }}
> >
{isLoggingOut ? <Loader/> : 'Выйти'}{' '} {isLoggingOut ? <Loader/> : 'Выйти'}{' '}

View File

@ -1,12 +1,11 @@
import React, { useState } from 'react' import React from 'react'
import { useSelector, useDispatch } from 'react-redux' import { useSelector, useDispatch } from 'react-redux'
import OutstaffingBlock from '../OutstaffingBlock/OutstaffingBlock' import OutstaffingBlock from '../OutstaffingBlock/OutstaffingBlock'
import TagSelect from '../Select/TagSelect' import TagSelect from '../Select/TagSelect'
import {
selectTags, import { selectTags, getPositionId, setPositionId} from '../../redux/outstaffingSlice'
getPositionId,
setPositionId
} from '../../redux/outstaffingSlice'
import front from '../../images/front_end.png' import front from '../../images/front_end.png'
import back from '../../images/back_end.png' import back from '../../images/back_end.png'
import design from '../../images/design.png' import design from '../../images/design.png'
@ -21,18 +20,18 @@ const createSelectPositionHandler =
} else { } else {
dispatch(setPositionId(id)) dispatch(setPositionId(id))
} }
} };
const Outstaffing = () => { const Outstaffing = () => {
const dispatch = useDispatch() const dispatch = useDispatch();
const positionId = useSelector(getPositionId) const positionId = useSelector(getPositionId);
const tagsArr = useSelector(selectTags) const tagsArr = useSelector(selectTags);
const onSelectPosition = createSelectPositionHandler({ const onSelectPosition = createSelectPositionHandler({
positionId, positionId,
setPositionId, setPositionId,
dispatch dispatch
}) });
return ( return (
<> <>
<section className='outstaffing'> <section className='outstaffing'>
@ -91,6 +90,6 @@ const Outstaffing = () => {
<TagSelect /> <TagSelect />
</> </>
) )
} };
export default Outstaffing export default Outstaffing

View File

@ -1,55 +1,48 @@
import React from 'react' import React from 'react'
import OutsideClickHandler from 'react-outside-click-handler' import OutsideClickHandler from 'react-outside-click-handler'
import { useDispatch, useSelector } from 'react-redux' import {useDispatch, useSelector} from 'react-redux'
import { import {
selectItems, selectItems,
selectedItems, selectedItems,
filteredCandidates, filteredCandidates,
auth
} from '../../redux/outstaffingSlice' } from '../../redux/outstaffingSlice'
import { fetchGet } from '../../server/server' import {useRequest} from "../../hooks/useRequest";
import { useHistory } from 'react-router-dom'
import { getRole } from '../../redux/roleSlice'
import './outstaffingBlock.scss' import './outstaffingBlock.scss'
const handlePositionClick = ({
const handlePositionClick = (
{
dispatch, dispatch,
positionId, positionId,
isSelected, isSelected,
onSelect, onSelect,
history, apiRequest
role }) => {
}) => {
if (isSelected) { if (isSelected) {
fetchGet({ apiRequest('/profile', {
link: `${process.env.REACT_APP_API_URL}/api/profile?limit=`, params: {limit: 1000},
params: 4,
history,
role,
logout: () => dispatch(auth(false))
}).then((profileArr) => { }).then((profileArr) => {
dispatch(filteredCandidates(profileArr)) dispatch(filteredCandidates(profileArr));
dispatch(selectedItems([])) dispatch(selectedItems([]));
onSelect(positionId) onSelect(positionId)
}) })
} else { } else {
fetchGet({ apiRequest('/profile', {
link: `${process.env.REACT_APP_API_URL}/api/profile?position_id=`, params: {
params: positionId, limit: '1000',
history, position_id: positionId},
role,
logout: () => dispatch(auth(false))
}).then((el) => { }).then((el) => {
dispatch(filteredCandidates(el)) dispatch(filteredCandidates(el));
dispatch(selectedItems([])) dispatch(selectedItems([]));
onSelect(positionId) onSelect(positionId)
}) })
} }
} };
const OutstaffingBlock = ({ const OutstaffingBlock = (
{
dataTags = [], dataTags = [],
selected, selected,
img, img,
@ -57,21 +50,22 @@ const OutstaffingBlock = ({
positionId, positionId,
isSelected, isSelected,
onSelect onSelect
}) => { }) => {
const history = useHistory()
const role = useSelector(getRole)
const dispatch = useDispatch()
const itemsArr = useSelector(selectItems) const dispatch = useDispatch();
const itemsArr = useSelector(selectItems);
const {apiRequest} = useRequest();
const handleBlockClick = (item, id) => { const handleBlockClick = (item, id) => {
if (!itemsArr.find((el) => item === el.value)) { if (!itemsArr.find((el) => item === el.value)) {
dispatch(selectedItems([...itemsArr, { id, value: item, label: item }])) dispatch(selectedItems([...itemsArr, {id, value: item, label: item}]))
}
} }
};
let classes let classes;
dataTags.forEach((el) => { dataTags.forEach((el) => {
if (el.name === 'skills_back') { if (el.name === 'skills_back') {
@ -81,7 +75,7 @@ const OutstaffingBlock = ({
} else if (el.name === 'skills_front') { } else if (el.name === 'skills_front') {
classes = 'front' classes = 'front'
} }
}) });
return ( return (
<OutsideClickHandler <OutsideClickHandler
@ -104,13 +98,12 @@ const OutstaffingBlock = ({
positionId, positionId,
isSelected, isSelected,
onSelect, onSelect,
history, apiRequest
role
}) })
} }
> >
<h3>{header}</h3> <h3>{header}</h3>
<img className={classes} src={img} alt='img' /> <img className={classes} src={img} alt='img'/>
</div> </div>
<div <div
className={`${ className={`${
@ -136,6 +129,6 @@ const OutstaffingBlock = ({
</div> </div>
</OutsideClickHandler> </OutsideClickHandler>
) )
} };
export default OutstaffingBlock export default OutstaffingBlock

View File

@ -3,6 +3,7 @@
color: #f9f9f9; color: #f9f9f9;
} }
.outstaffing-block { .outstaffing-block {
margin-top: 60px; margin-top: 60px;
display: flex; display: flex;
flex-direction: column; flex-direction: column;
@ -28,6 +29,7 @@
} }
&__img { &__img {
cursor: pointer;
min-width: 260px; min-width: 260px;
min-height: 120px; min-height: 120px;
background-color: #f9f9f9; background-color: #f9f9f9;

View File

@ -0,0 +1,81 @@
import React, { useEffect, useState } from 'react'
import {useDispatch, useSelector} from 'react-redux'
import { Link } from 'react-router-dom'
import moment from "moment";
import {currentMonth, getReports} from '../Calendar/calendarHelper'
import {ProfileCalendarComponent} from "./ProfileCalendarComponent";
import { Footer } from '../Footer/Footer'
import {ProfileHeader} from "../ProfileHeader/ProfileHeader";
import {useRequest} from "../../hooks/useRequest";
import { getProfileInfo } from '../../redux/outstaffingSlice'
import {setReportDate} from "../../redux/reportSlice";
import './profileCalendar.scss'
export const ProfileCalendar = () => {
const dispatch = useDispatch();
const profileInfo = useSelector(getProfileInfo);
const [month, setMonth] = useState('');
const [reports, setReports] = useState([]);
const [totalHours, setTotalHours] = useState(0);
const [requestDates, setRequestDates] = useState('');
const {apiRequest} = useRequest();
useEffect(() => {
setRequestDates(getReports(moment()))
});
useEffect(async () => {
if (!requestDates) {
return
}
apiRequest(`/reports/reports-by-date?${requestDates}&user_id=${localStorage.getItem('id')}`)
.then((reports) => {
let spendTime = 0;
reports.map((report) => {
if (report.spendTime) {
spendTime += Number(report.spendTime)
}
});
setTotalHours(spendTime);
setReports(reports)
})
}, [requestDates]);
useEffect(() => {
setMonth(currentMonth)
}, [month]);
return (
<div className='profile__calendar'>
<ProfileHeader/>
<div className='container'>
<h2 className='summary__title'>Ваши отчеты</h2>
<div className='summary__info'>
<div className='summary__person'>
<img src={profileInfo.photo} className='summary__avatar' alt='avatar'/>
<p className='summary__name'>{profileInfo.fio} {profileInfo.specification}</p>
</div>
<Link to='/report'>
<button className="calendar__btn" onClick={() => {
dispatch(setReportDate(''))
}}>Заполнить отчет за день</button>
</Link>
</div>
<div className='row'>
<div className='col-12 col-xl-12'>
<ProfileCalendarComponent reportsDates={reports} />
<p className='calendar__hours'>
{month} : <span> {totalHours} часов </span>
</p>
</div>
</div>
</div>
<Footer />
</div>
)
};

View File

@ -0,0 +1,119 @@
import React, { useState, useEffect } from 'react'
// import ellipse from '../../images/ellipse.png'
import rectangle from '../../images/rectangle__calendar.png'
import calendarIcon from '../../images/calendar_icon.png'
import moment from 'moment'
import 'moment/locale/ru'
import { calendarHelper, currentMonthAndDay} from '../Calendar/calendarHelper'
import { setReportDate } from '../../redux/reportSlice';
import {useDispatch} from "react-redux";
import {Link} from "react-router-dom";
import './../Calendar/calendarComponent.scss'
export const ProfileCalendarComponent = ({reportsDates}) => {
const dispatch = useDispatch();
const [value, setValue] = useState(moment())
const [calendar, setCalendar] = useState([])
useEffect(() => {
setCalendar(calendarHelper(value))
}, [value])
// function beforeToday(day) {
// return day.isBefore(new Date(), 'day')
// }
function isToday(day) {
return day.isSame(new Date(), 'day')
}
function correctDay(day) {
if (day < 10) {
return `0${day}`
} return day
}
function dayStyles(day) {
if (value < day) return `block`
for (const date of reportsDates) {
if (`${new Date(day).getFullYear()}-${correctDay(new Date(day).getMonth() + 1)}-${correctDay(new Date(day).getDate())}` === date.date) {
return `before`
}
}
if (day.day() === 6 || day.day() === 0) return `selected`
if (isToday(day)) return `today`
return 'pass'
}
function correctRoute(day) {
for (const date of reportsDates) {
if (`${new Date(day).getFullYear()}-${correctDay(new Date(day).getMonth() + 1)}-${correctDay(new Date(day).getDate())}` === date.date) {
return `../../view/report`
}
}
return '../../report'
}
// function prevMonth() {
// return value.clone().subtract(1, 'month')
// }
//
// function nextMonth() {
// return value.clone().add(1, 'month');
// }
return (
<div className='calendar-component'>
<div className='calendar-component__header'>
<h3>Мои отчеты</h3>
{/*<div className='calendar-component__header-box'>*/}
{/* <img src={ellipse} alt='' />*/}
{/* <span onClick={() => setValue(prevMonth())}>{prevMonth().format('MMMM')}</span>*/}
{/*</div>*/}
{/*<div className='calendar-component__header-box'>*/}
{/* <img src={ellipse} alt='' />*/}
{/* <span onClick={() => setValue(nextMonth())}>{nextMonth().format('MMMM')}</span>*/}
{/*</div>*/}
</div>
<div className='calendar-component__rectangle'>
<img src={rectangle} alt='' />
</div>
<div className='calendar-component__body'>
<div>
<p>Пн</p>
<p>Вт</p>
<p>Ср</p>
<p>Чт</p>
<p>Пт</p>
<p>Сб</p>
<p>Вс</p>
</div>
<div className='calendar-component__form'>
{calendar.map((week) =>
week.map((day) => (
<button
onClick={() => {
dispatch(setReportDate(day))
}}
key={day}
className={dayStyles(day)}
name={day.format('dddd')}
id='btn'
>
<Link to={correctRoute(day)}>
<img className={'calendar__icon'} src={calendarIcon} alt='' />
{currentMonthAndDay(day)}
</Link>
</button>
))
)}
</div>
</div>
</div>
)
}

View File

@ -0,0 +1,43 @@
.profile__calendar {
background: #F1F1F1;
height: 100%;
min-height: 100vh;
font-family: "LabGrotesque", sans-serif;
.container {
max-width: 1160px;
margin-top: 23px;
@media (max-width: 570px) {
margin-top: 0;
}
}
.summary__info {
padding-right: 25px;
}
.profile__calendar {
margin-top: 20px;
}
&__back {
text-decoration: none !important;
color: black !important;
font-size: 14px;
div {
display: flex;
column-gap: 20px;
}
}
&__profile {
margin-top: 42px;
}
&__btn {
transition: all 0.3s ease;
&:hover {
transform: scale(1.02);
}
}
}

View File

@ -0,0 +1,71 @@
import React, {useEffect, useState} from 'react';
import {useNavigate, NavLink} from "react-router-dom";
import {useDispatch, useSelector} from "react-redux";
import {Loader} from '../Loader/Loader'
import {auth, getProfileInfo, setProfileInfo} from "../../redux/outstaffingSlice";
import {getRole} from "../../redux/roleSlice";
import './profileHeader.scss'
import {useRequest} from "../../hooks/useRequest";
export const ProfileHeader = () => {
const navigate = useNavigate();
const dispatch = useDispatch();
const {apiRequest} = useRequest();
const userRole = useSelector(getRole);
const profileInfo = useSelector(getProfileInfo);
const [isLoggingOut, setIsLoggingOut] = useState(false);
useEffect(() => {
apiRequest(`/profile/${localStorage.getItem('cardId')}`)
.then((profileInfo) =>
dispatch(setProfileInfo(profileInfo))
);
}, [dispatch]);
const handler = () => {
setIsLoggingOut(true);
localStorage.clear();
dispatch(auth(false));
setIsLoggingOut(false);
navigate(userRole === 'ROLE_DEV' ? '/authdev' : '/auth')
};
return (
<header className='profileHeader'>
<div className='profileHeader__head'>
<div className='profileHeader__container'>
<h2 className='profileHeader__title'>itguild.<span>для разработчиков</span></h2>
<button onClick={handler} className='profileHeader__logout'>
{isLoggingOut ? <Loader/> : 'Выйти'}
</button>
</div>
</div>
<div className='profileHeader__info'>
<div className='profileHeader__container'>
<nav className='profileHeader__nav'>
<NavLink end to={'/profile/summary'}>Резюме</NavLink>
<NavLink end to={'/profile'}>Отчетность</NavLink>
<NavLink end to={'/'}>Трекер</NavLink>
<NavLink end to={'/'}>Выплаты</NavLink>
<NavLink end to={'/'}>Настройки</NavLink>
</nav>
<div className='profileHeader__personalInfo'>
<h3 className='profileHeader__personalInfoName'>{profileInfo.fio}</h3>
<img src={profileInfo.photo} className='profileHeader__personalInfoAvatar' alt='avatar'/>
</div>
</div>
</div>
</header>
)
};

View File

@ -0,0 +1,101 @@
.profileHeader {
width: 100%;
display: flex;
flex-direction: column;
font-family: 'LabGrotesque', sans-serif;
&__head {
background: #E1FCCF;
}
&__container {
max-width: 1160px;
padding: 0 10px;
margin: 0 auto;
display: flex;
justify-content: space-between;
align-items: center;
min-height: 50px;
}
&__title {
font-weight: 700;
font-size: 20px;
line-height: 32px;
margin-bottom: 0;
span {
color: #52B709;
}
}
&__logout {
background: none;
border: none;
font-weight: 500;
font-size: 16px;
line-height: 32px;
color: #000000;
}
&__info {
background: #FFFFFF;
}
&__nav {
display: flex;
column-gap: 30px;
.active {
color: #000000 !important;
}
a {
text-decoration: none !important;
cursor: pointer;
font-weight: 700;
font-size: 18px;
line-height: 32px;
color: #807777 !important;
transition: all 0.3s ease;
&:hover {
color: #261a1a !important;
}
}
@media (max-width: 800px) {
column-gap: 15px;
a {
font-size: 12px;
}
}
}
&__personalInfo {
display: flex;
column-gap: 20px;
align-items: center;
&Name {
margin-bottom: 0;
font-size: 12px;
line-height: 32px;
max-width: 150px;
overflow: hidden;
white-space: nowrap;
text-overflow: ellipsis;
@media (max-width: 600px) {
display: none;
}
}
&Avatar {
width: 37px;
height: 37px;
border-radius: 100px;
}
}
}

View File

@ -8,16 +8,16 @@ export const ProtectedRoute = ({ component: Component, ...rest }) => {
const existingToken = localStorage.getItem('auth_token') const existingToken = localStorage.getItem('auth_token')
const expiresAt = localStorage.getItem('access_token_expired_at') const expiresAt = localStorage.getItem('access_token_expired_at')
const isTokenAlive = (existingToken && expiresAt && new Date(expiresAt).getTime() > (new Date()).getTime()); const isTokenAlive = !isAuth && (existingToken && expiresAt && new Date(expiresAt).getTime() > (new Date()).getTime());
return ( return (
<Route <Route
{...rest} {...rest}
render={props => render={props =>
( isAuth || isTokenAlive) ? ( // ( isAuth || isTokenAlive) ? (
<Component {...props} /> <Component {...props} />
) : <Redirect to='/auth' /> // ) : <Redirect to='/auth' />
} }
/> />
); );
} };

View File

@ -1,58 +1,100 @@
import React, { useState } from 'react' import React, {useState} from 'react'
import { useSelector, useDispatch } from 'react-redux' import {useSelector} from 'react-redux'
import { fetchPost } from '../../server/server' import {Link} from 'react-router-dom'
import { useHistory, useParams, Redirect } from 'react-router-dom'
import { Loader } from '../Loader/Loader' import {Loader} from '../Loader/Loader'
import { auth } from '../../redux/outstaffingSlice' import {currentMonthAndDay} from '../Calendar/calendarHelper'
import { getRole } from '../../redux/roleSlice' import {Footer} from "../Footer/Footer";
import {ProfileHeader} from "../ProfileHeader/ProfileHeader";
import {useRequest} from "../../hooks/useRequest";
import {getReportDate} from '../../redux/reportSlice'
import calendarIcon from '../../images/calendar_icon.png' import calendarIcon from '../../images/calendar_icon.png'
import ellipse from '../../images/ellipse.png' import ellipse from '../../images/ellipse.png'
import remove from '../../images/remove.png' import remove from '../../images/remove.png'
import addIcon from '../../images/addIcon.png' import addIcon from '../../images/addIcon.png'
import { currentMonthAndDayReportPage } from '../Calendar/calendarHelper' import arrow from "../../images/right-arrow.png";
import './reportForm.scss' import './reportForm.scss'
const getCreatedDate = () => {
const date = new Date();
const dd = String(date.getDate()).padStart(2, '0')
const mm = String(date.getMonth() + 1).padStart(2, '0')
const yyyy = date.getFullYear()
const getCreatedDate = (day) => {
if (day) {
return `${new Date(day).getFullYear()}-${new Date(day).getMonth() + 1}-${new Date(day).getDate()}`
} else {
const date = new Date();
const dd = String(date.getDate()).padStart(2, '0');
const mm = String(date.getMonth() + 1).padStart(2, '0');
const yyyy = date.getFullYear();
return `${yyyy}-${mm}-${dd}` return `${yyyy}-${mm}-${dd}`
} }
};
const ReportForm = () => { const ReportForm = () => {
const dispatch = useDispatch() const reportDate = useSelector(getReportDate);
const history = useHistory()
const role = useSelector(getRole)
const [isFetching, setIsFetching] = useState(false) const {apiRequest} = useRequest();
const [reportSuccess, setReportSuccess] = useState(false) const [isFetching, setIsFetching] = useState(false);
const [reportSuccess, setReportSuccess] = useState(false);
const [inputs, setInputs] = useState([ { task: '', hours_spent: '', minutes_spent: 0 } ]); const [inputs, setInputs] = useState([{task: '', hours_spent: '', minutes_spent: 0}]);
const [troublesInputValue, setTroublesInputValue] = useState(''); const [troublesInputValue, setTroublesInputValue] = useState('');
const [scheduledInputValue, setScheduledInputValue] = useState(''); const [scheduledInputValue, setScheduledInputValue] = useState('');
const addInput = () => { const addInput = () => {
setInputs((prev) => [...prev, { task: '', hours_spent: '', minutes_spent: 0 }]) setInputs((prev) => [...prev, {task: '', hours_spent: '', minutes_spent: 0}])
} };
const totalHours = inputs.reduce((a,b) => a + b.hours_spent, 0) const totalHours = inputs.reduce((a, b) => a + b.hours_spent, 0);
const deleteInput = (indexRemove) => { const deleteInput = (indexRemove) => {
if (indexRemove !== 0) { if (indexRemove !== 0) {
setInputs((prev) => prev.filter((el, index) => index !== indexRemove)) setInputs((prev) => prev.filter((el, index) => index !== indexRemove))
} }
};
const handler = () => {
apiRequest('/reports/create', {
method: 'POST',
body: {
tasks: inputs,
difficulties: troublesInputValue,
tomorrow: scheduledInputValue,
created_at: getCreatedDate(reportDate),
status: 1,
},
}).then((res) => {
if (res.status === 200) {
setReportSuccess(true);
setTimeout(() => setReportSuccess(false), 2000)
} }
setInputs(() => []);
setTroublesInputValue('');
setScheduledInputValue('');
setIsFetching(false);
setInputs(() => [{task: '', hours_spent: '', minutes_spent: 0}]);
})
};
return ( return (
<section className='report-form'> <section className='report-form'>
<div className='row'> <ProfileHeader/>
<div className='col-xl-12'> <div className='container'>
<h2 className='summary__title'>Ваши отчеты - <span>добавить отчет</span></h2>
<div>
<div className='report__head'>
<Link className='calendar__back' to={`/profile/calendar`}>
<img src={arrow} alt=''/><p>Вернуться</p>
</Link>
</div>
</div>
<div className='report-form__content'>
<div className='report-form__block'> <div className='report-form__block'>
<div className='report-form__block-title'> <div className='report-form__block-title'>
<h2>Добавить отчет</h2> <h2>Добавление отчета за день</h2>
<h3>Дата заполнения отчета:</h3> <h3>Дата заполнения отчета:</h3>
</div> </div>
<div className='report-form__block-img'> <div className='report-form__block-img'>
@ -61,15 +103,14 @@ const ReportForm = () => {
src={calendarIcon} src={calendarIcon}
alt='' alt=''
/> />
{currentMonthAndDayReportPage()} {/*{currentMonthAndDayReportPage()}*/}
{reportDate ? currentMonthAndDay(reportDate) : getCreatedDate()}
</div> </div>
<div className='report-form__task-list'> <div className='report-form__task-list'>
<img src={ellipse} alt='' /> <img src={ellipse} alt=''/>
<span>Какие задачи были выполнены?</span> <span>Какие задачи были выполнены?</span>
</div> </div>
</div> </div>
</div>
</div>
<div className='row'> <div className='row'>
<div className='col-8'> <div className='col-8'>
@ -84,37 +125,38 @@ const ReportForm = () => {
return ( return (
<form id={'input'} key={`input__${index}`} className='report-form__task-form'> <form id={'input'} key={`input__${index}`} className='report-form__task-form'>
<div className='report-form__task-number'> <div className='report-form__task-number'>
{index+1}. {index + 1}.
</div> </div>
<div className='report-form__task-input report-form__task-input--description'> <div className='report-form__task-input report-form__task-input--description'>
<input name='text' type='text' onChange={ e => setInputs(inputs.map( (input, inputIndex) => { <input name='text' type='text' onChange={e => setInputs(inputs.map((input, inputIndex) => {
return index === inputIndex return index === inputIndex
? { ? {
...input, ...input,
task: e.target.value task: e.target.value
} }
: input : input
}))} /> }))}/>
</div> </div>
<div className='report-form__task-input report-form__task-input--hours'> <div className='report-form__task-input report-form__task-input--hours'>
<input name='number' type='number' min='1' onChange={ e => setInputs(inputs.map( (input, inputIndex) => { <input name='number' type='number' min='1'
onChange={e => setInputs(inputs.map((input, inputIndex) => {
return index === inputIndex return index === inputIndex
? { ? {
...input, ...input,
hours_spent: Number(e.target.value) hours_spent: Number(e.target.value)
} }
: input : input
}))} /> }))}/>
</div> </div>
<div className='report-form__task-remove'> <div className='report-form__task-remove'>
<img onClick={() => deleteInput(index)} src={remove} alt='' /> <img onClick={() => deleteInput(index)} src={remove} alt=''/>
</div> </div>
</form> </form>
) )
})} })}
<div className='report-form__form-add'> <div className='report-form__form-add'>
<img onClick={addInput} src={addIcon} alt='' /> <img onClick={addInput} src={addIcon} alt=''/>
<span>Добавить еще </span> <span>Добавить еще </span>
</div> </div>
</div> </div>
@ -125,49 +167,23 @@ const ReportForm = () => {
<div className='col-12'> <div className='col-12'>
<div className='report-form__input-box'> <div className='report-form__input-box'>
<div className='report-form__troubles'> <div className='report-form__troubles'>
<img src={ellipse} alt='' /> <img src={ellipse} alt=''/>
<span>Какие сложности возникли?</span> <span>Какие сложности возникли?</span>
</div> </div>
<input type='text' value={troublesInputValue} onChange={e => setTroublesInputValue(e.target.value)} /> <input type='text' value={troublesInputValue} onChange={e => setTroublesInputValue(e.target.value)}/>
<div className='report-form__scheduled'> <div className='report-form__scheduled'>
<img src={ellipse} alt='' /> <img src={ellipse} alt=''/>
<span>Что планируется сделать завтра?</span> <span>Что планируется сделать завтра?</span>
</div> </div>
<input type='text' value={scheduledInputValue} onChange={e => setScheduledInputValue(e.target.value)} /> <input type='text' value={scheduledInputValue} onChange={e => setScheduledInputValue(e.target.value)}/>
</div> </div>
</div> </div>
</div> </div>
<div className='row'> <div className='row'>
<div className='col-12'> <div className='col-12'>
<div className='report-form__footer'> <div className='report-form__footer'>
<button className='report-form__footer-btn' onClick={() => { <button className='report-form__footer-btn' onClick={() => handler()}>
fetchPost({ {isFetching ? <Loader/> : 'Отправить'}
link: `${process.env.REACT_APP_API_URL}/api/reports/create`,
history,
role,
body: {
tasks: inputs,
difficulties: troublesInputValue,
tomorrow: scheduledInputValue,
created_at: getCreatedDate(),
status: 1,
},
logout: () => dispatch(auth(false))
}).then((res) =>
res.json().then((resJSON) => {
if(res.status === 200) {
setReportSuccess(true)
setTimeout(()=> setReportSuccess(false),2000)
}
setInputs( () => [] )
setTroublesInputValue('');
setScheduledInputValue('');
setIsFetching(false)
setInputs(() => [ { task: '', hours_spent: '', minutes_spent: 0 } ]);
})
)
}}>
{isFetching ? <Loader /> : 'Отправить'}
</button> </button>
<p className='report-form__footer-text'> <p className='report-form__footer-text'>
Всего за день : <span>{totalHours} часов</span> Всего за день : <span>{totalHours} часов</span>
@ -178,8 +194,11 @@ const ReportForm = () => {
</div> </div>
</div> </div>
</div> </div>
</div>
</div>
<Footer/>
</section> </section>
) )
} };
export default ReportForm export default ReportForm

View File

@ -1,9 +1,51 @@
.report-form { .report-form {
background: #F1F1F1;
height: 100%;
min-height: 100vh;
font-family: "LabGrotesque", sans-serif;
.container {
max-width: 1160px;
margin-top: 23px;
@media (max-width: 570px) {
margin-top: 0;
}
}
&__content {
background: #FFFFFF;
border-radius: 12px;
margin: 25px 0 80px;
padding: 50px 40px;
}
.report__head {
margin-top: 20px;
a {
display: flex;
align-items: center;
grid-column-gap: 30px;
column-gap: 30px;
margin-top: 20px;
cursor: pointer;
text-decoration: none;
p {
margin-bottom: 0;
font-size: 14px;
line-height: 32px;
font-weight: 500;
color: black;
}
}
}
&__block-title { &__block-title {
margin-top: 76px;
h2 { h2 {
color: #282828; color: #52B709;
font-family: 'GT Eesti Pro Display'; font-family: 'GT Eesti Pro Display';
font-size: 3.3em; font-size: 3.3em;
font-weight: 700; font-weight: 700;

View File

@ -1,49 +1,46 @@
import React, { useState } from 'react' import React, {useState} from 'react'
import { useSelector, useDispatch } from 'react-redux' import {useSelector, useDispatch} from 'react-redux'
import Select from 'react-select' import Select from 'react-select'
import { Loader } from '../Loader/Loader' import {Loader} from '../Loader/Loader'
import style from './TagSelect.module.css' import {useRequest} from "../../hooks/useRequest";
import { import {
selectedItems, selectedItems,
selectItems, selectItems,
selectTags, selectTags,
filteredCandidates, filteredCandidates,
setPositionId, setPositionId
auth
} from '../../redux/outstaffingSlice' } from '../../redux/outstaffingSlice'
import { fetchGet } from '../../server/server'
import { useHistory } from 'react-router-dom' import style from './TagSelect.module.css'
import { getRole } from '../../redux/roleSlice'
const TagSelect = () => { const TagSelect = () => {
const history = useHistory
const role = useSelector(getRole)
const [searchLoading, setSearchLoading] = useState(false)
const dispatch = useDispatch()
const itemsArr = useSelector(selectItems) const [searchLoading, setSearchLoading] = useState(false);
const dispatch = useDispatch();
const tagsArr = useSelector(selectTags) const {apiRequest} = useRequest();
const handleSubmit = ({ dispatch, setSearchLoading }) => { const itemsArr = useSelector(selectItems);
setSearchLoading(true) const tagsArr = useSelector(selectTags);
dispatch(setPositionId(null)) const handleSubmit = ({dispatch, setSearchLoading}) => {
const filterItemsId = itemsArr.map((item) => item.id).join() setSearchLoading(true);
fetchGet({ dispatch(setPositionId(null));
link: `${process.env.REACT_APP_API_URL}/api/profile?skills=`, const filterItemsId = itemsArr.map((item) => item.id).join();
params: filterItemsId, const params = filterItemsId ? {skill: filterItemsId} : '';
history,
role,
logout: () => dispatch(auth(false)) apiRequest('/profile', {
params: {...params, limit: 1000},
}).then((el) => { }).then((el) => {
dispatch(filteredCandidates(el)) dispatch(filteredCandidates(el));
setSearchLoading(false) setSearchLoading(false)
}) })
// dispatch(selectedItems([])); // dispatch(selectedItems([]));
} };
return ( return (
<> <>
@ -56,7 +53,7 @@ const TagSelect = () => {
<div className={style.search__box}> <div className={style.search__box}>
<Select <Select
value={itemsArr} value={itemsArr}
onChange={(value) => dispatch(selectedItems(value))} onChange={(value) => {console.log(value) ;return dispatch(selectedItems(value))}}
isMulti isMulti
name='tags' name='tags'
className={style.select} className={style.select}
@ -73,11 +70,11 @@ const TagSelect = () => {
} }
/> />
<button <button
onClick={() => handleSubmit({ dispatch, setSearchLoading })} onClick={() => handleSubmit({dispatch, setSearchLoading})}
type='submit' type='submit'
className={style.search__submit} className={style.search__submit}
> >
{searchLoading ? <Loader width={30} height={30} /> : 'Поиск'} {searchLoading ? <Loader width={30} height={30}/> : 'Поиск'}
</button> </button>
</div> </div>
</div> </div>
@ -85,6 +82,6 @@ const TagSelect = () => {
</section> </section>
</> </>
) )
} };
export default TagSelect export default TagSelect

View File

@ -1,56 +1,52 @@
import React, { useEffect, useState } from 'react'; import React, {useEffect, useState} from 'react';
import { ContentTitle } from "../ContentTitle/ContentTitle" import {Link} from "react-router-dom"
import { ContentButton } from "../ContentButton/ContentButton" import {ContentTitle} from "../ContentTitle/ContentTitle"
import { BookkeepingFormField } from "../BookkeepingFormField/BookkeepingFormField" import {ContentButton} from "../ContentButton/ContentButton"
import { BookkepingSelect } from '../BookkepingSelect/BookkepingSelect'; import {BookkeepingFormField} from "../BookkeepingFormField/BookkeepingFormField"
import { BookkepingInput } from '../BookkepingInput/BookkepingInput'; import {BookkepingSelect} from '../BookkepingSelect/BookkepingSelect';
import { fetchGet } from '../../../../server/server' import {BookkepingInput} from '../BookkepingInput/BookkepingInput';
import { Link } from "react-router-dom"
import {useRequest} from "../../../../hooks/useRequest";
import "./actContent.css" import "./actContent.css"
export const ActContent = ()=> { export const ActContent = () => {
const [templates, setTemplates] = useState([]) const [templates, setTemplates] = useState([]);
const [selectedTemplate, setSelectedTemplate] = useState() const [selectedTemplate, setSelectedTemplate] = useState();
const [templatedFields, setTemplatedFields] = useState([]) const [templatedFields, setTemplatedFields] = useState([]);
const {apiRequest} = useRequest();
useEffect(() => { useEffect(() => {
fetchGet({ apiRequest('/template/get-template-list')
link: `${process.env.REACT_APP_API_URL}/api/template/get-template-list`, .then(res => setTemplates(res))
}).then((res) => { }, []);
setTemplates(res)
})
}, [])
useEffect(() => { useEffect(() => {
if (selectedTemplate === undefined) { if (selectedTemplate === undefined) {
return return
} }
fetchGet({ apiRequest(`/template/get-template-fields?template_id=${selectedTemplate}`)
link: `${process.env.REACT_APP_API_URL}/api/template/get-template-fields?template_id=${selectedTemplate}`, .then(res => setTemplatedFields(res[0].templateDocumentFields))
}) }, [selectedTemplate]);
.then((res) => {
setTemplatedFields(res[0].templateDocumentFields)
})
}, [selectedTemplate])
return (
return(
<div> <div>
<div className="content__info"> <div className="content__info">
<ContentTitle title="Создание акта" description="# Описание" /> <ContentTitle title="Создание акта" description="# Описание"/>
<div className="content__info-main"> <div className="content__info-main">
<form className='contract'> <form className='contract'>
<div className="contract__create"> <div className="contract__create">
<div className="contract__title">Создание акта </div> <div className="contract__title">Создание акта </div>
<input type="text" className="contract__number" placeholder="#" /> <input type="text" className="contract__number" placeholder="#"/>
<span>от</span> <span>от</span>
<input type="date" className="contract__date" /> <input type="date" className="contract__date"/>
</div> </div>
<BookkeepingFormField title="Шаблон документа" <BookkeepingFormField
title="Шаблон документа"
Component={BookkepingSelect} Component={BookkepingSelect}
innerComponentProps={{ innerComponentProps={{
onSelect: setSelectedTemplate, onSelect: setSelectedTemplate,
@ -60,12 +56,14 @@ export const ActContent = ()=> {
}} }}
action={{ action={{
text: "Добавить свой шаблон", text: "Добавить свой шаблон",
method: () => {} method: () => {
}
}} }}
/> />
{templatedFields.map((field, index ) => {templatedFields.map((field, index) =>
<BookkeepingFormField title={field.field.title} key={index} <BookkeepingFormField
title={field.field.title} key={index}
Component={BookkepingInput} Component={BookkepingInput}
innerComponentProps={{ innerComponentProps={{
placeholder: "Введите данные", placeholder: "Введите данные",
@ -74,9 +72,9 @@ export const ActContent = ()=> {
)} )}
<div className="content__btn-list"> <div className="content__btn-list">
<ContentButton styles={{ width: "290px", <ContentButton styles={{
width: "290px",
height: "75px", height: "75px",
boxShadow: "6px 5px 20px rgba(182, 75, 62, 0.21)", boxShadow: "6px 5px 20px rgba(182, 75, 62, 0.21)",
borderRadius: "38px", borderRadius: "38px",
@ -86,7 +84,8 @@ export const ActContent = ()=> {
}}>Сохранить</ContentButton> }}>Сохранить</ContentButton>
<Link to="/documents" className="link-act-button"> <Link to="/documents" className="link-act-button">
<div className='act-Button'> <div className='act-Button'>
<ContentButton styles={{color: "#282828", <ContentButton styles={{
color: "#282828",
marginLeft: "40px", marginLeft: "40px",
background: "none", background: "none",
border: "none" border: "none"
@ -99,4 +98,4 @@ export const ActContent = ()=> {
</div> </div>
</div> </div>
) )
} };

View File

@ -1,53 +1,50 @@
import React, { useEffect, useState } from "react"; import React, {useEffect, useState} from "react";
import { ContentTitle } from "../ContentTitle/ContentTitle" import {ContentTitle} from "../ContentTitle/ContentTitle"
import { ContentButton } from "../ContentButton/ContentButton" import {ContentButton} from "../ContentButton/ContentButton"
import { BookkeepingFormField } from "../BookkeepingFormField/BookkeepingFormField" import {BookkeepingFormField} from "../BookkeepingFormField/BookkeepingFormField"
import { BookkepingSelect } from '../BookkepingSelect/BookkepingSelect'; import {BookkepingSelect} from '../BookkepingSelect/BookkepingSelect';
import { BookkepingInput } from '../BookkepingInput/BookkepingInput'; import {BookkepingInput} from '../BookkepingInput/BookkepingInput';
import { fetchGet } from '../../../../server/server' import {Link} from "react-router-dom"
import { Link } from "react-router-dom" import {useRequest} from "../../../../hooks/useRequest";
export const ContractContent = () => { export const ContractContent = () => {
const [templates, setTemplates] = useState([]) const [templates, setTemplates] = useState([]);
const [selectedTemplate, setSelectedTemplate] = useState() const [selectedTemplate, setSelectedTemplate] = useState();
const [templatedFields, setTemplatedFields] = useState([]) const [templatedFields, setTemplatedFields] = useState([]);
const {apiRequest} = useRequest();
useEffect(() => { useEffect(() => {
fetchGet({ apiRequest(`/document/get-document-list`)
link: `${process.env.REACT_APP_API_URL}/api/document/get-document-list`, .then(res => setTemplates(res))
}).then((res) => { }, []);
setTemplates(res)
})
}, [])
useEffect(() => { useEffect(() => {
if (selectedTemplate === undefined) { if (selectedTemplate === undefined) {
return return
} }
fetchGet({ apiRequest(`/document/get-document?document_id=${selectedTemplate}`)
link: `${process.env.REACT_APP_API_URL}/api/document/get-document?document_id=${selectedTemplate}`, .then(res => setTemplatedFields(res[0].templateDocumentFields)
}) )
.then((res) => { }, [selectedTemplate]);
setTemplatedFields(res[0].templateDocumentFields)
})
}, [selectedTemplate])
return ( return (
<div> <div>
<div className="content__info"> <div className="content__info">
<ContentTitle title="Создание договора" description="# Описание" /> <ContentTitle title="Создание договора" description="# Описание"/>
<div className="content__info-main"> <div className="content__info-main">
<form className='contract'> <form className='contract'>
<div className="contract__create"> <div className="contract__create">
<div className="contract__title">Создание договора </div> <div className="contract__title">Создание договора </div>
<input type="text" className="contract__number" placeholder="#" /> <input type="text" className="contract__number" placeholder="#"/>
<span>от</span> <span>от</span>
<input type="date" className="contract__date" /> <input type="date" className="contract__date"/>
</div> </div>
<BookkeepingFormField title="Шаблон документа" <BookkeepingFormField
title="Шаблон документа"
Component={BookkepingSelect} Component={BookkepingSelect}
innerComponentProps={{ innerComponentProps={{
onSelect: setSelectedTemplate, onSelect: setSelectedTemplate,
@ -57,12 +54,14 @@ export const ContractContent = () => {
}} }}
action={{ action={{
text: "Добавить свой шаблон", text: "Добавить свой шаблон",
method: () => {} method: () => {
}
}} }}
/> />
{templatedFields.map((field, index ) => {templatedFields.map((field, index) =>
<BookkeepingFormField title={field.field.title} key={index} <BookkeepingFormField
title={field.field.title} key={index}
Component={BookkepingInput} Component={BookkepingInput}
innerComponentProps={{ innerComponentProps={{
placeholder: "Введите данные", placeholder: "Введите данные",
@ -71,7 +70,8 @@ export const ContractContent = () => {
)} )}
<div className="content__btn-list"> <div className="content__btn-list">
<ContentButton styles={{ width: "290px", <ContentButton styles={{
width: "290px",
height: "75px", height: "75px",
boxShadow: "6px 5px 20px rgba(182, 75, 62, 0.21)", boxShadow: "6px 5px 20px rgba(182, 75, 62, 0.21)",
borderRadius: "38px", borderRadius: "38px",
@ -81,7 +81,8 @@ export const ContractContent = () => {
}}>Сохранить</ContentButton> }}>Сохранить</ContentButton>
<Link to="/documents" className="link-act-button"> <Link to="/documents" className="link-act-button">
<div className='act-Button'> <div className='act-Button'>
<ContentButton styles={{color: "#282828", <ContentButton styles={{
color: "#282828",
marginLeft: "40px", marginLeft: "40px",
background: "none", background: "none",
border: "none" border: "none"
@ -94,4 +95,4 @@ export const ContractContent = () => {
</div> </div>
</div> </div>
) )
} };

View File

@ -6,7 +6,7 @@ import {selectedTest, selectUserInfo} from "../../../redux/quizSlice";
export const HeaderPageTestsQuiz = ({isVisibilityButton}) => { export const HeaderPageTestsQuiz = ({isVisibilityButton}) => {
const test = useSelector(selectedTest) const test = useSelector(selectedTest);
const userInfo = useSelector(selectUserInfo); const userInfo = useSelector(selectUserInfo);
return ( return (
@ -23,8 +23,8 @@ export const HeaderPageTestsQuiz = ({isVisibilityButton}) => {
</div> </div>
</div> </div>
{isVisibilityButton && {isVisibilityButton &&
<Link to={'/quiz-instruction'} className='quiz-btn quiz-btn_restriction'>Пройти</Link>} <Link to={'/quiz/instruction'} className='quiz-btn quiz-btn_restriction'>Пройти</Link>}
</div> </div>
</div> </div>
) )
} };

View File

@ -1,33 +1,29 @@
import {useEffect} from 'react' import React, {useEffect} from 'react'
import {useDispatch} from 'react-redux' import {useDispatch, useSelector} from 'react-redux'
import {useSelector} from 'react-redux' import {selectUserInfo, setQuestionnairesList, setUserInfo} from "../../../redux/quizSlice";
import {fetchGet} from '../../../server/server' import {useRequest} from "../../../hooks/useRequest";
import './quiz.scss' import './quiz.scss'
import {selectUserInfo, setQuestionnairesList, setUserInfo,} from "../../../redux/quizSlice";
export const HeaderQuiz = ({header}) => { export const HeaderQuiz = ({header}) => {
const dispatch = useDispatch() const dispatch = useDispatch();
const userId = localStorage.getItem('id'); const userId = localStorage.getItem('id');
const userInfo = useSelector(selectUserInfo); const userInfo = useSelector(selectUserInfo);
useEffect(() => { const {apiRequest} = useRequest();
dispatch(setUserInfo(userId))
}, [dispatch])
useEffect(() => { useEffect(() => {
fetchGet({ dispatch(setUserInfo(userId))
link: `${process.env.REACT_APP_API_URL}/api/user-questionnaire/questionnaires-list?user_id=${userId}`, }, [dispatch]);
Origin: `${process.env.REACT_APP_BASE_URL}`,
} useEffect(() => {
).then(response => { apiRequest(`/user-questionnaire/questionnaires-list?user_id=${userId}`)
dispatch(setQuestionnairesList(response)) .then(res => dispatch(setQuestionnairesList(res)))
}) }, [dispatch]);
}, [dispatch])
return ( return (
<div> <div>
{ userInfo?.status === 500 ? <div className="error-msg">{userInfo.message}</div> : {userInfo?.status === 500 ? <div className="error-msg">{userInfo.message}</div> :
<div className="header-quiz"> <div className="header-quiz">
<div className="header-quiz__container"> <div className="header-quiz__container">
{!userInfo ? <h2>Loading...</h2> : {!userInfo ? <h2>Loading...</h2> :
@ -47,4 +43,4 @@ export const HeaderQuiz = ({header}) => {
} }
</div> </div>
) )
} };

View File

@ -1,27 +1,28 @@
import {Link} from 'react-router-dom' import {Link} from 'react-router-dom'
import {CodeSnippetlighter} from '../../../pages/CodeSnippetPage'
import comment from './../../../images/comment.jpg' import comment from './../../../images/comment.jpg'
import './quiz.scss' import './quiz.scss'
import {useEffect, useState} from "react"; import {useEffect, useState} from "react";
import {useSelector} from "react-redux"; import {useSelector} from "react-redux";
import {selectedTest} from "../../../redux/quizSlice"; import {selectedTest} from "../../../redux/quizSlice";
import {fetchGet} from "../../../server/server";
import {useRequest} from "../../../hooks/useRequest";
export const Instruction = () => { export const Instruction = () => {
const [countQuestions, setCountQuestions] = useState(null) const [countQuestions, setCountQuestions] = useState(null);
const test = useSelector(selectedTest) const test = useSelector(selectedTest);
useEffect(async () => { const {apiRequest} = useRequest();
const response = await fetchGet({ useEffect( () => {
link: `${process.env.REACT_APP_API_URL}/api/user-questionnaire/get-question-number?user_questionnaire_uuid=${test.uuid}`,
Origin: `${process.env.REACT_APP_BASE_URL}`, apiRequest('/user-questionnaire/get-question-number', {
params: {user_questionnaire_uuid: test.uuid},
} }
) ).then((res)=> setCountQuestions(res.question_number))
setCountQuestions(response.question_number)
}, [])
}, []);
return ( return (
<div className="instruction"> <div className="instruction">
@ -36,7 +37,7 @@ export const Instruction = () => {
e e
lit, sed do eiusmod tempo lit, sed do eiusmod tempo
</div> </div>
<Link to="/quiz-test" className='instruction__btn quiz-btn quiz-btn_restriction'>Далее</Link> <Link to="/quiz/test" className='instruction__btn quiz-btn quiz-btn_restriction'>Далее</Link>
<div className="instruction__info"> <div className="instruction__info">
<div className="instruction__icon"> <div className="instruction__icon">
<img src={comment} alt=""/> <img src={comment} alt=""/>
@ -49,5 +50,5 @@ export const Instruction = () => {
</div> </div>
</div> </div>
) )
} };

View File

@ -7,7 +7,7 @@ import {setSelectedTest} from "../../../redux/quizSlice";
export const MyTestsQuiz = ({listTests}) => { export const MyTestsQuiz = ({listTests}) => {
const formationEndingOfScore = (score) => { const formationEndingOfScore = (score) => {
const lastNumber = score % 10 const lastNumber = score % 10;
if(score === 11 ||score === 12 ||score === 13 ||score === 14 ){ if(score === 11 ||score === 12 ||score === 13 ||score === 14 ){
return 'баллов' return 'баллов'
}else if(lastNumber === 2 || lastNumber === 3 || lastNumber === 4 ){ }else if(lastNumber === 2 || lastNumber === 3 || lastNumber === 4 ){
@ -17,10 +17,10 @@ export const MyTestsQuiz = ({listTests}) => {
}else{ }else{
return 'баллов' return 'баллов'
} }
} };
const dispatch = useDispatch() const dispatch = useDispatch();
const recordSelectedTest = (item) => dispatch(setSelectedTest(item)) const recordSelectedTest = (item) => dispatch(setSelectedTest(item));
return ( return (
<div className="my-tests"> <div className="my-tests">
@ -35,10 +35,10 @@ export const MyTestsQuiz = ({listTests}) => {
{item.questionnaire_title} {item.questionnaire_title}
</h3> </h3>
<div className="item-test__body test-data"> <div className="item-test__body test-data">
<Link to={'/quiz-interjacent'} className='quiz-btn' <Link to={'/quiz/interjacent'} className='quiz-btn'
onClick={() => recordSelectedTest(item)}>Пройти</Link> onClick={() => recordSelectedTest(item)}>Пройти</Link>
</div> </div>
</article> </article>;
case 2: case 2:
return <article className="my-tests__item item-test" key={item.questionnaire_title}> return <article className="my-tests__item item-test" key={item.questionnaire_title}>
<h3 className="item-test__name-test"> <h3 className="item-test__name-test">
@ -52,7 +52,7 @@ export const MyTestsQuiz = ({listTests}) => {
<div className="test-data__hr"></div> <div className="test-data__hr"></div>
<div className="test-data__score quiz-text">{item.score} {formationEndingOfScore(item.score)}</div> <div className="test-data__score quiz-text">{item.score} {formationEndingOfScore(item.score)}</div>
</div> </div>
</article> </article>;
case 3: case 3:
return <article className="my-tests__item item-test" key={item.questionnaire_title}> return <article className="my-tests__item item-test" key={item.questionnaire_title}>
<h3 className="item-test__name-test"> <h3 className="item-test__name-test">
@ -61,7 +61,7 @@ export const MyTestsQuiz = ({listTests}) => {
<div className="item-test__body test-data"> <div className="item-test__body test-data">
<div className='quiz-btn'>На проверке</div> <div className='quiz-btn'>На проверке</div>
</div> </div>
</article> </article>;
default: default:
break break
} }
@ -70,4 +70,4 @@ export const MyTestsQuiz = ({listTests}) => {
</div> </div>
</div> </div>
) )
} };

View File

@ -1,5 +1,4 @@
import { Link } from 'react-router-dom' import React from 'react'
import avatar from './../../../images/medium_male.png'
import './quiz.scss' import './quiz.scss'
export const Progressbar = ({indexQuestion, width}) => { export const Progressbar = ({indexQuestion, width}) => {
@ -15,4 +14,4 @@ export const Progressbar = ({indexQuestion, width}) => {
</div> </div>
</div> </div>
) )
} };

View File

@ -1,24 +1,22 @@
import React, {useEffect, useState} from 'react'; import React, {useEffect, useState} from 'react';
import {useDispatch, useSelector} from "react-redux"; import {useDispatch, useSelector} from "react-redux";
import {fetchResultTest, selectedTest, selectResult} from "../../../redux/quizSlice"; import {fetchResultTest, selectedTest, selectResult} from "../../../redux/quizSlice";
import {fetchGet} from "../../../server/server"; import {useRequest} from "../../../hooks/useRequest";
export const Results = () => { export const Results = () => {
const result = useSelector(selectResult) const result = useSelector(selectResult);
const test = useSelector(selectedTest) const test = useSelector(selectedTest);
const [maxScore, setMaxScore] = useState('') const [maxScore, setMaxScore] = useState('');
const dispatch = useDispatch() const dispatch = useDispatch();
const {apiRequest} = useRequest();
useEffect(async () => { useEffect(() => {
dispatch(fetchResultTest(test.uuid)) dispatch(fetchResultTest(test.uuid));
const response = await fetchGet({ apiRequest(`/user-questionnaire/get-points-number?user_questionnaire_uuid=${test.uuid}`)
link: `${process.env.REACT_APP_API_URL}/api/user-questionnaire/get-points-number?user_questionnaire_uuid=${test.uuid}`, .then((res) => setMaxScore(res.sum_point));
Origin: `${process.env.REACT_APP_BASE_URL}`,
} }, [apiRequest, dispatch, test]);
)
setMaxScore(response.sum_point)
}, [])
return ( return (
<div className={'result _container'}> <div className={'result _container'}>

View File

@ -1,59 +1,58 @@
import React, {useEffect} from 'react' import React, {useEffect, useState} from 'react'
import {useHistory} from "react-router" import {useNavigate} from "react-router-dom"
import {CodeSnippetlighter} from '../../../pages/CodeSnippetPage' import {useSelector, useDispatch} from 'react-redux'
import './quiz.scss'
import {useDispatch} from 'react-redux'
import {useState} from 'react'
import {
fetchGetAnswers,
selectAnswer,
selectedTest
} from '../../../redux/quizSlice'
import {useSelector} from 'react-redux'
import {Progressbar} from './ProgressbarQuiz'
import {fetchUserAnswersMany, fetchUserAnswerOne} from './../../../redux/quizSlice'
import {GetOptionTask} from './GetOptionTask'
import {fetchGet} from "../../../server/server";
import {useRequest} from "../../../hooks/useRequest";
import {Progressbar} from './ProgressbarQuiz'
import {GetOptionTask} from './GetOptionTask'
import {
fetchUserAnswersMany, fetchUserAnswerOne, fetchGetAnswers, selectAnswer, selectedTest
} from './../../../redux/quizSlice'
import './quiz.scss'
export const TaskQuiz = () => { export const TaskQuiz = () => {
const history = useHistory(); const navigate = useNavigate();
const dispatch = useDispatch() const dispatch = useDispatch();
const listAnswers = useSelector(selectAnswer)
const dataTest = useSelector(selectedTest) const listAnswers = useSelector(selectAnswer);
const dataTest = useSelector(selectedTest);
const [index, setIndex] = useState(0); const [index, setIndex] = useState(0);
const [checkedValues, setCheckedValues] = useState([]) const [checkedValues, setCheckedValues] = useState([]);
const [stripValue, setStripValue] = useState(0); const [stripValue, setStripValue] = useState(0);
const [inputValue, setInputValue] = useState('') const [inputValue, setInputValue] = useState('');
const [questions, setQuestions] = useState([]);
const {apiRequest} = useRequest();
const id = localStorage.getItem('id'); const id = localStorage.getItem('id');
const [questions, setQuestions] = useState([])
useEffect(async () => { useEffect(() => {
const response = await fetchGet({ apiRequest(`/question/get-questions?uuid=${dataTest.uuid}`)
link: `${process.env.REACT_APP_API_URL}/api/question/get-questions?uuid=${dataTest.uuid}`, .then((response) => {
Origin: `${process.env.REACT_APP_BASE_URL}`, console.log(response)
} setQuestions(response);
) dispatch(fetchGetAnswers(response[0].id));
setQuestions(response)
dispatch(fetchGetAnswers(response[0].id))
setStripValue((+index + 1) * 100 / response.length) setStripValue((+index + 1) * 100 / response.length)
}, [dispatch]) })
}, [dispatch]);
const nextQuestion = async (e) => { const nextQuestion = async (e) => {
e.preventDefault() e.preventDefault();
//Проверка на валидацию ответов //Проверка на валидацию ответов
if (checkedValues.length || inputValue) { if (checkedValues.length || inputValue) {
switch (questions[index].question_type_id) { switch (questions[index].question_type_id) {
case '3': case '3':
dispatch(fetchUserAnswersMany(checkedValues)) dispatch(fetchUserAnswersMany(checkedValues));
break; break;
case '2': case '2':
case '1': case '1':
case '4': case '4':
dispatch(fetchUserAnswerOne(checkedValues)) dispatch(fetchUserAnswerOne(checkedValues));
break; break;
default: default:
break; break;
@ -61,20 +60,20 @@ export const TaskQuiz = () => {
//Проверка на существование следующего запроса //Проверка на существование следующего запроса
if (index < questions.length - 1) { if (index < questions.length - 1) {
await dispatch(fetchGetAnswers(questions[index + 1].id)) await dispatch(fetchGetAnswers(questions[index + 1].id));
setIndex(prev => prev >= questions.length - 1 ? prev : prev + 1) setIndex(prev => prev >= questions.length - 1 ? prev : prev + 1);
setStripValue((prev => prev + (100 / questions.length))) setStripValue((prev => prev + (100 / questions.length)));
setCheckedValues([]); setCheckedValues([]);
setInputValue('') setInputValue('')
} else { } else {
history.push(`/quiz-result`) navigate(`/quiz/result`);
alert("Тест пройден!") alert("Тест пройден!")
} }
} else { } else {
alert("Вы не ответили на вопрос") alert("Вы не ответили на вопрос")
} }
} };
const handleChange = (e) => { const handleChange = (e) => {
const checked = e.target.checked; const checked = e.target.checked;
@ -86,8 +85,8 @@ export const TaskQuiz = () => {
question_id: questions[index].id, question_id: questions[index].id,
response_body: e.target.value response_body: e.target.value
}]) : }]) :
setCheckedValues(prev => [...prev.filter(item => item.response_body !== e.target.value)]) setCheckedValues(prev => [...prev.filter(item => item.response_body !== e.target.value)]);
break break;
case '1': case '1':
case '2': case '2':
case '4': case '4':
@ -136,7 +135,8 @@ export const TaskQuiz = () => {
{questions.length !== index + 1 && {questions.length !== index + 1 &&
<button type='submit' className='quiz-btn' <button type='submit' className='quiz-btn'
onClick={(e) => nextQuestion(e)}>Далее</button>} onClick={(e) => nextQuestion(e)}>Далее</button>}
{questions.length === index + 1 && <button onClick={(e) => nextQuestion(e)} {questions.length === index + 1 &&
<button onClick={(e) => nextQuestion(e)}
className='quiz-btn quiz-btn_dark-green'>Завершить</button>} className='quiz-btn quiz-btn_dark-green'>Завершить</button>}
</div> </div>
</form> </form>
@ -146,4 +146,4 @@ export const TaskQuiz = () => {
</div> </div>
</React.StrictMode> </React.StrictMode>
) )
} };

Binary file not shown.

Binary file not shown.

Binary file not shown.

Binary file not shown.

Binary file not shown.

Binary file not shown.

Binary file not shown.

Binary file not shown.

Binary file not shown.

Binary file not shown.

Binary file not shown.

Binary file not shown.

Binary file not shown.

Binary file not shown.

Binary file not shown.

Binary file not shown.

View File

@ -353,3 +353,44 @@
font-weight: bold; font-weight: bold;
font-style: normal; font-style: normal;
} }
@font-face {
font-family: 'LabGrotesque';
src: url('LabGrotesque-Regular.eot');
src: local('LabGrotesque-Regular'), local('LabGrotesque-Regular'),
url('LabGrotesque-Regular.eot?#iefix') format('embedded-opentype'),
url('LabGrotesque-Regular.woff2') format('woff2'), url('LabGrotesque-Regular.woff') format('woff'),
url('LabGrotesque-Regular.ttf') format('truetype');
font-weight: 400;
}
@font-face {
font-family: 'LabGrotesque';
src: url('LabGrotesque-Medium.eot');
src: local('LabGrotesque-Medium'), local('LabGrotesque-Medium'),
url('LabGrotesque-Medium.eot?#iefix') format('embedded-opentype'),
url('LabGrotesque-Medium.woff2') format('woff2'), url('LabGrotesque-Medium.woff') format('woff'),
url('LabGrotesque-Medium.ttf') format('truetype');
font-weight: 500;
}
@font-face {
font-family: 'LabGrotesque';
src: url('LabGrotesque-Bold.eot');
src: local('LabGrotesque-Bold'), local('LabGrotesque-Bold'),
url('LabGrotesque-Bold.eot?#iefix') format('embedded-opentype'),
url('LabGrotesque-Bold.woff2') format('woff2'), url('LabGrotesque-Bold.woff') format('woff'),
url('LabGrotesque-Bold.ttf') format('truetype');
font-weight: 700;
}
@font-face {
font-family: 'LabGrotesque';
src: url('LabGrotesque-Light.eot');
src: local('LabGrotesque-Light'), local('LabGrotesque-Light'),
url('LabGrotesque-Light.eot?#iefix') format('embedded-opentype'),
url('LabGrotesque-Light.woff2') format('woff2'), url('LabGrotesque-Light.woff') format('woff'),
url('LabGrotesque-Light.ttf') format('truetype');
font-weight: 300;
}

37
src/helper.js Normal file
View File

@ -0,0 +1,37 @@
export function createMarkup(text) {
return {__html: text.split('</p>').join('</p>')}
}
export function transformHtml(text) {
let startHtml = {__html: text.split('<h2>').join('<br><h2>').split('<br>')};
startHtml = startHtml.__html.filter((el) =>
el !== null && el !== "" || el === 0
);
const finalHtml = startHtml.map((item) =>
`<div class='experience__block'>
<div class="summary__sections__head">
<h3>Описание опыта работы</h3>
<button>Редактировать раздел</button>
</div>
<div class="experience__content">${item.split('<h3>')[0]}</div>
</div>`
);
return {__html: finalHtml.join('')}
}
//
// export const setToken = () => {
// const url = new URL(window.location.href);
// const urlT = url.searchParams.get("token");
// urlT ? sessionStorage.setItem('token', 'Bearer ' + urlT) : '';
// const tParam = urlT || sessionStorage.getItem('token');
// return tParam ? {"Authorization": tParam} : false
//
// };
export const getToken = () => {
const tParam = `Bearer ${localStorage.getItem('auth_token')}`
return tParam ? {Authorization: tParam} : {};
};
export const urlHasParams = (url) => url.indexOf('?') > 0 ? `${url}&${window.location.search.substr(1)}` : `${url}${window.location.search}`;

18
src/hooks/useLogout.js Normal file
View File

@ -0,0 +1,18 @@
import {useDispatch, useSelector} from "react-redux";
import {getRole} from "../redux/roleSlice";
import {useNavigate} from "react-router-dom";
import {auth} from "../redux/outstaffingSlice";
export const useLogout = () => {
const dispatch = useDispatch();
const userRole = useSelector(getRole);
const navigate = useNavigate();
const logout = () => {
localStorage.clear();
dispatch(auth(false));
navigate(userRole === 'ROLE_DEV' ? '/authdev' : '/auth')
};
return {logout}
};

61
src/hooks/useRequest.js Normal file
View File

@ -0,0 +1,61 @@
import axios from 'axios';
import {getToken, urlHasParams} from "../helper";
import {useLogout} from "./useLogout";
const instance = axios.create({
baseURL: process.env.REACT_APP_API_URL,
validateStatus(status) {
return status;
},
});
export const useRequest = () => {
const {logout} = useLogout();
const apiRequest = (url, {
method = 'get', params, data,
headers = {
'Access-Control-Allow-Origin': '*',
'Content-Type': 'application/json'
},
} = {}) => {
const fullHeaders = {...headers, ...getToken()};
let urWithParams = urlHasParams(url);
return instance
.request({
url: urWithParams,
method,
params,
data,
headers: {...fullHeaders},
})
.then(response => new Promise(resolve => {
if (response.data.redirect || response.status === 401) {
logout()
}
return resolve(response)
}))
.then(response => new Promise(resolve => resolve(response.data)))
};
const RequestError = (code, msg, data) => {
const description = msg ? `- ${msg}` : '';
this.name = 'RequestError';
this.message = `API returned: ${code}${description}.`;
this.code = code;
this.description = msg;
this.data = data;
};
RequestError.prototype = Object.create(Error.prototype);
RequestError.prototype.constructor = RequestError;
return {apiRequest}
};

BIN
src/images/arrowRight.png Normal file

Binary file not shown.

After

Width:  |  Height:  |  Size: 240 B

BIN
src/images/gitItemImg.png Normal file

Binary file not shown.

After

Width:  |  Height:  |  Size: 1.0 KiB

BIN
src/images/paymentIcon.png Normal file

Binary file not shown.

After

Width:  |  Height:  |  Size: 966 B

BIN
src/images/reports.png Normal file

Binary file not shown.

After

Width:  |  Height:  |  Size: 845 B

BIN
src/images/settingIcon.png Normal file

Binary file not shown.

After

Width:  |  Height:  |  Size: 836 B

BIN
src/images/summaryIcon.png Normal file

Binary file not shown.

After

Width:  |  Height:  |  Size: 1.4 KiB

BIN
src/images/timerIcon.png Normal file

Binary file not shown.

After

Width:  |  Height:  |  Size: 1.1 KiB

View File

@ -1,6 +1,6 @@
import React from 'react' import React from 'react'
import { AuthBox } from '../AuthBox/AuthBox' import { AuthBox } from '../../components/AuthBox/AuthBox'
import { useSelector } from 'react-redux' import { useSelector } from 'react-redux'
import arrow from '../../images/arrow__login_page.png' import arrow from '../../images/arrow__login_page.png'
@ -10,16 +10,18 @@ import text from '../../images/Body_Text.png'
import vector from '../../images/Vector_Smart_Object.png' import vector from '../../images/Vector_Smart_Object.png'
import { selectAuth } from '../../redux/outstaffingSlice' import { selectAuth } from '../../redux/outstaffingSlice'
import { Redirect } from 'react-router-dom' import { useNavigate} from 'react-router-dom'
import { Footer } from '../Footer/Footer' import { Footer } from '../../components/Footer/Footer'
import './authForDevelopers.scss' import './authForDevelopers.scss'
const AuthForDevelopers = () => { const AuthForDevelopers = () => {
const isAuth = useSelector(selectAuth)
const isAuth = useSelector(selectAuth);
let navigate = useNavigate();
if (isAuth) { if (isAuth) {
return <Redirect to='/report' /> navigate('/profile')
} }
return ( return (
@ -89,6 +91,6 @@ const AuthForDevelopers = () => {
</div> </div>
</section> </section>
) )
} };
export default AuthForDevelopers export default AuthForDevelopers

View File

@ -7,18 +7,19 @@ import vector from '../../images/Vector_Smart_Object.png'
import vectorBlack from '../../images/Vector_Smart_Object_black.png' import vectorBlack from '../../images/Vector_Smart_Object_black.png'
import { useSelector } from 'react-redux' import { useSelector } from 'react-redux'
import { selectAuth } from '../../redux/outstaffingSlice' import { selectAuth } from '../../redux/outstaffingSlice'
import { Redirect } from 'react-router-dom' import { useNavigate} from 'react-router-dom'
import { Footer } from '../Footer/Footer' import { Footer } from '../../components/Footer/Footer'
import { AuthBox } from '../AuthBox/AuthBox' import { AuthBox } from '../../components/AuthBox/AuthBox'
import './authForPartners.scss' import './authForPartners.scss'
const AuthForPartners = () => { const AuthForPartners = () => {
const isAuth = useSelector(selectAuth) const isAuth = useSelector(selectAuth);
let navigate = useNavigate();
if (isAuth) { if (isAuth) {
return <Redirect to='/' /> navigate('/')
} }
return ( return (
@ -81,6 +82,6 @@ const AuthForPartners = () => {
</div> </div>
</section> </section>
) )
} };
export default AuthForPartners export default AuthForPartners

View File

@ -1,8 +0,0 @@
import React from 'react';
import AuthForDevelopers from '../components/Auth/AuthForDevelopers';
const AuthPageForDevelopers = () => {
return <AuthForDevelopers />;
};
export default AuthPageForDevelopers;

View File

@ -1,8 +0,0 @@
import React from 'react';
import AuthForPartners from '../components/Auth/AuthForPartners';
const AuthPageForPartners = () => {
return <AuthForPartners />;
};
export default AuthPageForPartners;

View File

@ -1,11 +1,11 @@
import React from 'react'; import React from 'react';
import { useHistory } from 'react-router'; import { useNavigate } from 'react-router-dom';
import { WithLogout } from '../hoc/withLogout'; import { WithLogout } from '../hoc/withLogout';
import Calendar from '../components/Calendar/Calendar'; import Calendar from '../components/Calendar/Calendar';
const CalendarPage = () => { const CalendarPage = () => {
const history = useHistory(); const navigate = useNavigate();
return <WithLogout><Calendar onSelect={() => { history.push('/report/0') }} /></WithLogout>; return <WithLogout><Calendar onSelect={() => { navigate('/report/0') }} /></WithLogout>;
}; };
export default CalendarPage; export default CalendarPage;

View File

@ -1,53 +1,51 @@
import React, { useState } from 'react' import React from 'react'
import { useDispatch, useSelector } from 'react-redux' import {useDispatch, useSelector} from 'react-redux'
import { useHistory, useParams, Link } from 'react-router-dom' import {useParams, useNavigate} from 'react-router-dom'
import { import {
currentCandidate, currentCandidate,
selectCurrentCandidate, selectCurrentCandidate,
auth
} from '../redux/outstaffingSlice' } from '../redux/outstaffingSlice'
import SVG from 'react-inlinesvg' import SVG from 'react-inlinesvg'
import { WithLogout } from '../hoc/withLogout' import {WithLogout} from '../hoc/withLogout'
import Form from '../components/Form/Form' import Form from '../components/Form/Form'
import { LEVELS, SKILLS } from '../components/constants/constants' import {LEVELS, SKILLS} from '../constants/constants'
import { fetchGet } from '../server/server' import {Footer} from '../components/Footer/Footer'
import { Footer } from '../components/Footer/Footer'
import arrow from '../images/right-arrow.png' import arrow from '../images/right-arrow.png'
import rectangle from '../images/rectangle_secondPage.png' import rectangle from '../images/rectangle_secondPage.png'
import telegramIcon from '../images/telegram-icon.svg' import telegramIcon from '../images/telegram-icon.svg'
import './formPage.scss' import './formPage.scss'
import { getRole } from '../redux/roleSlice' import {useRequest} from "../hooks/useRequest";
const goBack = (history) => {
history.goBack()
}
const FormPage = () => { const FormPage = () => {
const params = useParams() const params = useParams();
const history = useHistory() const navigate = useNavigate();
const dispatch = useDispatch() const dispatch = useDispatch();
const candidate = useSelector(selectCurrentCandidate) const candidate = useSelector(selectCurrentCandidate);
const role = useSelector(getRole)
const {apiRequest} = useRequest();
const goBack = () => {
navigate(-1)
};
if (!candidate.id) { if (!candidate.id) {
fetchGet({ apiRequest('/profile', {
link: `${process.env.REACT_APP_API_URL}/api/profile/`, params: Number(params.id)
params: Number(params.id), })
history, .then((el) => dispatch(currentCandidate(el)))
role,
logout: () => dispatch(auth(false))
}).then((el) => dispatch(currentCandidate(el)))
} }
return ( return (
<WithLogout> <WithLogout>
<div className='form-page'> <div className='form-page'>
<div className='form-page__back'> <div className='form-page__back'>
<div className='form-page__arrow' onClick={() => goBack(history)}> <div className='form-page__arrow' onClick={goBack}>
<div className='form-page__arrow-img'> <div className='form-page__arrow-img'>
<img src={arrow} alt='' /> <img src={arrow} alt=''/>
</div> </div>
<div className='form-page__back-to-candidate'> <div className='form-page__back-to-candidate'>
<span>Вернуться к кандидату</span> <span>Вернуться к кандидату</span>
@ -56,7 +54,7 @@ const FormPage = () => {
</div> </div>
<div className='form-page__candidate'> <div className='form-page__candidate'>
<div className='form-page__avatar'> <div className='form-page__avatar'>
<img src={candidate.photo} /> <img src={candidate.photo} alt='candidate avatar'/>
</div> </div>
<div className='form-page__candidate-info'> <div className='form-page__candidate-info'>
<div className='form-page__position'> <div className='form-page__position'>
@ -66,14 +64,14 @@ const FormPage = () => {
</span> </span>
</div> </div>
<div className='form-page__selected'> <div className='form-page__selected'>
<img src={rectangle} /> <img src={rectangle} alt='rectangle'/>
<span>Выбранный кандидат</span> <span>Выбранный кандидат</span>
</div> </div>
</div> </div>
</div> </div>
<div className='form-page__interview'> <div className='form-page__interview'>
<div className='form-page__form'> <div className='form-page__form'>
<Form /> <Form/>
</div> </div>
<div className='form-page__separator'> <div className='form-page__separator'>
<div className='form-page__line'></div> <div className='form-page__line'></div>
@ -84,16 +82,16 @@ const FormPage = () => {
Заявка на собеседование через телеграм Заявка на собеседование через телеграм
</div> </div>
<div className='form-page__telegram-icon'> <div className='form-page__telegram-icon'>
<a href='https://t.me/st0kir' target='_blank'> <a href='https://t.me/st0kir' target='_blank' rel="noreferrer">
<SVG src={telegramIcon} /> <SVG src={telegramIcon}/>
</a> </a>
</div> </div>
</div> </div>
</div> </div>
<Footer /> <Footer/>
</div> </div>
</WithLogout> </WithLogout>
) )
} };
export default FormPage export default FormPage

View File

@ -0,0 +1,100 @@
import React from 'react';
import {useSelector} from "react-redux";
import {Link} from "react-router-dom";
import {ProfileHeader} from "../../components/ProfileHeader/ProfileHeader";
import {Footer} from "../../components/Footer/Footer";
import {getProfileInfo} from "../../redux/outstaffingSlice";
import reportsIcon from "../../images/reports.png"
import summaryIcon from "../../images/summaryIcon.png"
import timerIcon from "../../images/timerIcon.png"
import paymentIcon from "../../images/paymentIcon.png"
import settingIcon from "../../images/settingIcon.png"
import rightArrow from "../../images/arrowRight.png"
import './profile.scss'
export const Profile = () => {
const profileInfo = useSelector(getProfileInfo);
return (
<div className='profile'>
<ProfileHeader/>
<div className='container'>
<h2 className='profile__title'>Добрый день, <span>{profileInfo.fio}</span></h2>
<div className='summary__info'>
<div className='summary__person'>
<img src={profileInfo.photo} className='summary__avatar' alt='avatar'/>
<p className='summary__name'>{profileInfo.fio} {profileInfo.specification}</p>
</div>
</div>
<div className='profile__items'>
<Link to={'/profile/calendar'} className='item'>
<div className='item__about'>
<img src={reportsIcon} alt='report'/>
<h3>Ваша отчетность</h3>
</div>
<div className='item__info'>
<p><span></span>Отработанных в этом месяце часов</p>
<div className='item__infoLink'>
<img src={rightArrow} alt='arrow'/>
</div>
</div>
</Link>
<Link to={'/profile/summary'} className='item'>
<div className='item__about'>
<img src={summaryIcon} alt='summary'/>
<h3>Данные и резюме</h3>
</div>
<div className='item__info'>
<p>Ваше резюме<br/><span>заполнено</span></p>
<div className='item__infoLink'>
<img src={rightArrow} alt='arrow'/>
</div>
</div>
</Link>
<Link to={'/profile'} className='item'>
<div className='item__about'>
<img src={timerIcon} alt='timer'/>
<h3>Трекер времени</h3>
</div>
<div className='item__info'>
<p>Сколько времени занимает<br/> выполнение задач</p>
<div className='item__infoLink'>
<img src={rightArrow} alt='arrow'/>
</div>
</div>
</Link>
<Link to={'/profile'} className='item'>
<div className='item__about'>
<img src={paymentIcon} alt='payment'/>
<h3>Выплаты</h3>
</div>
<div className='item__info'>
<p>У вас <span>подтвержден</span><br/> статус самозанятого</p>
<div className='item__infoLink'>
<img src={rightArrow} alt='arrow'/>
</div>
</div>
</Link>
<Link to={'/profile'} className='item'>
<div className='item__about'>
<img src={settingIcon} alt='settings'/>
<h3>Настройки аккаунта</h3>
</div>
<div className='item__info'>
<p>Перейдите чтобы начать<br/> редактирование</p>
<div className='item__infoLink'>
<img src={rightArrow} alt='arrow'/>
</div>
</div>
</Link>
</div>
</div>
<Footer/>
</div>
)
};

View File

@ -0,0 +1,126 @@
.profile {
background: #F1F1F1;
height: 100%;
min-height: 100vh;
font-family: 'LabGrotesque', sans-serif;
&__title {
font-weight: 700;
font-size: 22px;
line-height: 32px;
color: #000000;
span {
color: #52B709;
}
@media (max-width: 560px) {
font-size: 17px;
margin-top: 20px;
}
}
&__info {
min-height: 128px;
background: white;
border-radius: 12px;
margin-top: 30px;
display: flex;
align-items: center;
padding: 0 130px 0 45px;
justify-content: space-between;
}
&__items {
display: flex;
flex-wrap: wrap;
margin-top: 30px;
row-gap: 25px;
column-gap: 35px;
@media (max-width: 1175px) {
justify-content: center;
}
.item {
max-width: 353px;
width: 100%;
padding: 35px 45px 15px 30px;
background: #FFFFFF;
border-radius: 12px;
text-decoration: none;
cursor: pointer;
transition: all 0.3s ease;
&:hover {
box-shadow: 6px 5px 20px rgb(87 98 80 / 21%);
transform: scale(1.02);
}
@media (max-width: 1175px) {
width: 48%;
max-width: none;
}
@media (max-width: 925px) {
width: 100%;
}
&__about {
display: flex;
column-gap: 20px;
align-items: center;
margin-bottom: 30px;
h3 {
color: #000000;
font-weight: 500;
font-size: 18px;
line-height: 22px;
max-width: 125px;
margin-bottom: 0;
}
}
&__info {
display: flex;
justify-content: space-between;
align-items: center;
p {
font-weight: 700;
font-size: 12px;
line-height: 20px;
color: #000000;
margin-bottom: 0;
span {
color: #52B709;
font-weight: 700;
}
}
&Link {
width: 48px;
height: 48px;
background: #DDEEC6;
border-radius: 50px;
display: flex;
justify-content: center;
align-items: center;
}
}
}
}
.container {
max-width: 1160px;
margin-top: 23px;
@media (max-width: 570px) {
margin-top: 0;
}
}
footer {
margin-top: 70px;
}
}

View File

@ -0,0 +1,8 @@
import React from 'react';
import { ProfileCalendar } from '../../src/components/ProfileCalendar/ProfileCalendar';
const ProfileCalendarPage = () => {
return <ProfileCalendar/>;
};
export default ProfileCalendarPage;

View File

@ -2,6 +2,6 @@ import React from 'react';
import { WithLogout } from '../hoc/withLogout'; import { WithLogout } from '../hoc/withLogout';
import ReportForm from '../components/ReportForm/ReportForm'; import ReportForm from '../components/ReportForm/ReportForm';
const ReportFormPage = () => <WithLogout><ReportForm /></WithLogout>; const ReportFormPage = () => <ReportForm />;
export default ReportFormPage; export default ReportFormPage;

View File

@ -22,7 +22,7 @@ const tasks = [
text: 'Задача «83 Навигационная система Поиск по почтовому индексу Добавить экран поиска по почтовому индексу» не может быть завершена, т.к. работа над задачей «82 Навигационная система Разработать модуль поиска по почтовому индексу» ещё не начата', text: 'Задача «83 Навигационная система Поиск по почтовому индексу Добавить экран поиска по почтовому индексу» не может быть завершена, т.к. работа над задачей «82 Навигационная система Разработать модуль поиска по почтовому индексу» ещё не начата',
hours: 3 hours: 3
} }
] ];
const SingleReportPage = () => { const SingleReportPage = () => {
return ( return (
@ -30,7 +30,7 @@ const SingleReportPage = () => {
<div className='single-report-page'> <div className='single-report-page'>
<div className='single-report-page__back'> <div className='single-report-page__back'>
<div className='single-report-page__back-arrow'> <div className='single-report-page__back-arrow'>
<img src={arrowLeft} /> <img src={arrowLeft} alt='arrowLeft'/>
</div> </div>
<div className='single-report-page__back-text'> <div className='single-report-page__back-text'>
Вернуться к списку Вернуться к списку
@ -103,6 +103,6 @@ const SingleReportPage = () => {
</div> </div>
</WithLogout> </WithLogout>
) )
} };
export default SingleReportPage export default SingleReportPage

View File

@ -0,0 +1,101 @@
import React, {useEffect, useState} from 'react';
import {useSelector} from "react-redux";
import {ProfileHeader} from "../../components/ProfileHeader/ProfileHeader";
import {getProfileInfo} from "../../redux/outstaffingSlice";
import {Footer} from '../../components/Footer/Footer'
import {transformHtml} from "../../helper";
import {useRequest} from "../../hooks/useRequest";
import arrow from "../../images/right-arrow.png";
import rightArrow from "../../images/arrowRight.png"
import gitImgItem from "../../images/gitItemImg.png"
import './summary.scss'
export const Summary = () => {
const profileInfo = useSelector(getProfileInfo);
const [openGit, setOpenGit] = useState(false);
const [gitInfo, setGitInfo] = useState([]);
const {apiRequest} = useRequest();
useEffect(() => {
apiRequest(`/profile/portfolio-projects?card_id=${localStorage.getItem('cardId')}`)
.then(responseGit => setGitInfo(responseGit))
}, []);
return (
<div className='summary'>
<ProfileHeader/>
<div className='container'>
<div className='summary__content'>
<h2 className='summary__title'>Ваше резюме {openGit && <span>- Git</span>}</h2>
{openGit && <div className='summary__back' onClick={() => setOpenGit(false)}>
<img src={arrow} alt='arrow'/>
<p>Вернуться</p>
</div>}
<div className='summary__info'>
<div className='summary__person'>
<img src={profileInfo.photo} className='summary__avatar' alt='avatar'/>
<p className='summary__name'>{profileInfo.fio} {profileInfo.specification}</p>
</div>
{!openGit &&
<button className='summary__git' onClick={() => setOpenGit(true)}>Git</button>
}
</div>
</div>
{!openGit &&
<div className='summary__skills skills__section'>
<div className='summary__sections__head'>
<h3>Основной стек</h3>
<button>Редактировать раздел</button>
</div>
<div className='skills__section__items'>
<div className='skills__section__items__wrapper'>
{profileInfo.skillValues && profileInfo.skillValues.map((skill) =>
<span key={skill.id} className='skill_item'>{skill.skill.name}</span>
)}
</div>
</div>
</div>
}
{profileInfo.vc_text && !openGit &&
<div className='summary__experience' dangerouslySetInnerHTML={transformHtml(profileInfo.vc_text)}>
</div>
}
{openGit &&
<div className='summary__sectionGit'>
<div className='summary__sections__head'>
<h3>Страница портфолио кода разработчика</h3>
<button>Редактировать раздел</button>
</div>
<div className='summary__sectionGitItems'>
{gitInfo.length && gitInfo.map((itemGit) => {
return <div key={itemGit.id} className='summary__sectionGitItem gitItem'>
<div className='gitItem__info'>
<div className='gitItem__info__about'>
<img src={gitImgItem} alt='gitImg'/>
<div className='gitItem__info__name'>
<h4>{itemGit.title}</h4>
<p>{itemGit.description}</p>
</div>
</div>
<div className='gitItem__info__specification'>
<span className='gitItem__lineSkill'/>
<p>{itemGit.main_stack}</p>
</div>
</div>
<a className='gitItem__link' href={itemGit.link} target="_blank" rel="noreferrer">
<img src={rightArrow} alt='arrowRight'/>
</a>
</div>
})
}
</div>
</div>
}
</div>
<Footer/>
</div>
)
};

View File

@ -0,0 +1,391 @@
.summary {
background: #F1F1F1;
height: 100%;
min-height: 100vh;
font-family: 'LabGrotesque', sans-serif;
//
//&__container {
// max-width: 1160px;
// padding: 0 10px;
// margin: 20px auto;
// position: relative;
// display: flex;
// flex-direction: column;
//}
&__content {
display: flex;
flex-direction: column;
}
&__title {
font-weight: 700;
font-size: 22px;
line-height: 32px;
margin-bottom: 0;
span {
color: #52B709;
}
}
&__back {
display: flex;
align-items: center;
column-gap: 30px;
margin-top: 20px;
cursor: pointer;
p {
margin-bottom: 0;
font-size: 14px;
line-height: 32px;
font-weight: 500;
}
}
&__info {
min-height: 128px;
background: white;
border-radius: 12px;
margin-top: 30px;
display: flex;
align-items: center;
padding: 0 130px 0 45px;
justify-content: space-between;
@media (max-width: 930px) {
padding: 0 40px;
}
@media (max-width: 690px) {
padding: 0 20px;
min-height: 80px;
}
}
&__person {
display: flex;
align-items: center;
column-gap: 45px;
@media (max-width: 690px) {
column-gap: 20px;
}
}
&__avatar {
width: 88px;
height: 88px;
border-radius: 100px;
@media (max-width: 690px) {
width: 44px;
height: 44px;
min-width: 44px;
min-height: 44px;
}
}
&__name {
font-weight: 500;
font-size: 16px;
line-height: 32px;
position: relative;
@media (max-width: 690px) {
font-size: 14px;
margin-right: 10px;
line-height: 15px;
}
&:after {
content: '';
position: absolute;
background: #52B709;
border-radius: 12px;
width: 70%;
height: 8px;
bottom: -14px;
left: 0;
}
}
&__git {
background: #52B709;
border-radius: 44px;
width: 177px;
height: 50px;
font-weight: 500;
font-size: 16px;
line-height: 32px;
color: white;
border: none;
transition: all 0.3s ease;
&:hover {
box-shadow: 6px 5px 20px rgb(87 98 80 / 21%);
transform: scale(1.02);
}
@media (max-width: 690px) {
width: 120px;
}
}
&__skills {
background: #FFFFFF;
border-radius: 12px;
margin-top: 35px;
}
&__sections__head {
display: flex;
min-height: 69px;
background: #E1FCCF;
border-radius: 12px 12px 0px 0px;
align-items: center;
padding: 0 35px 0 50px;
justify-content: space-between;
@media (max-width: 550px) {
padding: 0 15px;
}
h3 {
font-style: normal;
font-size: 18px;
line-height: 32px;
@media (max-width: 660px) {
line-height: 20px;
}
}
button {
background: #FFFFFF;
border-radius: 44px;
padding: 10px 20px;
display: flex;
align-items: center;
border: none;
height: 42px;
font-weight: 500;
font-size: 14px;
line-height: 32px;
white-space: nowrap;
@media (max-width: 520px) {
font-size: 12px;
padding: 10px;
white-space: nowrap;
}
}
}
.skills__section {
&__items {
padding: 25px 35px 25px 50px;
@media (max-width: 550px) {
padding: 15px 15px 20px;
}
&__wrapper {
max-width: 630px;
display: flex;
flex-wrap: wrap;
column-gap: 5px;
.skill_item {
font-weight: 700;
font-size: 16px;
line-height: 32px;
white-space: nowrap;
text-transform: uppercase;
}
}
}
}
&__experience {
display: flex;
flex-direction: column;
row-gap: 20px;
margin-top: 30px;
.experience {
&__block {
background: #FFFFFF;
border-radius: 12px;
}
&__content {
padding: 15px 35px 15px 50px;
@media (max-width: 550px) {
padding: 15px 15px 20px;
}
h2 {
font-weight: 700;
font-size: 16px;
line-height: 32px;
margin-bottom: 0;
}
p {
font-weight: 400;
font-size: 16px;
line-height: 32px;
margin-bottom: 0;
}
}
}
}
&__sectionGit {
margin-top: 25px;
&Items {
margin-top: 25px;
display: flex;
flex-wrap: wrap;
row-gap: 20px;
column-gap: 25px;
justify-content: space-between;
.gitItem {
width: 48%;
display: flex;
align-items: center;
justify-content: space-between;
background: #FFFFFF;
border-radius: 12px;
padding: 35px 30px 30px 45px;
transition: all 0.3s ease;
&:hover {
box-shadow: 6px 5px 20px rgb(87 98 80 / 21%);
transform: scale(1.02);
}
@media (max-width: 825px) {
width: 100%;
}
@media (max-width: 470px) {
padding: 15px;
}
&__info {
display: flex;
flex-direction: column;
max-width: 350px;
width: 100%;
@media (max-width: 825px) {
max-width: 100%;
}
&__about {
display: flex;
align-items: center;
column-gap: 15px;
}
&__name {
h4 {
font-weight: 700;
font-size: 18px;
line-height: 32px;
margin-bottom: 0;
}
p {
font-weight: 300;
font-size: 16px;
line-height: 32px;
margin-bottom: 0;
white-space: nowrap;
max-width: 300px;
overflow: hidden;
text-overflow: ellipsis;
@media (max-width: 1040px) {
max-width: 250px;
}
@media (max-width: 890px) {
max-width: 200px;
}
@media (max-width: 825px) {
max-width: 500px;
}
@media (max-width: 720px) {
max-width: 250px;
}
@media (max-width: 470px) {
max-width: 200px;
}
}
}
&__specification {
margin-top: 30px;
display: flex;
align-items: center;
padding-left: 10px;
column-gap: 25px;
@media (max-width: 470px) {
margin-top: 0;
}
span {
background: #D4F123;
border-radius: 12px;
max-width: 260px;
width: 100%;
height: 8px;
}
p {
margin-bottom: 0;
font-weight: 400;
font-size: 14px;
line-height: 32px;
}
}
}
&__link {
border-radius: 50%;
background: #DDEEC6;
min-width: 48px;
height: 48px;
cursor: pointer;
display: flex;
align-items: center;
justify-content: center;
}
}
}
}
footer {
margin-top: 70px;
}
.container {
max-width: 1160px;
margin-top: 23px;
@media (max-width: 570px) {
margin-top: 0;
}
}
}

View File

@ -1,6 +1,6 @@
import {Redirect} from "react-router-dom" import {useNavigate} from "react-router-dom"
import { HeaderPageTestsQuiz } from "../../components/features/quiz/HeaderPageTests" import {HeaderPageTestsQuiz} from "../../components/features/quiz/HeaderPageTests"
import { Instruction } from "../../components/features/quiz/Instructions" import {Instruction} from "../../components/features/quiz/Instructions"
import React from "react"; import React from "react";
import {useSelector} from "react-redux"; import {useSelector} from "react-redux";
import {selectedTest} from "../../redux/quizSlice"; import {selectedTest} from "../../redux/quizSlice";
@ -10,14 +10,15 @@ export const InstructionPage = () => {
const test = useSelector(selectedTest) const test = useSelector(selectedTest)
if(!test){ let navigate = useNavigate();
return <Redirect to={'/quiz'} /> if (!test) {
navigate('/quiz')
} }
return ( return (
<> <>
<HeaderPageTestsQuiz isVisibilityButton={false}/> <HeaderPageTestsQuiz isVisibilityButton={false}/>
<Instruction /> <Instruction/>
</> </>
) )
} };

View File

@ -1,18 +1,20 @@
import {Redirect} from "react-router-dom" import React from "react";
import {useNavigate} from "react-router-dom"
import {useSelector} from "react-redux";
import {HeaderPageTestsQuiz} from "../../components/features/quiz/HeaderPageTests" import {HeaderPageTestsQuiz} from "../../components/features/quiz/HeaderPageTests"
import {MyTestsQuiz} from "../../components/features/quiz/MyTestsQuiz" import {MyTestsQuiz} from "../../components/features/quiz/MyTestsQuiz"
import {useSelector} from "react-redux";
import {selectedTest, selectPassedTests} from "../../redux/quizSlice"; import {selectedTest, selectPassedTests} from "../../redux/quizSlice";
import React from "react";
export const InterjacentPage = () => { export const InterjacentPage = () => {
const test = useSelector(selectedTest) const test = useSelector(selectedTest);
const passedTests = useSelector(selectPassedTests) const passedTests = useSelector(selectPassedTests)
let navigate = useNavigate();
if (!test) { if (!test) {
return <Redirect to={'/quiz'}/> navigate('/quiz')
} }
return ( return (
@ -21,4 +23,4 @@ export const InterjacentPage = () => {
<MyTestsQuiz listTests={passedTests}/> <MyTestsQuiz listTests={passedTests}/>
</> </>
) )
} };

View File

@ -1,17 +1,16 @@
import {Link, Redirect} from 'react-router-dom' import {useNavigate} from 'react-router-dom'
import {HeaderPageTestsQuiz} from '../../components/features/quiz/HeaderPageTests' import {HeaderPageTestsQuiz} from '../../components/features/quiz/HeaderPageTests'
import {Progressbar} from '../../components/features/quiz/ProgressbarQuiz'
import {TaskQuiz} from '../../components/features/quiz/Task' import {TaskQuiz} from '../../components/features/quiz/Task'
import {useSelector} from "react-redux"; import {useSelector} from "react-redux";
import {selectedTest} from "../../redux/quizSlice"; import {selectedTest} from "../../redux/quizSlice";
import React from "react"; import React from "react";
export const QuizTestPage = () => { export const QuizTestPage = () => {
let navigate = useNavigate()
const test = useSelector(selectedTest) const test = useSelector(selectedTest)
if (!test) { if (!test) {
return <Redirect to={'/quiz'}/> navigate('/quiz')
} }
return ( return (
<> <>
@ -19,4 +18,4 @@ export const QuizTestPage = () => {
<TaskQuiz/> <TaskQuiz/>
</> </>
) )
} };

View File

@ -1,4 +1,4 @@
import {Link, Redirect} from "react-router-dom" import {useNavigate} from "react-router-dom"
import {HeaderPageTestsQuiz} from "../../components/features/quiz/HeaderPageTests" import {HeaderPageTestsQuiz} from "../../components/features/quiz/HeaderPageTests"
import {Results} from "../../components/features/quiz/Results"; import {Results} from "../../components/features/quiz/Results";
import {useSelector} from "react-redux"; import {useSelector} from "react-redux";
@ -10,8 +10,9 @@ export const ResultPage = () => {
const test = useSelector(selectedTest) const test = useSelector(selectedTest)
let navigate = useNavigate();
if (!test) { if (!test) {
return <Redirect to={'/quiz'}/> navigate('/quiz')
} }
return ( return (
@ -20,4 +21,4 @@ export const ResultPage = () => {
<Results/> <Results/>
</> </>
) )
} };

View File

@ -8,6 +8,8 @@ const initialState = {
currentCandidate: {}, currentCandidate: {},
auth: false, auth: false,
positionId: null, positionId: null,
profileInfo: {},
reportsDates: ''
}; };
export const outstaffingSlice = createSlice({ export const outstaffingSlice = createSlice({
@ -37,11 +39,17 @@ export const outstaffingSlice = createSlice({
}, },
setUserInfo: (state, action) => { setUserInfo: (state, action) => {
state.userInfo = action.payload; state.userInfo = action.payload;
} },
setProfileInfo: (state, action) => {
state.profileInfo = action.payload;
},
setReportsDates: (state, action) => {
state.reportsDates = action.payload;
},
}, },
}); });
export const { tags, profiles, selectedItems, auth, currentCandidate, filteredCandidates, setPositionId, setUserInfo } = outstaffingSlice.actions; export const { tags, profiles, selectedItems, auth, currentCandidate, filteredCandidates, setPositionId, setUserInfo, setProfileInfo, setReportsDates } = outstaffingSlice.actions;
export const selectProfiles = (state) => state.outstaffing.profiles; export const selectProfiles = (state) => state.outstaffing.profiles;
export const selectTags = (state) => state.outstaffing.tags; export const selectTags = (state) => state.outstaffing.tags;
@ -50,6 +58,8 @@ export const selectItems = (state) => state.outstaffing.selectedItems;
export const selectCurrentCandidate = (state) => state.outstaffing.currentCandidate; export const selectCurrentCandidate = (state) => state.outstaffing.currentCandidate;
export const selectAuth = (state) => state.outstaffing.auth; export const selectAuth = (state) => state.outstaffing.auth;
export const getPositionId = (state) => state.outstaffing.positionId; export const getPositionId = (state) => state.outstaffing.positionId;
export const getProfileInfo = (state) => state.outstaffing.profileInfo;
export const selectUserInfo = (state) => state.outstaffing.userInfo; export const selectUserInfo = (state) => state.outstaffing.userInfo;
export const getReportsDates = (state) => state.outstaffing.reportsDates;
export default outstaffingSlice.reducer; export default outstaffingSlice.reducer;

View File

@ -1,5 +1,5 @@
import {createAsyncThunk, createSlice} from '@reduxjs/toolkit'; import {createAsyncThunk, createSlice} from '@reduxjs/toolkit';
import {fetchGet, fetchPost} from './../server/server' import {fetchGet} from './../server/server'
import axios from "axios"; import axios from "axios";
@ -17,17 +17,16 @@ export const setUserInfo = createAsyncThunk(
'userInfo', 'userInfo',
async (id) => { async (id) => {
try{ try{
const response = await fetchGet({ return await fetchGet({
link: `${process.env.REACT_APP_API_URL}/api/profile/get-main-data?user_id=${id}`, link: `${process.env.REACT_APP_API_URL}/api/profile/get-main-data?user_id=${id}`,
Origin: `${process.env.REACT_APP_BASE_URL}`, Origin: `${process.env.REACT_APP_BASE_URL}`,
} }
) )
return response
}catch (e) { }catch (e) {
console.log(e) console.log(e)
} }
} }
) );
export const fetchUserAnswersMany = createAsyncThunk( export const fetchUserAnswersMany = createAsyncThunk(
'answersUserMany', 'answersUserMany',
@ -38,13 +37,13 @@ export const fetchUserAnswersMany = createAsyncThunk(
headers: { headers: {
Authorization: `Bearer ${localStorage.getItem('auth_token')}`, Authorization: `Bearer ${localStorage.getItem('auth_token')}`,
} }
}) });
return response.data return response.data
}catch (e) { }catch (e) {
console.log(e) console.log(e)
} }
} }
) );
export const fetchUserAnswerOne = createAsyncThunk( export const fetchUserAnswerOne = createAsyncThunk(
'answersUserOne', 'answersUserOne',
async (checkedValues) => { async (checkedValues) => {
@ -54,55 +53,43 @@ export const fetchUserAnswerOne = createAsyncThunk(
headers: { headers: {
Authorization: `Bearer ${localStorage.getItem('auth_token')}`, Authorization: `Bearer ${localStorage.getItem('auth_token')}`,
} }
}) });
return response.data return response.data
}catch (e) { }catch (e) {
console.log(e) console.log(e)
} }
} }
) );
export const fetchGetAnswers = createAsyncThunk( export const fetchGetAnswers = createAsyncThunk(
'answers', 'answers',
async (question_id) => { async (question_id) => {
const resp = await fetchGet({ return await fetchGet({
link: `${process.env.REACT_APP_API_URL}/api/answer/get-answers?question_id=${question_id}`, link: `${process.env.REACT_APP_API_URL}/api/answer/get-answers?question_id=${question_id}`,
Origin: `${process.env.REACT_APP_BASE_URL}`, Origin: `${process.env.REACT_APP_BASE_URL}`,
} }
) )
return resp
} }
) );
// export const fetchGetQuestion = createAsyncThunk(
// 'questions',
// async (uuid) => {
// const resp = await fetchGet({
// link: `${process.env.REACT_APP_API_URL}/api/question/get-questions?uuid=${uuid}`,
// Origin: `${process.env.REACT_APP_BASE_URL}`,
// }
// )
// return resp
// }
// )
export const fetchResultTest = createAsyncThunk( export const fetchResultTest = createAsyncThunk(
'result', 'result',
async (uuid) => { async (uuid) => {
const resp = await fetchGet({ return await fetchGet({
link: `${process.env.REACT_APP_API_URL}/api/user-questionnaire/questionnaire-completed?user_questionnaire_uuid=${uuid}`, link: `${process.env.REACT_APP_API_URL}/api/user-questionnaire/questionnaire-completed?user_questionnaire_uuid=${uuid}`,
Origin: `${process.env.REACT_APP_BASE_URL}`, Origin: `${process.env.REACT_APP_BASE_URL}`,
} }
) )
return resp
} }
) );
export const quizSlice = createSlice({ export const quizSlice = createSlice({
name: 'quiz', name: 'quiz',
initialState, initialState,
reducers: { reducers: {
setQuestionnairesList: (state, action) => { setQuestionnairesList: (state, action) => {
state.dataQuestionnairesOfUser = action.payload state.dataQuestionnairesOfUser = action.payload;
state.passedTests = action.payload.filter(item=>item.status === 2) state.passedTests = action.payload.filter(item => item.status === 2)
}, },
setSelectedTest: (state, action) => { setSelectedTest: (state, action) => {
state.selectedTest = action.payload state.selectedTest = action.payload
@ -123,11 +110,10 @@ export const quizSlice = createSlice({
export const {setQuestionnairesList, setSelectedTest} = quizSlice.actions; export const {setQuestionnairesList, setSelectedTest} = quizSlice.actions;
// export const selectQuestions = (state) => state.quiz.questions;
export const selectAnswer = (state) => state.quiz.answer; export const selectAnswer = (state) => state.quiz.answer;
export const selectQuestionnairesOfUser = (state) => state.quiz.dataQuestionnairesOfUser; export const selectQuestionnairesOfUser = (state) => state.quiz.dataQuestionnairesOfUser;
export const selectResult = (state) => state.quiz.result; export const selectResult = (state) => state.quiz.result;
export const selectIsLoading = (state) => state.quiz.isLoading;
export const selectedTest = (state) => state.quiz.selectedTest; export const selectedTest = (state) => state.quiz.selectedTest;
export const selectPassedTests = (state) => state.quiz.passedTests; export const selectPassedTests = (state) => state.quiz.passedTests;
export const selectUserInfo = (state) => state.quiz.userInfo; export const selectUserInfo = (state) => state.quiz.userInfo;

View File

@ -2,6 +2,7 @@ import { createSlice } from '@reduxjs/toolkit';
const initialState = { const initialState = {
dateSelected: '', dateSelected: '',
reportDate: ''
}; };
export const reportSlice = createSlice({ export const reportSlice = createSlice({
@ -11,11 +12,16 @@ export const reportSlice = createSlice({
dateSelected: (state, action) => { dateSelected: (state, action) => {
state.dateSelected = action.payload; state.dateSelected = action.payload;
}, },
setReportDate: (state, action) => {
state.reportDate = action.payload;
},
}, },
}); });
export const { dateSelected, } = reportSlice.actions; export const { dateSelected, setReportDate} = reportSlice.actions;
export const selectDate = (state) => state.report.dateSelected; export const selectDate = (state) => state.report.dateSelected;
export const getReportDate = (state) => state.report.reportDate;
export default reportSlice.reducer; export default reportSlice.reducer;

View File

@ -1,22 +1,22 @@
export const withAuthRedirect = export const withAuthRedirect =
(actionCall) => (actionCall) =>
({ link, params, history, role, logout, body }) => { ({ link, params, history, role, logout, body }) => {
const linkWithParams = params // const linkWithParams = params
? `${link}?${new URLSearchParams(params)}` // ? `${link}?${new URLSearchParams(params)}`
: link // : link;
return actionCall(linkWithParams, body) // return actionCall(linkWithParams, body)
.then((res) => { // .then((res) => {
if (res.status && res.status === 401) { // if (res.status && res.status === 401) {
localStorage.clear() // localStorage.clear();
logout && logout() // logout && logout();
history.push(role === 'ROLE_DEV' ? '/authdev' : '/auth') // history.push(role === 'ROLE_DEV' ? '/authdev' : '/auth')
} // }
//
return res // return res
}) // })
.catch((err) => { // .catch((err) => {
localStorage.clear() // localStorage.clear();
logout && logout() // logout && logout();
history.push(role === 'ROLE_DEV' ? '/authdev' : '/auth') // history.push(role === 'ROLE_DEV' ? '/authdev' : '/auth')
}) // })
} };

View File

@ -1,84 +1,5 @@
import { withAuthRedirect } from './authRedirect' import { withAuthRedirect } from './authRedirect'
export const fetchForm = withAuthRedirect(async (link, info) => {
try {
const response = await fetch(link, {
method: 'POST',
headers: {
// 'Access-Control-Request-Headers': 'authorization',
Authorization: `Bearer ${localStorage.getItem('auth_token')}`,
Origin: `${process.env.REACT_APP_BASE_URL}`,
'Content-Type': 'application/json'
},
body: JSON.stringify(info)
})
return response
} catch (error) {
console.log('Query error', error)
}
})
export const fetchAuth = async ({
username,
password,
dispatch,
catchError
}) => {
const apiURL = process.env.REACT_APP_API_URL
try {
const response = await fetch(`${apiURL}/api/user/login`, {
method: 'POST',
mode: 'cors',
headers: {
'Access-Control-Request-Headers': 'authorization',
'Content-Type': 'application/json'
// Origin: `http://localhost`
},
body: JSON.stringify({
username,
password
})
})
if (!response.ok) {
catchError()
return response.statusText
}
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
)
dispatch(resJSON)
})
} catch (error) {
console.error('Error occured: ', error)
}
}
export const fetchReportList = withAuthRedirect(async (link) => {
try {
const response = await fetch(
`https://guild.loc/api/reports/index?user_id=26&fromDate=2021-10-18`,
// link,
{
method: 'GET',
headers: {
Authorization: `Bearer ${localStorage.getItem('auth_token')}`
}
}
)
let data = await response.json()
return data
} catch (error) {
console.log('Query error', error)
}
})
export const fetchGet = withAuthRedirect(async (link) => { export const fetchGet = withAuthRedirect(async (link) => {
try { try {
@ -87,31 +8,11 @@ export const fetchGet = withAuthRedirect(async (link) => {
headers: { headers: {
Authorization: `Bearer ${localStorage.getItem('auth_token')}` Authorization: `Bearer ${localStorage.getItem('auth_token')}`
} }
}) });
let data = await response.json() let data = await response.json();
return data return data
} catch (error) { } catch (error) {
console.log('Query error', error) console.log('Query error', error)
} }
}) });
export const fetchPost = withAuthRedirect(async (link, body) => {
console.log('i', body)
try {
const response = await fetch(link, {
method: 'POST',
headers: {
Authorization: `Bearer ${localStorage.getItem('auth_token')}`,
'Content-Type': 'application/json',
//Origin: `http://localhost:3000`
Origin: `${process.env.REACT_APP_BASE_URL}`
},
body: JSON.stringify(body)
})
return response
} catch (error) {
console.log('Query error', error)
}
})