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_BASE_URL = https://dev.itguild.info
REACT_APP_API_URL = https://dev.itguild.info/api
REACT_APP_BASE_URL = https://dev.itguild.info/api

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

View File

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

View File

@ -1,66 +1,66 @@
import React from 'react'
import { BrowserRouter as Router, Route, Switch } from 'react-router-dom'
import 'bootstrap/dist/css/bootstrap.min.css'
import './fonts/stylesheet.css'
import {BrowserRouter as Router, Route, Routes, Navigate} from 'react-router-dom';
import { ProtectedRoute } from './components/ProtectedRoute/ProtectedRoute'
import AuthPageForDevelopers from './pages/AuthPageForDevelopers'
import AuthPageForPartners from './pages/AuthPageForPartners'
import AuthForPartners from "./pages/AuthForPartners/AuthForPartners";
import AuthForDevelopers from "./pages/AuthForDevelopers/AuthForDevelopers";
import HomePage from './pages/HomePage'
import CandidatePage from './pages/CandidatePage'
import CalendarPage from './pages/CalendarPage'
import ReportPage from './pages/ReportFormPage.js'
import ProfileCalendarPage from './pages/ProfileCalendarPage.js'
import FormPage from './pages/FormPage.js'
import SingleReportPage from './pages/SingleReportPage'
import { QuizPage } from './pages/quiz/QuizPage'
import { InterjacentPage } from './pages/quiz/InterjacentPage'
import { QuizTestPage } from './pages/quiz/QuizTestPage'
import { InstructionPage } from './pages/quiz/InstructionPage'
import { ResultPage } from './pages/quiz/ResultPage'
import {QuizPage} from './pages/quiz/QuizPage'
import {InterjacentPage} from './pages/quiz/InterjacentPage'
import {QuizTestPage} from './pages/quiz/QuizTestPage'
import {InstructionPage} from './pages/quiz/InstructionPage'
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 = () => {
return (
<>
<h1>IT Аутстаффинг в России</h1>
<Router>
<Switch>
<Route path='/authdev' exact>
<AuthPageForDevelopers />
</Route>
<Route path='/auth' exact>
<AuthPageForPartners />
</Route>
<ProtectedRoute exact path='/' component={HomePage} />
<ProtectedRoute
exact
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>
</>
<>
<h1>IT Аутстаффинг в России</h1>
<Router>
<Routes>
<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 exact path='profile'>
<Route index element={<Profile/>}/>
<Route exact path='calendar' element={<ProfileCalendarPage/>}/>
<Route exact path='summary' element={<Summary/>}/>
</Route>
<Route path="*" element={<Navigate to="/" replace/>}/>
</Routes>
</Router>
</>
)
}
};
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 (
<div className='achievement'>
<div className='achievement__icon'>
<img src={achievement.img} />
<img src={achievement.img} alt='achievement' />
</div>
<div className='achievement__popup'>
<div className='achievement__title'>{achievement.title}</div>

View File

@ -1,107 +1,129 @@
import React, { useState } from 'react'
import { Link } from 'react-router-dom'
import { useDispatch, useSelector } from 'react-redux'
import React, {useState} from 'react'
import {Link} from 'react-router-dom'
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 { Loader } from '../Loader/Loader'
import { fetchAuth } from '../../server/server'
import { setRole } from '../../redux/roleSlice'
import { selectIsLoading } from '../../redux/loaderSlice'
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 dispatch = useDispatch()
const SweetAlert = withSwalInstance(swal);
const isLoading = useSelector(selectIsLoading)
export const AuthBox = ({title, altTitle, roleChangeLink}) => {
const dispatch = useDispatch();
const [username, setUsername] = useState('')
const [password, setPassword] = useState('')
const [error, setError] = useState(null)
const {apiRequest} = useRequest();
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 (
<div className='auth-box'>
<h2 className='auth-box__header'>
Войти в <span>систему</span>
</h2>
<div className='auth-box__title'>
<img src={ellipse} alt='' />
<span>{title}</span>
</div>
<form className='auth-box__form'>
<label htmlFor='login'>Ваш логин:</label>
<input
id='login'
type='text'
placeholder='Логин'
value={username}
onChange={(e) => setUsername(e.target.value)}
/>
<label htmlFor='password'>Пароль:</label>
<input
id='password'
type='password'
placeholder='Пароль'
value={password}
onChange={(e) => setPassword(e.target.value)}
/>
{error && (
<div className='auth-box__form-error'>
<SweetAlert
show={!!error}
title='Ошибка'
text={error}
onConfirm={() => setError(null)}
/>
</div>
)}
<div className='auth-box__form-buttons'>
<button
className='auth-box__form-btn'
onClick={
!isLoading
? (e) => {
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 /> : 'Войти'}
</button>
<Link to={roleChangeLink}>
<button className='auth-box__form-btn--role auth-box__auth-link'>
{altTitle}
</button>
</Link>
<div className='auth-box'>
<h2 className='auth-box__header'>
Войти в <span>систему</span>
</h2>
<div className='auth-box__title'>
<img src={ellipse} alt=''/>
<span>{title}</span>
</div>
</form>
</div>
<form className='auth-box__form'>
<label htmlFor='login'>Ваш логин:</label>
<input
id='login'
type='text'
placeholder='Логин'
value={username}
onChange={(e) => setUsername(e.target.value)}
/>
<label htmlFor='password'>Пароль:</label>
<input
id='password'
type='password'
placeholder='Пароль'
value={password}
onChange={(e) => setPassword(e.target.value)}
/>
{error && (
<div className='auth-box__form-error'>
<SweetAlert
show={!!error}
title='Ошибка'
text={error}
onConfirm={() => setError(null)}
/>
</div>
)}
<div className='auth-box__form-buttons'>
<button
className='auth-box__form-btn'
onClick={(e) => {
e.preventDefault();
submitHandler()
}}
>
{isLoading ? <Loader/> : 'Войти'}
</button>
<Link to={roleChangeLink}>
<button className='auth-box__form-btn--role auth-box__auth-link'>
{altTitle}
</button>
</Link>
</div>
</form>
</div>
)
}
};

View File

@ -1,92 +1,64 @@
import React, { useEffect, useState } from 'react'
import { useDispatch, useSelector } from 'react-redux'
import { selectCurrentCandidate, auth } from '../../redux/outstaffingSlice'
import { Link, useHistory, useParams } 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 React, {useEffect, useState} from 'react'
import {useSelector} from 'react-redux'
import {selectCurrentCandidate} from '../../redux/outstaffingSlice'
import {Link} from 'react-router-dom'
import { fetchReportList } from '../../server/server'
import { getRole } from '../../redux/roleSlice'
import CalendarComponent from './CalendarComponent'
import {currentMonth} from './calendarHelper'
import {Footer} from '../Footer/Footer'
import rectangle from '../../images/rectangle_secondPage.png'
import './calendar.scss'
const getDateParamString = ({ paramName, value }) => {
return value ? `${paramName}=${value}` : ''
}
const Calendar = ({onSelect}) => {
const Calendar = ({ onSelect }) => {
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 candidateForCalendar = useSelector(selectCurrentCandidate);
const history = useHistory()
// 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: () => {}
// })
// }, [])
const [month, setMonth] = useState('');
useEffect(() => {
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 (
<section className='calendar'>
<div className='row'>
<h2 className='calendar__profile'>
Добрый день, <span>Александр !</span>
</h2>
<div className='col-12 col-xl-12 d-flex justify-content-between align-items-center flex-column flex-sm-row'>
<div className='calendar__info'>
<img className='calendar__info-img' src={photo} alt='img' />
<h3 className='calendar__info-name'>{abbreviatedName}</h3>
</div>
<div className='calendar__title'>
<h3 className='calendar__title-text'>{skillsName} разработчик</h3>
<img className='calendar__title-img' src={rectangle} alt='img' />
</div>
<div>
<Link to='/report'>
<button className='calendar__btn'>Заполнить отчет за день</button>
</Link>
<section className='calendar'>
<div className='row'>
<h2 className='calendar__profile'>
Добрый день, <span>Александр !</span>
</h2>
<div className='col-12 col-xl-12 d-flex justify-content-between align-items-center flex-column flex-sm-row'>
<div className='calendar__info'>
<img className='calendar__info-img' src={photo} alt='img'/>
<h3 className='calendar__info-name'>{abbreviatedName}</h3>
</div>
<div className='calendar__title'>
<h3 className='calendar__title-text'>{skillsName} разработчик</h3>
<img className='calendar__title-img' src={rectangle} alt='img'/>
</div>
<div>
<Link to='/report'>
<button className='calendar__btn'>Заполнить отчет за день</button>
</Link>
</div>
</div>
</div>
</div>
<div className='row'>
<div className='col-12 col-xl-12'>
<CalendarComponent onSelect={onSelect} />
<p className='calendar__hours'>
{month} : <span> 60 часов </span>
</p>
<div className='row'>
<div className='col-12 col-xl-12'>
<CalendarComponent onSelect={onSelect}/>
<p className='calendar__hours'>
{month} : <span> 60 часов </span>
</p>
</div>
</div>
</div>
<Footer />
</section>
<Footer/>
</section>
)
}
};
export default Calendar

View File

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

View File

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

View File

@ -20,6 +20,16 @@ export function calendarHelper(value) {
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() {
const currentMonth = moment().format('MMMM');
@ -32,4 +42,4 @@ export function currentMonthAndDay(day) {
export function currentMonthAndDayReportPage() {
return moment().format('D MMMM');
}
}

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

@ -1,77 +1,71 @@
import React from 'react'
import OutsideClickHandler from 'react-outside-click-handler'
import { useDispatch, useSelector } from 'react-redux'
import {useDispatch, useSelector} from 'react-redux'
import {
selectItems,
selectedItems,
filteredCandidates,
auth
} from '../../redux/outstaffingSlice'
import { fetchGet } from '../../server/server'
import { useHistory } from 'react-router-dom'
import { getRole } from '../../redux/roleSlice'
import {useRequest} from "../../hooks/useRequest";
import './outstaffingBlock.scss'
const handlePositionClick = ({
dispatch,
positionId,
isSelected,
onSelect,
history,
role
}) => {
const handlePositionClick = (
{
dispatch,
positionId,
isSelected,
onSelect,
apiRequest
}) => {
if (isSelected) {
fetchGet({
link: `${process.env.REACT_APP_API_URL}/api/profile?limit=`,
params: 4,
history,
role,
logout: () => dispatch(auth(false))
apiRequest('/profile', {
params: {limit: 1000},
}).then((profileArr) => {
dispatch(filteredCandidates(profileArr))
dispatch(selectedItems([]))
dispatch(filteredCandidates(profileArr));
dispatch(selectedItems([]));
onSelect(positionId)
})
} else {
fetchGet({
link: `${process.env.REACT_APP_API_URL}/api/profile?position_id=`,
params: positionId,
history,
role,
logout: () => dispatch(auth(false))
apiRequest('/profile', {
params: {
limit: '1000',
position_id: positionId},
}).then((el) => {
dispatch(filteredCandidates(el))
dispatch(selectedItems([]))
dispatch(filteredCandidates(el));
dispatch(selectedItems([]));
onSelect(positionId)
})
}
}
};
const OutstaffingBlock = ({
dataTags = [],
selected,
img,
header,
positionId,
isSelected,
onSelect
}) => {
const history = useHistory()
const role = useSelector(getRole)
const OutstaffingBlock = (
{
dataTags = [],
selected,
img,
header,
positionId,
isSelected,
onSelect
}) => {
const dispatch = useDispatch()
const itemsArr = useSelector(selectItems)
const dispatch = useDispatch();
const itemsArr = useSelector(selectItems);
const {apiRequest} = useRequest();
const handleBlockClick = (item, id) => {
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) => {
if (el.name === 'skills_back') {
@ -81,61 +75,60 @@ const OutstaffingBlock = ({
} else if (el.name === 'skills_front') {
classes = 'front'
}
})
});
return (
<OutsideClickHandler
onOutsideClick={() => {
isSelected && onSelect(null)
}}
>
<div
className={`outstaffing-block${
isSelected ? ' outstaffing-block__selected' : ''
}`}
<OutsideClickHandler
onOutsideClick={() => {
isSelected && onSelect(null)
}}
>
<div
className={`outstaffing-block__img ${
selected ? ' outstaffing-block__border' : ''
}`}
onClick={() =>
handlePositionClick({
dispatch,
positionId,
isSelected,
onSelect,
history,
role
})
}
className={`outstaffing-block${
isSelected ? ' outstaffing-block__selected' : ''
}`}
>
<h3>{header}</h3>
<img className={classes} src={img} alt='img' />
<div
className={`outstaffing-block__img ${
selected ? ' outstaffing-block__border' : ''
}`}
onClick={() =>
handlePositionClick({
dispatch,
positionId,
isSelected,
onSelect,
apiRequest
})
}
>
<h3>{header}</h3>
<img className={classes} src={img} alt='img'/>
</div>
<div
className={`${
selected
? 'outstaffing-block__mobile--block'
: 'outstaffing-block__mobile--none'
}`}
>
<p className='outstaffing-block__text'># Популярный стек</p>
{dataTags && (
<ul className='outstaffing-block__items'>
{dataTags.map((item) => (
<li
key={item.id}
onClick={() => handleBlockClick(item.value, item.id)}
>
{item.value}
</li>
))}
</ul>
)}
</div>
</div>
<div
className={`${
selected
? 'outstaffing-block__mobile--block'
: 'outstaffing-block__mobile--none'
}`}
>
<p className='outstaffing-block__text'># Популярный стек</p>
{dataTags && (
<ul className='outstaffing-block__items'>
{dataTags.map((item) => (
<li
key={item.id}
onClick={() => handleBlockClick(item.value, item.id)}
>
{item.value}
</li>
))}
</ul>
)}
</div>
</div>
</OutsideClickHandler>
</OutsideClickHandler>
)
}
};
export default OutstaffingBlock

View File

@ -3,6 +3,7 @@
color: #f9f9f9;
}
.outstaffing-block {
margin-top: 60px;
display: flex;
flex-direction: column;
@ -28,6 +29,7 @@
}
&__img {
cursor: pointer;
min-width: 260px;
min-height: 120px;
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 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 (
<Route
{...rest}
render={props =>
( isAuth || isTokenAlive) ? (
// ( isAuth || isTokenAlive) ? (
<Component {...props} />
) : <Redirect to='/auth' />
// ) : <Redirect to='/auth' />
}
/>
);
}
};

View File

@ -1,185 +1,204 @@
import React, { useState } from 'react'
import { useSelector, useDispatch } from 'react-redux'
import { fetchPost } from '../../server/server'
import { useHistory, useParams, Redirect } from 'react-router-dom'
import { Loader } from '../Loader/Loader'
import { auth } from '../../redux/outstaffingSlice'
import { getRole } from '../../redux/roleSlice'
import React, {useState} from 'react'
import {useSelector} from 'react-redux'
import {Link} from 'react-router-dom'
import {Loader} from '../Loader/Loader'
import {currentMonthAndDay} from '../Calendar/calendarHelper'
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 ellipse from '../../images/ellipse.png'
import remove from '../../images/remove.png'
import addIcon from '../../images/addIcon.png'
import { currentMonthAndDayReportPage } from '../Calendar/calendarHelper'
import arrow from "../../images/right-arrow.png";
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()
return `${yyyy}-${mm}-${dd}`
}
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}`
}
};
const ReportForm = () => {
const dispatch = useDispatch()
const history = useHistory()
const role = useSelector(getRole)
const reportDate = useSelector(getReportDate);
const [isFetching, setIsFetching] = useState(false)
const [reportSuccess, setReportSuccess] = useState(false)
const {apiRequest} = useRequest();
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 [scheduledInputValue, setScheduledInputValue] = useState('');
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) => {
if (indexRemove !== 0) {
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 (
<section className='report-form'>
<div className='row'>
<div className='col-xl-12'>
<div className='report-form__block'>
<div className='report-form__block-title'>
<h2>Добавить отчет</h2>
<h3>Дата заполнения отчета:</h3>
<section className='report-form'>
<ProfileHeader/>
<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 className='report-form__block-img'>
<img
className='report-form__calendar-icon'
src={calendarIcon}
alt=''
/>
{currentMonthAndDayReportPage()}
</div>
<div className='report-form__content'>
<div className='report-form__block'>
<div className='report-form__block-title'>
<h2>Добавление отчета за день</h2>
<h3>Дата заполнения отчета:</h3>
</div>
<div className='report-form__block-img'>
<img
className='report-form__calendar-icon'
src={calendarIcon}
alt=''
/>
{/*{currentMonthAndDayReportPage()}*/}
{reportDate ? currentMonthAndDay(reportDate) : getCreatedDate()}
</div>
<div className='report-form__task-list'>
<img src={ellipse} alt=''/>
<span>Какие задачи были выполнены?</span>
</div>
</div>
<div className='report-form__task-list'>
<img src={ellipse} alt='' />
<span>Какие задачи были выполнены?</span>
<div className='row'>
<div className='col-8'>
<div className='report-form__task-header'>
<p className='report-form__task-title--description'>
Краткое описание задачи
</p>
<p className='report-form__task-title--hours'>Количество часов</p>
</div>
{inputs.map((input, index) => {
return (
<form id={'input'} key={`input__${index}`} className='report-form__task-form'>
<div className='report-form__task-number'>
{index + 1}.
</div>
<div className='report-form__task-input report-form__task-input--description'>
<input name='text' type='text' onChange={e => setInputs(inputs.map((input, inputIndex) => {
return index === inputIndex
? {
...input,
task: e.target.value
}
: input
}))}/>
</div>
<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) => {
return index === inputIndex
? {
...input,
hours_spent: Number(e.target.value)
}
: input
}))}/>
</div>
<div className='report-form__task-remove'>
<img onClick={() => deleteInput(index)} src={remove} alt=''/>
</div>
</form>
)
})}
<div className='report-form__form-add'>
<img onClick={addInput} src={addIcon} alt=''/>
<span>Добавить еще </span>
</div>
</div>
<div className='col-4'></div>
</div>
<div className='row'>
<div className='col-12'>
<div className='report-form__input-box'>
<div className='report-form__troubles'>
<img src={ellipse} alt=''/>
<span>Какие сложности возникли?</span>
</div>
<input type='text' value={troublesInputValue} onChange={e => setTroublesInputValue(e.target.value)}/>
<div className='report-form__scheduled'>
<img src={ellipse} alt=''/>
<span>Что планируется сделать завтра?</span>
</div>
<input type='text' value={scheduledInputValue} onChange={e => setScheduledInputValue(e.target.value)}/>
</div>
</div>
</div>
</div>
<div className='row'>
<div className='col-8'>
<div className='report-form__task-header'>
<p className='report-form__task-title--description'>
Краткое описание задачи
</p>
<p className='report-form__task-title--hours'>Количество часов</p>
</div>
{inputs.map((input, index) => {
return (
<form id={'input'} key={`input__${index}`} className='report-form__task-form'>
<div className='report-form__task-number'>
{index+1}.
</div>
<div className='report-form__task-input report-form__task-input--description'>
<input name='text' type='text' onChange={ e => setInputs(inputs.map( (input, inputIndex) => {
return index === inputIndex
? {
...input,
task: e.target.value
}
: input
}))} />
</div>
<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) => {
return index === inputIndex
? {
...input,
hours_spent: Number(e.target.value)
}
: input
}))} />
</div>
<div className='report-form__task-remove'>
<img onClick={() => deleteInput(index)} src={remove} alt='' />
</div>
</form>
)
})}
<div className='report-form__form-add'>
<img onClick={addInput} src={addIcon} alt='' />
<span>Добавить еще </span>
</div>
</div>
<div className='col-4'></div>
</div>
<div className='row'>
<div className='col-12'>
<div className='report-form__input-box'>
<div className='report-form__troubles'>
<img src={ellipse} alt='' />
<span>Какие сложности возникли?</span>
</div>
<input type='text' value={troublesInputValue} onChange={e => setTroublesInputValue(e.target.value)} />
<div className='report-form__scheduled'>
<img src={ellipse} alt='' />
<span>Что планируется сделать завтра?</span>
</div>
<input type='text' value={scheduledInputValue} onChange={e => setScheduledInputValue(e.target.value)} />
</div>
</div>
</div>
<div className='row'>
<div className='col-12'>
<div className='report-form__footer'>
<button className='report-form__footer-btn' onClick={() => {
fetchPost({
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>
<p className='report-form__footer-text'>
Всего за день : <span>{totalHours} часов</span>
</p>
{reportSuccess &&
<div className='row'>
<div className='col-12'>
<div className='report-form__footer'>
<button className='report-form__footer-btn' onClick={() => handler()}>
{isFetching ? <Loader/> : 'Отправить'}
</button>
<p className='report-form__footer-text'>
Всего за день : <span>{totalHours} часов</span>
</p>
{reportSuccess &&
<p className='report-form__footer-done'>Отчет отправлен</p>
}
}
</div>
</div>
</div>
</div>
</div>
</div>
</section>
<Footer/>
</section>
)
}
};
export default ReportForm

View File

@ -1,9 +1,51 @@
.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 {
margin-top: 76px;
h2 {
color: #282828;
color: #52B709;
font-family: 'GT Eesti Pro Display';
font-size: 3.3em;
font-weight: 700;

View File

@ -1,90 +1,87 @@
import React, { useState } from 'react'
import { useSelector, useDispatch } from 'react-redux'
import React, {useState} from 'react'
import {useSelector, useDispatch} from 'react-redux'
import Select from 'react-select'
import { Loader } from '../Loader/Loader'
import style from './TagSelect.module.css'
import {Loader} from '../Loader/Loader'
import {useRequest} from "../../hooks/useRequest";
import {
selectedItems,
selectItems,
selectTags,
filteredCandidates,
setPositionId,
auth
setPositionId
} from '../../redux/outstaffingSlice'
import { fetchGet } from '../../server/server'
import { useHistory } from 'react-router-dom'
import { getRole } from '../../redux/roleSlice'
import style from './TagSelect.module.css'
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 }) => {
setSearchLoading(true)
const itemsArr = useSelector(selectItems);
const tagsArr = useSelector(selectTags);
dispatch(setPositionId(null))
const filterItemsId = itemsArr.map((item) => item.id).join()
const handleSubmit = ({dispatch, setSearchLoading}) => {
setSearchLoading(true);
fetchGet({
link: `${process.env.REACT_APP_API_URL}/api/profile?skills=`,
params: filterItemsId,
history,
role,
logout: () => dispatch(auth(false))
dispatch(setPositionId(null));
const filterItemsId = itemsArr.map((item) => item.id).join();
const params = filterItemsId ? {skill: filterItemsId} : '';
apiRequest('/profile', {
params: {...params, limit: 1000},
}).then((el) => {
dispatch(filteredCandidates(el))
dispatch(filteredCandidates(el));
setSearchLoading(false)
})
// dispatch(selectedItems([]));
}
};
return (
<>
<section className={style.search}>
<div className='row'>
<div className='col-12'>
<h2 className={style.search__title}>
Найти специалиста по навыкам
</h2>
<div className={style.search__box}>
<Select
value={itemsArr}
onChange={(value) => dispatch(selectedItems(value))}
isMulti
name='tags'
className={style.select}
classNamePrefix={style.select}
options={
tagsArr &&
tagsArr.flat().map((item) => {
return {
id: item.id,
value: item.value,
label: item.value
<>
<section className={style.search}>
<div className='row'>
<div className='col-12'>
<h2 className={style.search__title}>
Найти специалиста по навыкам
</h2>
<div className={style.search__box}>
<Select
value={itemsArr}
onChange={(value) => {console.log(value) ;return dispatch(selectedItems(value))}}
isMulti
name='tags'
className={style.select}
classNamePrefix={style.select}
options={
tagsArr &&
tagsArr.flat().map((item) => {
return {
id: item.id,
value: item.value,
label: item.value
}
})
}
})
}
/>
<button
onClick={() => handleSubmit({ dispatch, setSearchLoading })}
type='submit'
className={style.search__submit}
>
{searchLoading ? <Loader width={30} height={30} /> : 'Поиск'}
</button>
/>
<button
onClick={() => handleSubmit({dispatch, setSearchLoading})}
type='submit'
className={style.search__submit}
>
{searchLoading ? <Loader width={30} height={30}/> : 'Поиск'}
</button>
</div>
</div>
</div>
</div>
</section>
</>
</section>
</>
)
}
};
export default TagSelect

View File

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

View File

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

View File

@ -6,7 +6,7 @@ import {selectedTest, selectUserInfo} from "../../../redux/quizSlice";
export const HeaderPageTestsQuiz = ({isVisibilityButton}) => {
const test = useSelector(selectedTest)
const test = useSelector(selectedTest);
const userInfo = useSelector(selectUserInfo);
return (
@ -23,8 +23,8 @@ export const HeaderPageTestsQuiz = ({isVisibilityButton}) => {
</div>
</div>
{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>
)
}
};

View File

@ -1,50 +1,46 @@
import {useEffect} from 'react'
import {useDispatch} from 'react-redux'
import {useSelector} from 'react-redux'
import {fetchGet} from '../../../server/server'
import React, {useEffect} from 'react'
import {useDispatch, useSelector} from 'react-redux'
import {selectUserInfo, setQuestionnairesList, setUserInfo} from "../../../redux/quizSlice";
import {useRequest} from "../../../hooks/useRequest";
import './quiz.scss'
import {selectUserInfo, setQuestionnairesList, setUserInfo,} from "../../../redux/quizSlice";
export const HeaderQuiz = ({header}) => {
const dispatch = useDispatch()
const userId = localStorage.getItem('id');
const userInfo = useSelector(selectUserInfo);
const dispatch = useDispatch();
const userId = localStorage.getItem('id');
const userInfo = useSelector(selectUserInfo);
useEffect(() => {
dispatch(setUserInfo(userId))
}, [dispatch])
const {apiRequest} = useRequest();
useEffect(() => {
fetchGet({
link: `${process.env.REACT_APP_API_URL}/api/user-questionnaire/questionnaires-list?user_id=${userId}`,
Origin: `${process.env.REACT_APP_BASE_URL}`,
}
).then(response => {
dispatch(setQuestionnairesList(response))
})
}, [dispatch])
useEffect(() => {
dispatch(setUserInfo(userId))
}, [dispatch]);
return (
<div>
{ userInfo?.status === 500 ? <div className="error-msg">{userInfo.message}</div> :
<div className="header-quiz">
<div className="header-quiz__container">
{!userInfo ? <h2>Loading...</h2> :
<>
{header && <h2 className={'header-quiz__title-main'}>Добрый день, {userInfo.fio}</h2>}
<div className="header-quiz__body header-quiz__body_interjacent">
<div className="header-quiz__avatar">
useEffect(() => {
apiRequest(`/user-questionnaire/questionnaires-list?user_id=${userId}`)
.then(res => dispatch(setQuestionnairesList(res)))
}, [dispatch]);
return (
<div>
{userInfo?.status === 500 ? <div className="error-msg">{userInfo.message}</div> :
<div className="header-quiz">
<div className="header-quiz__container">
{!userInfo ? <h2>Loading...</h2> :
<>
{header && <h2 className={'header-quiz__title-main'}>Добрый день, {userInfo.fio}</h2>}
<div className="header-quiz__body header-quiz__body_interjacent">
<div className="header-quiz__avatar">
<img src={userInfo.photo} alt={userInfo.photo}/>
</div>
<div className="header-quiz__name-user">{userInfo.fio}</div>
<div className="header-quiz__title">{userInfo.position_name}</div>
</div>
</>
}
</div>
<div className="header-quiz__name-user">{userInfo.fio}</div>
<div className="header-quiz__title">{userInfo.position_name}</div>
</div>
</>
}
</div>
</div>
</div>
}
</div>
)
}
}
</div>
)
};

View File

@ -1,27 +1,28 @@
import {Link} from 'react-router-dom'
import {CodeSnippetlighter} from '../../../pages/CodeSnippetPage'
import comment from './../../../images/comment.jpg'
import './quiz.scss'
import {useEffect, useState} from "react";
import {useSelector} from "react-redux";
import {selectedTest} from "../../../redux/quizSlice";
import {fetchGet} from "../../../server/server";
import {useRequest} from "../../../hooks/useRequest";
export const Instruction = () => {
const [countQuestions, setCountQuestions] = useState(null)
const test = useSelector(selectedTest)
const [countQuestions, setCountQuestions] = useState(null);
const test = useSelector(selectedTest);
useEffect(async () => {
const {apiRequest} = useRequest();
const response = await fetchGet({
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}`,
}
)
setCountQuestions(response.question_number)
useEffect( () => {
}, [])
apiRequest('/user-questionnaire/get-question-number', {
params: {user_questionnaire_uuid: test.uuid},
}
).then((res)=> setCountQuestions(res.question_number))
}, []);
return (
<div className="instruction">
@ -36,7 +37,7 @@ export const Instruction = () => {
e
lit, sed do eiusmod tempo
</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__icon">
<img src={comment} alt=""/>
@ -49,5 +50,5 @@ export const Instruction = () => {
</div>
</div>
)
}
};

View File

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

View File

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

View File

@ -1,37 +1,35 @@
import React, {useEffect, useState} from 'react';
import {useDispatch, useSelector} from "react-redux";
import {fetchResultTest, selectedTest, selectResult} from "../../../redux/quizSlice";
import {fetchGet} from "../../../server/server";
import {useRequest} from "../../../hooks/useRequest";
export const Results = () => {
const result = useSelector(selectResult)
const test = useSelector(selectedTest)
const [maxScore, setMaxScore] = useState('')
const dispatch = useDispatch()
const result = useSelector(selectResult);
const test = useSelector(selectedTest);
const [maxScore, setMaxScore] = useState('');
const dispatch = useDispatch();
const {apiRequest} = useRequest();
useEffect(async () => {
dispatch(fetchResultTest(test.uuid))
const response = await fetchGet({
link: `${process.env.REACT_APP_API_URL}/api/user-questionnaire/get-points-number?user_questionnaire_uuid=${test.uuid}`,
Origin: `${process.env.REACT_APP_BASE_URL}`,
}
)
setMaxScore(response.sum_point)
}, [])
useEffect(() => {
dispatch(fetchResultTest(test.uuid));
apiRequest(`/user-questionnaire/get-points-number?user_questionnaire_uuid=${test.uuid}`)
.then((res) => setMaxScore(res.sum_point));
return (
<div className={'result _container'}>
}, [apiRequest, dispatch, test]);
return (
<div className={'result _container'}>
{
!result ? <h1 style={{display: "block"}}>Ожидайте результата...</h1> :
!result ? <h1 style={{display: "block"}}>Ожидайте результата...</h1> :
<div className="result__body">
<div className="result__body">
<div className="result__text">Благодарим за прохождение теста</div>
<div className="result__text">Ваш Результат: <span
className="result__score">{result.score}</span> из {maxScore} </div>
</div>
className="result__score">{result.score}</span> из {maxScore} </div>
</div>
}
</div>
);
</div>
);
};

View File

@ -1,149 +1,149 @@
import React, {useEffect} from 'react'
import {useHistory} from "react-router"
import {CodeSnippetlighter} from '../../../pages/CodeSnippetPage'
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 React, {useEffect, useState} from 'react'
import {useNavigate} from "react-router-dom"
import {useSelector, useDispatch} from 'react-redux'
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 = () => {
const history = useHistory();
const dispatch = useDispatch()
const listAnswers = useSelector(selectAnswer)
const dataTest = useSelector(selectedTest)
const [index, setIndex] = useState(0);
const [checkedValues, setCheckedValues] = useState([])
const [stripValue, setStripValue] = useState(0);
const [inputValue, setInputValue] = useState('')
const id = localStorage.getItem('id');
const [questions, setQuestions] = useState([])
const navigate = useNavigate();
const dispatch = useDispatch();
useEffect(async () => {
const response = await fetchGet({
link: `${process.env.REACT_APP_API_URL}/api/question/get-questions?uuid=${dataTest.uuid}`,
Origin: `${process.env.REACT_APP_BASE_URL}`,
}
)
setQuestions(response)
dispatch(fetchGetAnswers(response[0].id))
setStripValue((+index + 1) * 100 / response.length)
}, [dispatch])
const listAnswers = useSelector(selectAnswer);
const dataTest = useSelector(selectedTest);
const [index, setIndex] = useState(0);
const [checkedValues, setCheckedValues] = useState([]);
const [stripValue, setStripValue] = useState(0);
const [inputValue, setInputValue] = useState('');
const [questions, setQuestions] = useState([]);
const nextQuestion = async (e) => {
e.preventDefault()
const {apiRequest} = useRequest();
//Проверка на валидацию ответов
if (checkedValues.length || inputValue) {
switch (questions[index].question_type_id) {
case '3':
dispatch(fetchUserAnswersMany(checkedValues))
break;
case '2':
case '1':
case '4':
dispatch(fetchUserAnswerOne(checkedValues))
break;
default:
break;
}
const id = localStorage.getItem('id');
//Проверка на существование следующего запроса
if (index < questions.length - 1) {
await dispatch(fetchGetAnswers(questions[index + 1].id))
setIndex(prev => prev >= questions.length - 1 ? prev : prev + 1)
setStripValue((prev => prev + (100 / questions.length)))
setCheckedValues([]);
setInputValue('')
} else {
history.push(`/quiz-result`)
alert("Тест пройден!")
}
useEffect(() => {
apiRequest(`/question/get-questions?uuid=${dataTest.uuid}`)
.then((response) => {
console.log(response)
setQuestions(response);
dispatch(fetchGetAnswers(response[0].id));
setStripValue((+index + 1) * 100 / response.length)
})
}, [dispatch]);
} else {
alert("Вы не ответили на вопрос")
}
}
const nextQuestion = async (e) => {
e.preventDefault();
const handleChange = (e) => {
const checked = e.target.checked;
//Проверка на валидацию ответов
if (checkedValues.length || inputValue) {
switch (questions[index].question_type_id) {
case '3':
checked ? setCheckedValues(prev => [...prev, {
user_id: id,
user_questionnaire_uuid: dataTest.uuid,
question_id: questions[index].id,
response_body: e.target.value
}]) :
setCheckedValues(prev => [...prev.filter(item => item.response_body !== e.target.value)])
break
case '1':
case '2':
case '4':
setCheckedValues([{
user_id: id,
user_questionnaire_uuid: dataTest.uuid,
question_id: questions[index].id,
response_body: e.target.value
}])
case '3':
dispatch(fetchUserAnswersMany(checkedValues));
break;
case '2':
case '1':
case '4':
dispatch(fetchUserAnswerOne(checkedValues));
break;
default:
break;
}
};
//Проверка на существование следующего запроса
if (index < questions.length - 1) {
await dispatch(fetchGetAnswers(questions[index + 1].id));
setIndex(prev => prev >= questions.length - 1 ? prev : prev + 1);
setStripValue((prev => prev + (100 / questions.length)));
setCheckedValues([]);
setInputValue('')
} else {
navigate(`/quiz/result`);
alert("Тест пройден!")
}
} else {
alert("Вы не ответили на вопрос")
}
};
const handleChange = (e) => {
const checked = e.target.checked;
switch (questions[index].question_type_id) {
case '3':
checked ? setCheckedValues(prev => [...prev, {
user_id: id,
user_questionnaire_uuid: dataTest.uuid,
question_id: questions[index].id,
response_body: e.target.value
}]) :
setCheckedValues(prev => [...prev.filter(item => item.response_body !== e.target.value)]);
break;
case '1':
case '2':
case '4':
setCheckedValues([{
user_id: id,
user_questionnaire_uuid: dataTest.uuid,
question_id: questions[index].id,
response_body: e.target.value
}])
}
};
return (
<React.StrictMode>
return (
<React.StrictMode>
<Progressbar indexQuestion={index + 1} width={stripValue}/>
<div className="task">
{!questions.length || !stripValue || !listAnswers.length ?
<h1 className={'_container'} style={{display: "block"}}>Loading....</h1>
:
<div className="task__container">
{!questions.length || !stripValue || !listAnswers.length ?
<h1 className={'_container'} style={{display: "block"}}>Loading....</h1>
:
<div className="task__container">
<div className="task__code code">
{/* <CodeSnippetlighter /> */}
{/* <CodeSnippetlighter /> */}
</div>
<h3 className="task__title quiz-title_h3">{questions[index].question_body}</h3>
<div className="task__body">
<form className='task__form form-task'>
{
questions[index].question_type_id === 1 ?
<GetOptionTask
type={1}
inputValue={checkedValues.length ? checkedValues[0].response_body : ''}
handleChange={handleChange}
/>
:
listAnswers.map((answer) => (
<GetOptionTask
key={answer.id}
type={questions[index].question_type_id}
handleChange={handleChange}
answer={answer}
/>
))
}
<div className="form-task__buttons">
{questions.length !== index + 1 &&
<button type='submit' className='quiz-btn'
onClick={(e) => nextQuestion(e)}>Далее</button>}
{questions.length === index + 1 && <button onClick={(e) => nextQuestion(e)}
className='quiz-btn quiz-btn_dark-green'>Завершить</button>}
</div>
</form>
<form className='task__form form-task'>
{
questions[index].question_type_id === 1 ?
<GetOptionTask
type={1}
inputValue={checkedValues.length ? checkedValues[0].response_body : ''}
handleChange={handleChange}
/>
:
listAnswers.map((answer) => (
<GetOptionTask
key={answer.id}
type={questions[index].question_type_id}
handleChange={handleChange}
answer={answer}
/>
))
}
<div className="form-task__buttons">
{questions.length !== index + 1 &&
<button type='submit' className='quiz-btn'
onClick={(e) => nextQuestion(e)}>Далее</button>}
{questions.length === index + 1 &&
<button onClick={(e) => nextQuestion(e)}
className='quiz-btn quiz-btn_dark-green'>Завершить</button>}
</div>
</form>
</div>
</div>
}
</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-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 { AuthBox } from '../AuthBox/AuthBox'
import { AuthBox } from '../../components/AuthBox/AuthBox'
import { useSelector } from 'react-redux'
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 { selectAuth } from '../../redux/outstaffingSlice'
import { Redirect } from 'react-router-dom'
import { Footer } from '../Footer/Footer'
import { useNavigate} from 'react-router-dom'
import { Footer } from '../../components/Footer/Footer'
import './authForDevelopers.scss'
const AuthForDevelopers = () => {
const isAuth = useSelector(selectAuth)
const isAuth = useSelector(selectAuth);
let navigate = useNavigate();
if (isAuth) {
return <Redirect to='/report' />
navigate('/profile')
}
return (
@ -89,6 +91,6 @@ const AuthForDevelopers = () => {
</div>
</section>
)
}
};
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 { useSelector } from 'react-redux'
import { selectAuth } from '../../redux/outstaffingSlice'
import { Redirect } from 'react-router-dom'
import { useNavigate} from 'react-router-dom'
import { Footer } from '../Footer/Footer'
import { AuthBox } from '../AuthBox/AuthBox'
import { Footer } from '../../components/Footer/Footer'
import { AuthBox } from '../../components/AuthBox/AuthBox'
import './authForPartners.scss'
const AuthForPartners = () => {
const isAuth = useSelector(selectAuth)
const isAuth = useSelector(selectAuth);
let navigate = useNavigate();
if (isAuth) {
return <Redirect to='/' />
navigate('/')
}
return (
@ -81,6 +82,6 @@ const AuthForPartners = () => {
</div>
</section>
)
}
};
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 { useHistory } from 'react-router';
import { useNavigate } from 'react-router-dom';
import { WithLogout } from '../hoc/withLogout';
import Calendar from '../components/Calendar/Calendar';
const CalendarPage = () => {
const history = useHistory();
return <WithLogout><Calendar onSelect={() => { history.push('/report/0') }} /></WithLogout>;
const navigate = useNavigate();
return <WithLogout><Calendar onSelect={() => { navigate('/report/0') }} /></WithLogout>;
};
export default CalendarPage;

View File

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

View File

@ -22,7 +22,7 @@ const tasks = [
text: 'Задача «83 Навигационная система Поиск по почтовому индексу Добавить экран поиска по почтовому индексу» не может быть завершена, т.к. работа над задачей «82 Навигационная система Разработать модуль поиска по почтовому индексу» ещё не начата',
hours: 3
}
]
];
const SingleReportPage = () => {
return (
@ -30,7 +30,7 @@ const SingleReportPage = () => {
<div className='single-report-page'>
<div className='single-report-page__back'>
<div className='single-report-page__back-arrow'>
<img src={arrowLeft} />
<img src={arrowLeft} alt='arrowLeft'/>
</div>
<div className='single-report-page__back-text'>
Вернуться к списку
@ -103,6 +103,6 @@ const SingleReportPage = () => {
</div>
</WithLogout>
)
}
};
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 { HeaderPageTestsQuiz } from "../../components/features/quiz/HeaderPageTests"
import { Instruction } from "../../components/features/quiz/Instructions"
import {useNavigate} from "react-router-dom"
import {HeaderPageTestsQuiz} from "../../components/features/quiz/HeaderPageTests"
import {Instruction} from "../../components/features/quiz/Instructions"
import React from "react";
import {useSelector} from "react-redux";
import {selectedTest} from "../../redux/quizSlice";
@ -8,16 +8,17 @@ import {selectedTest} from "../../redux/quizSlice";
export const InstructionPage = () => {
const test = useSelector(selectedTest)
const test = useSelector(selectedTest)
if(!test){
return <Redirect to={'/quiz'} />
}
let navigate = useNavigate();
if (!test) {
navigate('/quiz')
}
return (
<>
<HeaderPageTestsQuiz isVisibilityButton={false}/>
<Instruction />
</>
)
}
return (
<>
<HeaderPageTestsQuiz isVisibilityButton={false}/>
<Instruction/>
</>
)
};

View File

@ -1,24 +1,26 @@
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 {MyTestsQuiz} from "../../components/features/quiz/MyTestsQuiz"
import {useSelector} from "react-redux";
import {selectedTest, selectPassedTests} from "../../redux/quizSlice";
import React from "react";
export const InterjacentPage = () => {
const test = useSelector(selectedTest)
const passedTests = useSelector(selectPassedTests)
const test = useSelector(selectedTest);
const passedTests = useSelector(selectPassedTests)
let navigate = useNavigate();
if (!test) {
navigate('/quiz')
}
if (!test) {
return <Redirect to={'/quiz'}/>
}
return (
<>
return (
<>
<HeaderPageTestsQuiz isVisibilityButton={true}/>
<MyTestsQuiz listTests={passedTests}/>
</>
)
}
</>
)
};

View File

@ -1,22 +1,21 @@
import {Link, Redirect} from 'react-router-dom'
import {useNavigate} from 'react-router-dom'
import {HeaderPageTestsQuiz} from '../../components/features/quiz/HeaderPageTests'
import {Progressbar} from '../../components/features/quiz/ProgressbarQuiz'
import {TaskQuiz} from '../../components/features/quiz/Task'
import {useSelector} from "react-redux";
import {selectedTest} from "../../redux/quizSlice";
import React from "react";
export const QuizTestPage = () => {
let navigate = useNavigate()
const test = useSelector(selectedTest)
const test = useSelector(selectedTest)
if (!test) {
return <Redirect to={'/quiz'}/>
}
return (
<>
if (!test) {
navigate('/quiz')
}
return (
<>
<HeaderPageTestsQuiz isVisibilityButton={false}/>
<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 {Results} from "../../components/features/quiz/Results";
import {useSelector} from "react-redux";
@ -8,16 +8,17 @@ import React from "react";
export const ResultPage = () => {
const test = useSelector(selectedTest)
const test = useSelector(selectedTest)
if (!test) {
return <Redirect to={'/quiz'}/>
}
let navigate = useNavigate();
if (!test) {
navigate('/quiz')
}
return (
<>
return (
<>
<HeaderPageTestsQuiz isVisibilityButton={false}/>
<Results/>
</>
)
}
</>
)
};

View File

@ -8,6 +8,8 @@ const initialState = {
currentCandidate: {},
auth: false,
positionId: null,
profileInfo: {},
reportsDates: ''
};
export const outstaffingSlice = createSlice({
@ -37,11 +39,17 @@ export const outstaffingSlice = createSlice({
},
setUserInfo: (state, action) => {
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 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 selectAuth = (state) => state.outstaffing.auth;
export const getPositionId = (state) => state.outstaffing.positionId;
export const getProfileInfo = (state) => state.outstaffing.profileInfo;
export const selectUserInfo = (state) => state.outstaffing.userInfo;
export const getReportsDates = (state) => state.outstaffing.reportsDates;
export default outstaffingSlice.reducer;

View File

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

View File

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

View File

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

View File

@ -1,84 +1,5 @@
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) => {
try {
@ -87,31 +8,11 @@ export const fetchGet = withAuthRedirect(async (link) => {
headers: {
Authorization: `Bearer ${localStorage.getItem('auth_token')}`
}
})
let data = await response.json()
});
let data = await response.json();
return data
} catch (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)
}
})
});