@ -1,2 +1,2 @@
|
|||||||
REACT_APP_API_URL = https://dev.itguild.info
|
REACT_APP_API_URL = https://dev.itguild.info/api
|
||||||
REACT_APP_BASE_URL = https://dev.itguild.info
|
REACT_APP_BASE_URL = https://dev.itguild.info/api
|
29165
package-lock.json
generated
@ -12,7 +12,7 @@
|
|||||||
"@testing-library/user-event": "^12.8.3",
|
"@testing-library/user-event": "^12.8.3",
|
||||||
"@typescript-eslint/eslint-plugin": "^4.5.0",
|
"@typescript-eslint/eslint-plugin": "^4.5.0",
|
||||||
"@typescript-eslint/parser": "^4.5.0",
|
"@typescript-eslint/parser": "^4.5.0",
|
||||||
"axios": "^0.21.1",
|
"axios": "^0.24.0",
|
||||||
"babel-eslint": "^10.1.0",
|
"babel-eslint": "^10.1.0",
|
||||||
"babel-jest": "^26.6.0",
|
"babel-jest": "^26.6.0",
|
||||||
"babel-loader": "8.1.0",
|
"babel-loader": "8.1.0",
|
||||||
@ -65,7 +65,7 @@
|
|||||||
"react-phone-input-2": "^2.14.0",
|
"react-phone-input-2": "^2.14.0",
|
||||||
"react-redux": "^7.2.4",
|
"react-redux": "^7.2.4",
|
||||||
"react-refresh": "^0.8.3",
|
"react-refresh": "^0.8.3",
|
||||||
"react-router-dom": "^5.2.0",
|
"react-router-dom": "^6.2.1",
|
||||||
"react-select": "^4.3.1",
|
"react-select": "^4.3.1",
|
||||||
"react-syntax-highlighter": "^15.4.5",
|
"react-syntax-highlighter": "^15.4.5",
|
||||||
"react-yandex-metrika": "^2.6.0",
|
"react-yandex-metrika": "^2.6.0",
|
||||||
@ -84,7 +84,8 @@
|
|||||||
"webpack": "4.44.2",
|
"webpack": "4.44.2",
|
||||||
"webpack-dev-server": "3.11.1",
|
"webpack-dev-server": "3.11.1",
|
||||||
"webpack-manifest-plugin": "2.2.0",
|
"webpack-manifest-plugin": "2.2.0",
|
||||||
"workbox-webpack-plugin": "5.1.4"
|
"workbox-webpack-plugin": "5.1.4",
|
||||||
|
"react-router": "latest"
|
||||||
},
|
},
|
||||||
"scripts": {
|
"scripts": {
|
||||||
"start": "node scripts/start.js",
|
"start": "node scripts/start.js",
|
||||||
|
@ -1,5 +1,5 @@
|
|||||||
<!DOCTYPE html>
|
<!DOCTYPE html>
|
||||||
<html lang="en">
|
<html lang="en" style="height: 100%">
|
||||||
<head>
|
<head>
|
||||||
<meta charset="utf-8" />
|
<meta charset="utf-8" />
|
||||||
<meta name="viewport" content="width=device-width, initial-scale=1" />
|
<meta name="viewport" content="width=device-width, initial-scale=1" />
|
||||||
@ -14,7 +14,7 @@
|
|||||||
<link rel="shortcut icon" href="favicon.ico" />
|
<link rel="shortcut icon" href="favicon.ico" />
|
||||||
</head>
|
</head>
|
||||||
|
|
||||||
<body>
|
<body style="min-height: 100%">
|
||||||
<!-- <noscript>You need to enable JavaScript to run this app.</noscript> -->
|
<!-- <noscript>You need to enable JavaScript to run this app.</noscript> -->
|
||||||
<div id="root"></div>
|
<div id="root"></div>
|
||||||
<!-- <script type="text/javascript">
|
<!-- <script type="text/javascript">
|
||||||
|
102
src/App.js
@ -1,66 +1,66 @@
|
|||||||
import React from 'react'
|
import React from 'react'
|
||||||
import { BrowserRouter as Router, Route, Switch } from 'react-router-dom'
|
import {BrowserRouter as Router, Route, Routes, Navigate} from 'react-router-dom';
|
||||||
import 'bootstrap/dist/css/bootstrap.min.css'
|
|
||||||
import './fonts/stylesheet.css'
|
|
||||||
|
|
||||||
import { ProtectedRoute } from './components/ProtectedRoute/ProtectedRoute'
|
|
||||||
|
|
||||||
import AuthPageForDevelopers from './pages/AuthPageForDevelopers'
|
import AuthForPartners from "./pages/AuthForPartners/AuthForPartners";
|
||||||
import AuthPageForPartners from './pages/AuthPageForPartners'
|
import AuthForDevelopers from "./pages/AuthForDevelopers/AuthForDevelopers";
|
||||||
import HomePage from './pages/HomePage'
|
import HomePage from './pages/HomePage'
|
||||||
import CandidatePage from './pages/CandidatePage'
|
import CandidatePage from './pages/CandidatePage'
|
||||||
import CalendarPage from './pages/CalendarPage'
|
import CalendarPage from './pages/CalendarPage'
|
||||||
import ReportPage from './pages/ReportFormPage.js'
|
import ReportPage from './pages/ReportFormPage.js'
|
||||||
|
import ProfileCalendarPage from './pages/ProfileCalendarPage.js'
|
||||||
import FormPage from './pages/FormPage.js'
|
import FormPage from './pages/FormPage.js'
|
||||||
import SingleReportPage from './pages/SingleReportPage'
|
import SingleReportPage from './pages/SingleReportPage'
|
||||||
import { QuizPage } from './pages/quiz/QuizPage'
|
import {QuizPage} from './pages/quiz/QuizPage'
|
||||||
import { InterjacentPage } from './pages/quiz/InterjacentPage'
|
import {InterjacentPage} from './pages/quiz/InterjacentPage'
|
||||||
import { QuizTestPage } from './pages/quiz/QuizTestPage'
|
import {QuizTestPage} from './pages/quiz/QuizTestPage'
|
||||||
import { InstructionPage } from './pages/quiz/InstructionPage'
|
import {InstructionPage} from './pages/quiz/InstructionPage'
|
||||||
import { ResultPage } from './pages/quiz/ResultPage'
|
import {ResultPage} from './pages/quiz/ResultPage'
|
||||||
|
import {Profile} from './pages/Profile/Profile.js'
|
||||||
|
import {Summary} from './pages/Summary/Summary'
|
||||||
|
|
||||||
|
import './fonts/stylesheet.css'
|
||||||
|
import 'bootstrap/dist/css/bootstrap.min.css'
|
||||||
|
|
||||||
|
|
||||||
const App = () => {
|
const App = () => {
|
||||||
return (
|
return (
|
||||||
<>
|
<>
|
||||||
<h1>IT Аутстаффинг в России</h1>
|
<h1>IT Аутстаффинг в России</h1>
|
||||||
<Router>
|
<Router>
|
||||||
<Switch>
|
|
||||||
<Route path='/authdev' exact>
|
<Routes>
|
||||||
<AuthPageForDevelopers />
|
<Route exact path='/' element={<HomePage/>}/>
|
||||||
</Route>
|
|
||||||
<Route path='/auth' exact>
|
<Route exact path='/authdev' element={<AuthForDevelopers/>}/>
|
||||||
<AuthPageForPartners />
|
<Route exact path='/auth' element={<AuthForPartners/>}/>
|
||||||
</Route>
|
|
||||||
<ProtectedRoute exact path='/' component={HomePage} />
|
<Route exact path='/candidate/:id' element={<CandidatePage/>}/>
|
||||||
<ProtectedRoute
|
<Route exact path='/candidate/:id/form' element={<FormPage/>}/>
|
||||||
exact
|
<Route path='/:userId/calendar' element={<CalendarPage/>}/>
|
||||||
path='/candidate/:id'
|
|
||||||
component={CandidatePage}
|
<Route exact path='/report' element={<ReportPage/>}/>
|
||||||
/>
|
<Route path='/report/:id' element={<SingleReportPage/>}/>
|
||||||
<ProtectedRoute path='/:userId/calendar' component={CalendarPage} />
|
|
||||||
<ProtectedRoute
|
<Route exact path='quiz'>
|
||||||
exact
|
<Route index element={<QuizPage/>}/>
|
||||||
path='/candidate/:id/form'
|
<Route exact path='interjacent' element={<InterjacentPage/>}/>
|
||||||
component={FormPage}
|
<Route exact path='test' element={<QuizTestPage/>}/>
|
||||||
/>
|
<Route exact path='instruction' element={<InstructionPage/>}/>
|
||||||
<ProtectedRoute exact path='/report' component={ReportPage} />
|
<Route exact path='result' element={<ResultPage/>}/>
|
||||||
<ProtectedRoute path='/report/:id' component={SingleReportPage} />
|
</Route>
|
||||||
<ProtectedRoute path='/quiz' component={QuizPage} />
|
|
||||||
<ProtectedRoute
|
<Route exact path='profile'>
|
||||||
path='/quiz-interjacent'
|
<Route index element={<Profile/>}/>
|
||||||
component={InterjacentPage}
|
<Route exact path='calendar' element={<ProfileCalendarPage/>}/>
|
||||||
/>
|
<Route exact path='summary' element={<Summary/>}/>
|
||||||
<ProtectedRoute path='/quiz-test' component={QuizTestPage} />
|
</Route>
|
||||||
<ProtectedRoute
|
|
||||||
path='/quiz-instruction'
|
<Route path="*" element={<Navigate to="/" replace/>}/>
|
||||||
component={InstructionPage}
|
</Routes>
|
||||||
/>
|
</Router>
|
||||||
<ProtectedRoute path='/quiz-result' component={ResultPage} />
|
</>
|
||||||
<ProtectedRoute component={() => <div>Page not found</div>} />
|
|
||||||
</Switch>
|
|
||||||
</Router>
|
|
||||||
</>
|
|
||||||
)
|
)
|
||||||
}
|
};
|
||||||
|
|
||||||
export default App
|
export default App
|
||||||
|
48
src/api/request.js
Normal 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;
|
@ -6,7 +6,7 @@ export const Achievement = ({ achievement }) => {
|
|||||||
return (
|
return (
|
||||||
<div className='achievement'>
|
<div className='achievement'>
|
||||||
<div className='achievement__icon'>
|
<div className='achievement__icon'>
|
||||||
<img src={achievement.img} />
|
<img src={achievement.img} alt='achievement' />
|
||||||
</div>
|
</div>
|
||||||
<div className='achievement__popup'>
|
<div className='achievement__popup'>
|
||||||
<div className='achievement__title'>{achievement.title}</div>
|
<div className='achievement__title'>{achievement.title}</div>
|
||||||
|
@ -1,107 +1,129 @@
|
|||||||
import React, { useState } from 'react'
|
import React, {useState} from 'react'
|
||||||
import { Link } from 'react-router-dom'
|
import {Link} from 'react-router-dom'
|
||||||
import { useDispatch, useSelector } from 'react-redux'
|
import {useDispatch, useSelector} from 'react-redux'
|
||||||
|
import {withSwalInstance} from 'sweetalert2-react'
|
||||||
|
import swal from 'sweetalert2'
|
||||||
|
|
||||||
|
import {Loader} from '../Loader/Loader'
|
||||||
|
|
||||||
|
import {auth, setUserInfo} from '../../redux/outstaffingSlice'
|
||||||
|
import {loading} from '../../redux/loaderSlice'
|
||||||
|
import {setRole} from '../../redux/roleSlice'
|
||||||
|
|
||||||
|
import {selectIsLoading} from '../../redux/loaderSlice'
|
||||||
|
|
||||||
import { auth, setUserInfo } from '../../redux/outstaffingSlice'
|
|
||||||
import { loading } from '../../redux/loaderSlice'
|
|
||||||
import ellipse from '../../images/ellipse.png'
|
import ellipse from '../../images/ellipse.png'
|
||||||
|
|
||||||
import { Loader } from '../Loader/Loader'
|
|
||||||
|
|
||||||
import { fetchAuth } from '../../server/server'
|
|
||||||
import { setRole } from '../../redux/roleSlice'
|
|
||||||
import { selectIsLoading } from '../../redux/loaderSlice'
|
|
||||||
|
|
||||||
import './authBox.scss'
|
import './authBox.scss'
|
||||||
|
import {useRequest} from "../../hooks/useRequest";
|
||||||
|
|
||||||
import { withSwalInstance } from 'sweetalert2-react'
|
|
||||||
import swal from 'sweetalert2'
|
|
||||||
const SweetAlert = withSwalInstance(swal)
|
|
||||||
|
|
||||||
export const AuthBox = ({ title, altTitle, roleChangeLink }) => {
|
const SweetAlert = withSwalInstance(swal);
|
||||||
const dispatch = useDispatch()
|
|
||||||
|
|
||||||
const isLoading = useSelector(selectIsLoading)
|
export const AuthBox = ({title, altTitle, roleChangeLink}) => {
|
||||||
|
const dispatch = useDispatch();
|
||||||
|
|
||||||
const [username, setUsername] = useState('')
|
const {apiRequest} = useRequest();
|
||||||
const [password, setPassword] = useState('')
|
|
||||||
const [error, setError] = useState(null)
|
const isLoading = useSelector(selectIsLoading);
|
||||||
|
|
||||||
|
const [username, setUsername] = useState('');
|
||||||
|
const [password, setPassword] = useState('');
|
||||||
|
const [error, setError] = useState(null);
|
||||||
|
|
||||||
|
const submitHandler = () => {
|
||||||
|
|
||||||
|
if (!isLoading) {
|
||||||
|
dispatch(loading(true));
|
||||||
|
apiRequest('/user/login',
|
||||||
|
{
|
||||||
|
method: 'POST',
|
||||||
|
data: JSON.stringify({
|
||||||
|
username,
|
||||||
|
password
|
||||||
|
})
|
||||||
|
}).then((res) => {
|
||||||
|
|
||||||
|
if (!res.access_token) {
|
||||||
|
|
||||||
|
setError('Некорректные данные для входа');
|
||||||
|
dispatch(loading(false))
|
||||||
|
|
||||||
|
} else {
|
||||||
|
|
||||||
|
localStorage.setItem('auth_token', res.access_token);
|
||||||
|
localStorage.setItem('id', res.id);
|
||||||
|
localStorage.setItem('cardId', res.card_id);
|
||||||
|
localStorage.setItem(
|
||||||
|
'access_token_expired_at',
|
||||||
|
res.access_token_expired_at
|
||||||
|
);
|
||||||
|
dispatch(auth(true));
|
||||||
|
dispatch(setUserInfo(res));
|
||||||
|
dispatch(loading(false));
|
||||||
|
dispatch(setRole('ROLE_PARTNER'))
|
||||||
|
}
|
||||||
|
})
|
||||||
|
}
|
||||||
|
};
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<div className='auth-box'>
|
<div className='auth-box'>
|
||||||
<h2 className='auth-box__header'>
|
<h2 className='auth-box__header'>
|
||||||
Войти в <span>систему</span>
|
Войти в <span>систему</span>
|
||||||
</h2>
|
</h2>
|
||||||
<div className='auth-box__title'>
|
<div className='auth-box__title'>
|
||||||
<img src={ellipse} alt='' />
|
<img src={ellipse} alt=''/>
|
||||||
<span>{title}</span>
|
<span>{title}</span>
|
||||||
</div>
|
|
||||||
<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>
|
</div>
|
||||||
</form>
|
<form className='auth-box__form'>
|
||||||
</div>
|
<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>
|
||||||
)
|
)
|
||||||
}
|
};
|
||||||
|
@ -1,92 +1,64 @@
|
|||||||
import React, { useEffect, useState } from 'react'
|
import React, {useEffect, useState} from 'react'
|
||||||
import { useDispatch, useSelector } from 'react-redux'
|
import {useSelector} from 'react-redux'
|
||||||
import { selectCurrentCandidate, auth } from '../../redux/outstaffingSlice'
|
import {selectCurrentCandidate} from '../../redux/outstaffingSlice'
|
||||||
import { Link, useHistory, useParams } from 'react-router-dom'
|
import {Link} from 'react-router-dom'
|
||||||
import calendarMale from '../../images/medium_male.png'
|
|
||||||
import rectangle from '../../images/rectangle_secondPage.png'
|
|
||||||
import CalendarComponent from './CalendarComponent'
|
|
||||||
import { currentMonth } from './calendarHelper'
|
|
||||||
import { Footer } from '../Footer/Footer'
|
|
||||||
|
|
||||||
import { fetchReportList } from '../../server/server'
|
import CalendarComponent from './CalendarComponent'
|
||||||
import { getRole } from '../../redux/roleSlice'
|
import {currentMonth} from './calendarHelper'
|
||||||
|
import {Footer} from '../Footer/Footer'
|
||||||
|
|
||||||
|
import rectangle from '../../images/rectangle_secondPage.png'
|
||||||
|
|
||||||
import './calendar.scss'
|
import './calendar.scss'
|
||||||
|
|
||||||
const getDateParamString = ({ paramName, value }) => {
|
const Calendar = ({onSelect}) => {
|
||||||
return value ? `${paramName}=${value}` : ''
|
|
||||||
}
|
|
||||||
|
|
||||||
const Calendar = ({ onSelect }) => {
|
const candidateForCalendar = useSelector(selectCurrentCandidate);
|
||||||
const dispatch = useDispatch()
|
|
||||||
const candidateForCalendar = useSelector(selectCurrentCandidate)
|
|
||||||
const role = useSelector(getRole)
|
|
||||||
const { userId } = useParams()
|
|
||||||
const [month, setMonth] = useState('')
|
|
||||||
const [fromDate, setFromDate] = useState(null)
|
|
||||||
const [toDate, setToDate] = useState(null)
|
|
||||||
|
|
||||||
const history = useHistory()
|
const [month, setMonth] = useState('');
|
||||||
|
|
||||||
// useEffect(() => {
|
|
||||||
// fetchReportList({
|
|
||||||
// link: `${
|
|
||||||
// process.env.REACT_APP_API_URL
|
|
||||||
// }/api/reports/index?user_id=${userId}${getDateParamString({
|
|
||||||
// paramName: 'fromDate',
|
|
||||||
// value: fromDate
|
|
||||||
// })}${getDateParamString({
|
|
||||||
// paramName: 'toDate',
|
|
||||||
// value: toDate
|
|
||||||
// })}`,
|
|
||||||
// history,
|
|
||||||
// role,
|
|
||||||
// logout: () => {}
|
|
||||||
// })
|
|
||||||
// }, [])
|
|
||||||
|
|
||||||
useEffect(() => {
|
useEffect(() => {
|
||||||
setMonth(currentMonth)
|
setMonth(currentMonth)
|
||||||
}, [month])
|
}, [month]);
|
||||||
|
|
||||||
const { name, skillsName, photo } = candidateForCalendar
|
const {name, skillsName, photo} = candidateForCalendar;
|
||||||
|
|
||||||
const abbreviatedName = name && name.substring(0, name.lastIndexOf(' '))
|
const abbreviatedName = name && name.substring(0, name.lastIndexOf(' '));
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<section className='calendar'>
|
<section className='calendar'>
|
||||||
<div className='row'>
|
<div className='row'>
|
||||||
<h2 className='calendar__profile'>
|
<h2 className='calendar__profile'>
|
||||||
Добрый день, <span>Александр !</span>
|
Добрый день, <span>Александр !</span>
|
||||||
</h2>
|
</h2>
|
||||||
<div className='col-12 col-xl-12 d-flex justify-content-between align-items-center flex-column flex-sm-row'>
|
<div className='col-12 col-xl-12 d-flex justify-content-between align-items-center flex-column flex-sm-row'>
|
||||||
<div className='calendar__info'>
|
<div className='calendar__info'>
|
||||||
<img className='calendar__info-img' src={photo} alt='img' />
|
<img className='calendar__info-img' src={photo} alt='img'/>
|
||||||
<h3 className='calendar__info-name'>{abbreviatedName}</h3>
|
<h3 className='calendar__info-name'>{abbreviatedName}</h3>
|
||||||
</div>
|
</div>
|
||||||
<div className='calendar__title'>
|
<div className='calendar__title'>
|
||||||
<h3 className='calendar__title-text'>{skillsName} разработчик</h3>
|
<h3 className='calendar__title-text'>{skillsName} разработчик</h3>
|
||||||
<img className='calendar__title-img' src={rectangle} alt='img' />
|
<img className='calendar__title-img' src={rectangle} alt='img'/>
|
||||||
</div>
|
</div>
|
||||||
<div>
|
<div>
|
||||||
<Link to='/report'>
|
<Link to='/report'>
|
||||||
<button className='calendar__btn'>Заполнить отчет за день</button>
|
<button className='calendar__btn'>Заполнить отчет за день</button>
|
||||||
</Link>
|
</Link>
|
||||||
|
</div>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
|
||||||
|
|
||||||
<div className='row'>
|
<div className='row'>
|
||||||
<div className='col-12 col-xl-12'>
|
<div className='col-12 col-xl-12'>
|
||||||
<CalendarComponent onSelect={onSelect} />
|
<CalendarComponent onSelect={onSelect}/>
|
||||||
<p className='calendar__hours'>
|
<p className='calendar__hours'>
|
||||||
{month} : <span> 60 часов </span>
|
{month} : <span> 60 часов </span>
|
||||||
</p>
|
</p>
|
||||||
|
</div>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
<Footer/>
|
||||||
<Footer />
|
</section>
|
||||||
</section>
|
|
||||||
)
|
)
|
||||||
}
|
};
|
||||||
|
|
||||||
export default Calendar
|
export default Calendar
|
||||||
|
@ -1,9 +1,9 @@
|
|||||||
.calendar {
|
.calendar {
|
||||||
margin-bottom: 40px;
|
margin-bottom: 40px;
|
||||||
|
font-family: 'LabGrotesque', sans-serif;
|
||||||
|
|
||||||
&__profile {
|
&__profile {
|
||||||
color: #282828;
|
color: #282828;
|
||||||
font-family: 'GT Eesti Pro Display';
|
|
||||||
font-size: 3.4em;
|
font-size: 3.4em;
|
||||||
font-weight: 700;
|
font-weight: 700;
|
||||||
font-style: normal;
|
font-style: normal;
|
||||||
@ -29,7 +29,6 @@
|
|||||||
height: 112px;
|
height: 112px;
|
||||||
|
|
||||||
&-name {
|
&-name {
|
||||||
font-family: 'GT Eesti Pro Display';
|
|
||||||
font-size: 2em;
|
font-size: 2em;
|
||||||
font-weight: 400;
|
font-weight: 400;
|
||||||
font-style: normal;
|
font-style: normal;
|
||||||
@ -53,7 +52,6 @@
|
|||||||
|
|
||||||
&__title {
|
&__title {
|
||||||
&-text {
|
&-text {
|
||||||
font-family: 'GT Eesti Pro Display';
|
|
||||||
font-size: 2.7em;
|
font-size: 2.7em;
|
||||||
font-weight: 700;
|
font-weight: 700;
|
||||||
font-style: normal;
|
font-style: normal;
|
||||||
@ -66,7 +64,6 @@
|
|||||||
&__btn {
|
&__btn {
|
||||||
width: 280px;
|
width: 280px;
|
||||||
height: 62px;
|
height: 62px;
|
||||||
box-shadow: 6px 5px 20px rgba(82, 151, 34, 0.21);
|
|
||||||
border-radius: 31px;
|
border-radius: 31px;
|
||||||
background-color: #ffffff;
|
background-color: #ffffff;
|
||||||
background-image: linear-gradient(to top, #6aaf5c 0%, #52b709 100%),
|
background-image: linear-gradient(to top, #6aaf5c 0%, #52b709 100%),
|
||||||
@ -83,10 +80,15 @@
|
|||||||
font-size: 1.6em;
|
font-size: 1.6em;
|
||||||
letter-spacing: normal;
|
letter-spacing: normal;
|
||||||
text-align: center;
|
text-align: center;
|
||||||
|
transition: all 0.3s ease;
|
||||||
|
|
||||||
|
&:hover {
|
||||||
|
box-shadow: 6px 5px 20px rgb(87 98 80 / 21%);
|
||||||
|
transform: scale(1.02);
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
&__hours {
|
&__hours {
|
||||||
font-family: 'GT Eesti Pro Display';
|
|
||||||
font-size: 2.2em;
|
font-size: 2.2em;
|
||||||
font-weight: 600;
|
font-weight: 600;
|
||||||
font-style: normal;
|
font-style: normal;
|
||||||
@ -96,7 +98,6 @@
|
|||||||
margin-left: 68px;
|
margin-left: 68px;
|
||||||
|
|
||||||
span {
|
span {
|
||||||
font-family: 'GT Eesti Pro Display';
|
|
||||||
font-weight: 100;
|
font-weight: 100;
|
||||||
font-style: normal;
|
font-style: normal;
|
||||||
letter-spacing: normal;
|
letter-spacing: normal;
|
||||||
|
@ -1,18 +1,19 @@
|
|||||||
.calendar-component {
|
.calendar-component {
|
||||||
position: relative;
|
position: relative;
|
||||||
margin-top: 80px;
|
margin-top: 30px;
|
||||||
margin-bottom: 60px;
|
margin-bottom: 60px;
|
||||||
background-color: #f9f9f9;
|
background-color: #f9f9f9;
|
||||||
padding-left: 68px;
|
padding-left: 68px;
|
||||||
padding-right: 54px;
|
padding-right: 54px;
|
||||||
padding-top: 48px;
|
padding-top: 48px;
|
||||||
padding-bottom: 94px;
|
padding-bottom: 94px;
|
||||||
|
font-family: 'LabGrotesque', sans-serif;
|
||||||
|
|
||||||
&__header {
|
&__header {
|
||||||
display: flex;
|
display: flex;
|
||||||
|
|
||||||
h3 {
|
h3 {
|
||||||
font-family: 'GT Eesti Pro Display';
|
|
||||||
font-size: 2.5em;
|
font-size: 2.5em;
|
||||||
font-weight: 400;
|
font-weight: 400;
|
||||||
font-style: normal;
|
font-style: normal;
|
||||||
@ -33,7 +34,6 @@
|
|||||||
|
|
||||||
span {
|
span {
|
||||||
color: #18586e;
|
color: #18586e;
|
||||||
font-family: 'GT Eesti Pro Display';
|
|
||||||
font-size: 1.6em;
|
font-size: 1.6em;
|
||||||
font-weight: 500;
|
font-weight: 500;
|
||||||
font-style: normal;
|
font-style: normal;
|
||||||
@ -57,12 +57,11 @@
|
|||||||
&__body {
|
&__body {
|
||||||
div {
|
div {
|
||||||
display: grid;
|
display: grid;
|
||||||
|
|
||||||
grid-template-columns: 1fr 1fr 1fr 1fr 1fr 1fr 1fr;
|
grid-template-columns: 1fr 1fr 1fr 1fr 1fr 1fr 1fr;
|
||||||
|
|
||||||
p {
|
p {
|
||||||
color: #398208;
|
color: #398208;
|
||||||
font-family: 'GT Eesti Pro Display';
|
|
||||||
font-size: 25px;
|
font-size: 25px;
|
||||||
font-weight: 400;
|
font-weight: 400;
|
||||||
font-style: normal;
|
font-style: normal;
|
||||||
@ -87,13 +86,17 @@
|
|||||||
border: 1px solid #c4c4c4;
|
border: 1px solid #c4c4c4;
|
||||||
background-color: #ffffff;
|
background-color: #ffffff;
|
||||||
margin-top: 20px;
|
margin-top: 20px;
|
||||||
font-family: 'GT Eesti Pro Display';
|
|
||||||
font-size: 1.2em;
|
font-size: 1.2em;
|
||||||
font-weight: 100;
|
font-weight: 100;
|
||||||
font-style: normal;
|
font-style: normal;
|
||||||
letter-spacing: normal;
|
letter-spacing: normal;
|
||||||
line-height: normal;
|
line-height: normal;
|
||||||
text-align: center;
|
text-align: center;
|
||||||
|
|
||||||
|
a {
|
||||||
|
text-decoration: none;
|
||||||
|
color: #000000;
|
||||||
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@ -250,7 +253,11 @@
|
|||||||
}
|
}
|
||||||
|
|
||||||
.before {
|
.before {
|
||||||
background-color: #f0f7e0 !important;
|
background-color: #e5f9b6 !important;
|
||||||
|
}
|
||||||
|
|
||||||
|
.pass {
|
||||||
|
background-color: #f7d7c9 !important;
|
||||||
}
|
}
|
||||||
|
|
||||||
.today {
|
.today {
|
||||||
@ -260,4 +267,8 @@
|
|||||||
|
|
||||||
.selected {
|
.selected {
|
||||||
background-color: #f9f9c3 !important;
|
background-color: #f9f9c3 !important;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
.block {
|
||||||
|
pointer-events: none;
|
||||||
|
}
|
||||||
|
@ -20,6 +20,16 @@ export function calendarHelper(value) {
|
|||||||
return calendar;
|
return calendar;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
export function getReports(value) {
|
||||||
|
const startDay = value.clone().startOf('month').startOf('week').startOf('day');
|
||||||
|
const reportsStart = `${new Date(startDay).getFullYear()}-${new Date(startDay).getMonth() + 1}-${new Date(startDay).getDate()}`
|
||||||
|
const endDay = value.clone().endOf('month').endOf('week');
|
||||||
|
const reportsEnd = `${new Date(endDay).getFullYear()}-${new Date(endDay).getMonth() + 1}-${new Date(endDay).getDate()}`
|
||||||
|
const getReports = `fromDate=${reportsStart}&toDate=${reportsEnd}`
|
||||||
|
|
||||||
|
return getReports;
|
||||||
|
}
|
||||||
|
|
||||||
export function currentMonth() {
|
export function currentMonth() {
|
||||||
const currentMonth = moment().format('MMMM');
|
const currentMonth = moment().format('MMMM');
|
||||||
|
|
||||||
@ -32,4 +42,4 @@ export function currentMonthAndDay(day) {
|
|||||||
|
|
||||||
export function currentMonthAndDayReportPage() {
|
export function currentMonthAndDayReportPage() {
|
||||||
return moment().format('D MMMM');
|
return moment().format('D MMMM');
|
||||||
}
|
}
|
||||||
|
@ -1,43 +1,41 @@
|
|||||||
import React, {useEffect} from 'react'
|
import React, {useEffect, useState} from 'react'
|
||||||
import {useHistory, useParams, Link} from 'react-router-dom'
|
import {useParams, Link, useNavigate} from 'react-router-dom'
|
||||||
import {useSelector, useDispatch} from 'react-redux'
|
import {useSelector, useDispatch} from 'react-redux'
|
||||||
import {
|
|
||||||
currentCandidate,
|
import SkillSection from '../SkillSection/SkillSection'
|
||||||
selectCurrentCandidate,
|
import Sidebar from '../CandidateSidebar/CandidateSidebar'
|
||||||
auth
|
import {Footer} from '../Footer/Footer'
|
||||||
} from '../../redux/outstaffingSlice'
|
|
||||||
|
import {currentCandidate, selectCurrentCandidate,} from '../../redux/outstaffingSlice'
|
||||||
|
|
||||||
|
import {useRequest} from "../../hooks/useRequest";
|
||||||
|
import {createMarkup} from "../../helper";
|
||||||
|
|
||||||
import arrow from '../../images/right-arrow.png'
|
import arrow from '../../images/right-arrow.png'
|
||||||
import rectangle from '../../images/rectangle_secondPage.png'
|
import rectangle from '../../images/rectangle_secondPage.png'
|
||||||
import Sidebar from '../CandidateSidebar/CandidateSidebar'
|
|
||||||
import SkillSection from '../SkillSection/SkillSection'
|
|
||||||
import front from '../../images/front_end.png'
|
import front from '../../images/front_end.png'
|
||||||
import back from '../../images/back_end.png'
|
import back from '../../images/back_end.png'
|
||||||
import design from '../../images/design.png'
|
import design from '../../images/design.png'
|
||||||
import {fetchGet} from '../../server/server'
|
|
||||||
import {Footer} from '../Footer/Footer'
|
|
||||||
|
|
||||||
import './candidate.scss'
|
import './candidate.scss'
|
||||||
import {getRole} from '../../redux/roleSlice'
|
|
||||||
import {useState} from 'react'
|
|
||||||
|
|
||||||
const Candidate = () => {
|
const Candidate = () => {
|
||||||
const history = useHistory();
|
|
||||||
const {id: candidateId} = useParams();
|
const {id: candidateId} = useParams();
|
||||||
|
const navigate = useNavigate();
|
||||||
const dispatch = useDispatch();
|
const dispatch = useDispatch();
|
||||||
const role = useSelector(getRole);
|
|
||||||
const [activeSnippet, setActiveSnippet] = useState(true);
|
const [activeSnippet, setActiveSnippet] = useState(true);
|
||||||
|
|
||||||
|
const {apiRequest} = useRequest();
|
||||||
|
|
||||||
useEffect(() => {
|
useEffect(() => {
|
||||||
window.scrollTo(0, 0)
|
window.scrollTo(0, 0)
|
||||||
}, []);
|
}, []);
|
||||||
|
|
||||||
useEffect(() => {
|
useEffect(() => {
|
||||||
fetchGet({
|
apiRequest(`/profile/${candidateId}`, {
|
||||||
link: `${process.env.REACT_APP_API_URL}/api/profile/${candidateId}`,
|
|
||||||
params: Number(candidateId),
|
params: Number(candidateId),
|
||||||
history,
|
|
||||||
role,
|
|
||||||
logout: () => dispatch(auth(false))
|
|
||||||
}).then((el) => dispatch(currentCandidate(el)))
|
}).then((el) => dispatch(currentCandidate(el)))
|
||||||
}, [dispatch, candidateId]);
|
}, [dispatch, candidateId]);
|
||||||
|
|
||||||
@ -79,10 +77,6 @@ const Candidate = () => {
|
|||||||
return styles
|
return styles
|
||||||
};
|
};
|
||||||
|
|
||||||
function createMarkup(text) {
|
|
||||||
return {__html: text.split('</p>').join('</p>')}
|
|
||||||
}
|
|
||||||
|
|
||||||
const {header, img, classes} = setStyles();
|
const {header, img, classes} = setStyles();
|
||||||
|
|
||||||
return (
|
return (
|
||||||
@ -100,7 +94,7 @@ const Candidate = () => {
|
|||||||
<div className='row'>
|
<div className='row'>
|
||||||
<div className='col-12 candidate__header'>
|
<div className='col-12 candidate__header'>
|
||||||
|
|
||||||
<div className='candidate__arrow' onClick={() => history.push('/')}>
|
<div className='candidate__arrow' onClick={() => navigate('/')}>
|
||||||
<div className='candidate__arrow-img'>
|
<div className='candidate__arrow-img'>
|
||||||
<img src={arrow} alt=''/>
|
<img src={arrow} alt=''/>
|
||||||
</div>
|
</div>
|
||||||
@ -141,20 +135,13 @@ const Candidate = () => {
|
|||||||
: 'Описание отсутствует...'}
|
: 'Описание отсутствует...'}
|
||||||
</p>
|
</p>
|
||||||
)}
|
)}
|
||||||
{/* <Link to={`/candidate/${currentCandidateObj.id}/form`}>
|
|
||||||
<button type="submit" className='candidate__btn'>
|
|
||||||
Выбрать к собеседованию
|
|
||||||
</button>
|
|
||||||
</Link> */}
|
|
||||||
<SkillSection skillsArr={skillValues}/>
|
<SkillSection skillsArr={skillValues}/>
|
||||||
|
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
) :
|
) :
|
||||||
(
|
(
|
||||||
// <div className="col-12 col-xl-8">
|
|
||||||
// <CodeSnippetlighter />
|
|
||||||
// </div>
|
|
||||||
<div className="col-12 col-xl-8">
|
<div className="col-12 col-xl-8">
|
||||||
<div className="candidate__works works">
|
<div className="candidate__works works">
|
||||||
<div className="works__body">
|
<div className="works__body">
|
||||||
|
@ -2,7 +2,7 @@ import React from 'react'
|
|||||||
import { Link } from 'react-router-dom'
|
import { Link } from 'react-router-dom'
|
||||||
import { Achievement } from '../Achievement/Achievement'
|
import { Achievement } from '../Achievement/Achievement'
|
||||||
|
|
||||||
import { LEVELS, SKILLS } from '../constants/constants'
|
import { LEVELS, SKILLS } from '../../constants/constants'
|
||||||
|
|
||||||
import './candidateSidebar.scss'
|
import './candidateSidebar.scss'
|
||||||
|
|
||||||
|
@ -1,43 +1,21 @@
|
|||||||
import React, { useEffect, useState } from 'react'
|
import React from 'react'
|
||||||
|
import {useSelector} from 'react-redux'
|
||||||
|
import {Link} from 'react-router-dom'
|
||||||
|
|
||||||
|
import {Loader} from '../Loader/Loader'
|
||||||
|
|
||||||
|
import {LEVELS, SKILLS} from '../../constants/constants'
|
||||||
|
import {selectProfiles, selectFilteredCandidates,} from '../../redux/outstaffingSlice'
|
||||||
|
|
||||||
import male from '../../images/medium_male.png'
|
import male from '../../images/medium_male.png'
|
||||||
import rectangle from '../../images/rectangle_secondPage.png'
|
import rectangle from '../../images/rectangle_secondPage.png'
|
||||||
import { Link, useHistory } from 'react-router-dom'
|
|
||||||
import { LEVELS, SKILLS } from '../constants/constants'
|
|
||||||
import {
|
|
||||||
selectProfiles,
|
|
||||||
selectFilteredCandidates,
|
|
||||||
selectItems,
|
|
||||||
auth
|
|
||||||
} from '../../redux/outstaffingSlice'
|
|
||||||
import { useSelector, useDispatch } from 'react-redux'
|
|
||||||
import { fetchGet } from '../../server/server'
|
|
||||||
import { Loader } from '../Loader/Loader'
|
|
||||||
import { getRole } from '../../redux/roleSlice'
|
|
||||||
|
|
||||||
import './description.scss'
|
import './description.scss'
|
||||||
|
|
||||||
const Description = ({ onLoadMore, isLoadingMore }) => {
|
const Description = ({ onLoadMore, isLoadingMore }) => {
|
||||||
const dispatch = useDispatch()
|
|
||||||
const [isLoaded, setIsLoaded] = useState(false)
|
|
||||||
const history = useHistory()
|
|
||||||
const role = useSelector(getRole)
|
|
||||||
const candidatesListArr = useSelector(selectProfiles)
|
|
||||||
const itemsArr = useSelector(selectItems)
|
|
||||||
const filteredListArr = useSelector(selectFilteredCandidates)
|
|
||||||
const [allCandidates, getAllCandidates] = useState([])
|
|
||||||
|
|
||||||
useEffect(() => {
|
const candidatesListArr = useSelector(selectProfiles);
|
||||||
fetchGet({
|
const filteredListArr = useSelector(selectFilteredCandidates);
|
||||||
link: `${process.env.REACT_APP_API_URL}/api/profile?limit=`,
|
|
||||||
params: 1000,
|
|
||||||
history,
|
|
||||||
role,
|
|
||||||
logout: () => dispatch(auth(false))
|
|
||||||
}).then((p) => {
|
|
||||||
getAllCandidates(p)
|
|
||||||
setIsLoaded(true)
|
|
||||||
})
|
|
||||||
}, [])
|
|
||||||
|
|
||||||
if (!filteredListArr) {
|
if (!filteredListArr) {
|
||||||
return (
|
return (
|
||||||
@ -95,7 +73,7 @@ const Description = ({ onLoadMore, isLoadingMore }) => {
|
|||||||
))
|
))
|
||||||
) : (
|
) : (
|
||||||
<div className='description__empty'>
|
<div className='description__empty'>
|
||||||
{isLoaded
|
{isLoadingMore
|
||||||
? 'В данный момент в категории нет свободных специалистов'
|
? 'В данный момент в категории нет свободных специалистов'
|
||||||
: 'Загрузка...'}
|
: 'Загрузка...'}
|
||||||
</div>
|
</div>
|
||||||
@ -229,9 +207,8 @@ const Description = ({ onLoadMore, isLoadingMore }) => {
|
|||||||
<div className='col-12'>
|
<div className='col-12'>
|
||||||
<div className='description__footer'>
|
<div className='description__footer'>
|
||||||
<div className='description__footer-btn'>
|
<div className='description__footer-btn'>
|
||||||
{allCandidates &&
|
{
|
||||||
candidatesListArr &&
|
candidatesListArr &&
|
||||||
candidatesListArr.length !== allCandidates.length &&
|
|
||||||
filteredListArr.length === 0 ? (
|
filteredListArr.length === 0 ? (
|
||||||
<button onClick={() => onLoadMore(2)}>Загрузить еще</button>
|
<button onClick={() => onLoadMore(2)}>Загрузить еще</button>
|
||||||
) : null}
|
) : null}
|
||||||
@ -242,6 +219,6 @@ const Description = ({ onLoadMore, isLoadingMore }) => {
|
|||||||
</div>
|
</div>
|
||||||
</section>
|
</section>
|
||||||
)
|
)
|
||||||
}
|
};
|
||||||
|
|
||||||
export default Description
|
export default Description
|
||||||
|
@ -1,7 +1,5 @@
|
|||||||
import React, { useState } from 'react'
|
import React, { useState } from 'react'
|
||||||
import { fetchPost } from '../../server/server'
|
import {useParams, useNavigate} from 'react-router-dom'
|
||||||
import { auth } from '../../redux/outstaffingSlice'
|
|
||||||
import { useHistory, useParams, Redirect } from 'react-router-dom'
|
|
||||||
import { Loader } from '../Loader/Loader'
|
import { Loader } from '../Loader/Loader'
|
||||||
import PhoneInput from 'react-phone-input-2'
|
import PhoneInput from 'react-phone-input-2'
|
||||||
import 'react-phone-input-2/lib/style.css'
|
import 'react-phone-input-2/lib/style.css'
|
||||||
@ -9,59 +7,57 @@ import './form.scss'
|
|||||||
|
|
||||||
import { withSwalInstance } from 'sweetalert2-react'
|
import { withSwalInstance } from 'sweetalert2-react'
|
||||||
import swal from 'sweetalert2'
|
import swal from 'sweetalert2'
|
||||||
import { useSelector, useDispatch } from 'react-redux'
|
import {useRequest} from "../../hooks/useRequest";
|
||||||
import { getRole } from '../../redux/roleSlice'
|
|
||||||
|
|
||||||
const SweetAlert = withSwalInstance(swal)
|
|
||||||
|
const SweetAlert = withSwalInstance(swal);
|
||||||
|
|
||||||
const Form = () => {
|
const Form = () => {
|
||||||
const dispatch = useDispatch()
|
|
||||||
const history = useHistory()
|
const navigate = useNavigate();
|
||||||
const role = useSelector(getRole)
|
|
||||||
const urlParams = useParams()
|
const urlParams = useParams();
|
||||||
const [status, setStatus] = useState(null)
|
const [status, setStatus] = useState(null);
|
||||||
const [data, setData] = useState({
|
const [data, setData] = useState({
|
||||||
email: '',
|
email: '',
|
||||||
phone: '',
|
phone: '',
|
||||||
comment: ''
|
comment: ''
|
||||||
})
|
});
|
||||||
const [isFetching, setIsFetching] = useState(false)
|
const [isFetching, setIsFetching] = useState(false);
|
||||||
|
|
||||||
|
const {apiRequest} = useRequest();
|
||||||
|
|
||||||
const handleChange = (e) => {
|
const handleChange = (e) => {
|
||||||
const { id, value } = e.target
|
const { id, value } = e.target;
|
||||||
|
|
||||||
setData((prev) => ({
|
setData((prev) => ({
|
||||||
...prev,
|
...prev,
|
||||||
[id]: value
|
[id]: value
|
||||||
}))
|
}))
|
||||||
}
|
};
|
||||||
|
|
||||||
const handleSubmit = (e) => {
|
const handleSubmit = (e) => {
|
||||||
e.preventDefault()
|
e.preventDefault();
|
||||||
|
|
||||||
setIsFetching(true)
|
setIsFetching(true);
|
||||||
const formData = new FormData()
|
const formData = new FormData();
|
||||||
formData.append('profile_id', urlParams.id)
|
formData.append('profile_id', urlParams.id);
|
||||||
formData.append('email', data.email)
|
formData.append('email', data.email);
|
||||||
formData.append('phone', data.phone)
|
formData.append('phone', data.phone);
|
||||||
formData.append('comment', data.comment)
|
formData.append('comment', data.comment);
|
||||||
|
|
||||||
fetchPost({
|
apiRequest('/interview-request/create-interview-request',{
|
||||||
link: `${process.env.REACT_APP_API_URL}/api/interview-request/create-interview-request`,
|
method: 'POST',
|
||||||
params: {
|
params: {
|
||||||
profile_id: urlParams.id,
|
profile_id: urlParams.id,
|
||||||
...data
|
...data
|
||||||
},
|
}
|
||||||
history,
|
}).then((res) => {
|
||||||
role,
|
setStatus(res);
|
||||||
logout: () => dispatch(auth(false))
|
setIsFetching(false)
|
||||||
}).then((res) =>
|
}
|
||||||
res.json().then((resJSON) => {
|
|
||||||
setStatus(resJSON)
|
|
||||||
setIsFetching(false)
|
|
||||||
})
|
|
||||||
)
|
)
|
||||||
}
|
};
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<>
|
<>
|
||||||
@ -79,8 +75,8 @@ const Form = () => {
|
|||||||
setStatus(null)
|
setStatus(null)
|
||||||
}
|
}
|
||||||
: () => {
|
: () => {
|
||||||
setStatus(null)
|
setStatus(null);
|
||||||
history.push(`/candidate/${urlParams.id}`)
|
navigate(`/candidate/${urlParams.id}`)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
/>
|
/>
|
||||||
@ -135,6 +131,6 @@ const Form = () => {
|
|||||||
</div>
|
</div>
|
||||||
</>
|
</>
|
||||||
)
|
)
|
||||||
}
|
};
|
||||||
|
|
||||||
export default Form
|
export default Form
|
||||||
|
@ -1,66 +1,64 @@
|
|||||||
import React, { useState, useEffect } from 'react'
|
import React, {useState, useEffect} from 'react'
|
||||||
import { useDispatch, useSelector } from 'react-redux'
|
import {useDispatch} from 'react-redux'
|
||||||
|
|
||||||
import Outstaffing from '../Outstaffing/Outstaffing'
|
import Outstaffing from '../Outstaffing/Outstaffing'
|
||||||
import Description from '../Description/Description'
|
import Description from '../Description/Description'
|
||||||
import { fetchGet } from '../../server/server'
|
import {Footer} from '../Footer/Footer'
|
||||||
import { profiles, tags, auth } from '../../redux/outstaffingSlice'
|
|
||||||
import { getRole } from '../../redux/roleSlice'
|
import {profiles, tags} from '../../redux/outstaffingSlice'
|
||||||
import { Footer } from '../Footer/Footer'
|
|
||||||
import { useHistory } from 'react-router-dom'
|
import {useRequest} from "../../hooks/useRequest";
|
||||||
|
|
||||||
|
|
||||||
const Home = () => {
|
const Home = () => {
|
||||||
const history = useHistory()
|
|
||||||
const [isLoadingMore, setIsLoadingMore] = useState(false)
|
|
||||||
const [index, setIndex] = useState(4)
|
|
||||||
|
|
||||||
const dispatch = useDispatch()
|
const [isLoadingMore, setIsLoadingMore] = useState(false);
|
||||||
const role = useSelector(getRole)
|
const [index, setIndex] = useState(4);
|
||||||
|
|
||||||
|
const dispatch = useDispatch();
|
||||||
|
|
||||||
|
const {apiRequest} = useRequest();
|
||||||
|
|
||||||
useEffect(() => {
|
useEffect(() => {
|
||||||
setIsLoadingMore(true)
|
setIsLoadingMore(true);
|
||||||
fetchGet({
|
apiRequest('/profile', {
|
||||||
link: `${process.env.REACT_APP_API_URL}/api/profile?limit=`,
|
params: {limit: 1000},
|
||||||
params: index,
|
|
||||||
history,
|
|
||||||
role,
|
|
||||||
logout: () => dispatch(auth(false))
|
|
||||||
}).then((profileArr) => {
|
}).then((profileArr) => {
|
||||||
dispatch(profiles(profileArr))
|
|
||||||
setIsLoadingMore(false)
|
|
||||||
})
|
|
||||||
|
|
||||||
fetchGet({
|
dispatch(profiles(profileArr));
|
||||||
link: `${process.env.REACT_APP_API_URL}/api/skills/skills-on-main-page`,
|
setIsLoadingMore(false)
|
||||||
history,
|
});
|
||||||
role,
|
|
||||||
logout: () => dispatch(auth(false))
|
apiRequest('/skills/skills-on-main-page', {
|
||||||
}).then((skills) => {
|
}).then((skills) => {
|
||||||
if (!skills) {
|
if (!skills) {
|
||||||
return []
|
return []
|
||||||
}
|
}
|
||||||
const keys = Object.keys(skills)
|
|
||||||
const values = Object.values(skills)
|
const keys = Object.keys(skills);
|
||||||
|
const values = Object.values(skills);
|
||||||
|
|
||||||
const tempTags = values.map((value, index) =>
|
const tempTags = values.map((value, index) =>
|
||||||
value.map((val) => {
|
value.map((val) => {
|
||||||
return { id: val.id, value: val.tags, name: keys[index] }
|
return {id: val.id, value: val.tags, name: keys[index]}
|
||||||
})
|
})
|
||||||
)
|
);
|
||||||
dispatch(tags(tempTags))
|
dispatch(tags(tempTags))
|
||||||
})
|
})
|
||||||
}, [dispatch, index])
|
|
||||||
|
}, [index]);
|
||||||
|
|
||||||
const loadMore = (count) => {
|
const loadMore = (count) => {
|
||||||
setIndex((prev) => prev + count)
|
setIndex((prev) => prev + count)
|
||||||
}
|
};
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<>
|
<>
|
||||||
<Outstaffing />
|
<Outstaffing/>
|
||||||
<Description onLoadMore={loadMore} isLoadingMore={isLoadingMore} />
|
<Description onLoadMore={loadMore} isLoadingMore={isLoadingMore}/>
|
||||||
<Footer />
|
<Footer/>
|
||||||
</>
|
</>
|
||||||
)
|
)
|
||||||
}
|
};
|
||||||
|
|
||||||
export default Home
|
export default Home
|
||||||
|
@ -1,5 +1,6 @@
|
|||||||
import SVGLoader from 'react-loader-spinner'
|
import SVGLoader from 'react-loader-spinner'
|
||||||
import './loader.scss'
|
import './loader.scss'
|
||||||
|
import React from "react";
|
||||||
|
|
||||||
export const Loader = ({ width = 50, height = 50 }) => {
|
export const Loader = ({ width = 50, height = 50 }) => {
|
||||||
return (
|
return (
|
||||||
@ -7,4 +8,4 @@ export const Loader = ({ width = 50, height = 50 }) => {
|
|||||||
<SVGLoader type='Circles' color='#fff' height={height} width={width} />
|
<SVGLoader type='Circles' color='#fff' height={height} width={width} />
|
||||||
</div>
|
</div>
|
||||||
)
|
)
|
||||||
}
|
};
|
||||||
|
@ -4,7 +4,7 @@
|
|||||||
display: flex;
|
display: flex;
|
||||||
justify-content: center;
|
justify-content: center;
|
||||||
align-items: center;
|
align-items: center;
|
||||||
|
position: relative;
|
||||||
&:hover {
|
&:hover {
|
||||||
path {
|
path {
|
||||||
fill: #6aaf5c;
|
fill: #6aaf5c;
|
||||||
|
@ -1,5 +1,5 @@
|
|||||||
import React, {useState} from 'react'
|
import React, {useState} from 'react'
|
||||||
import {useHistory} from 'react-router-dom'
|
import {useNavigate} from 'react-router-dom'
|
||||||
import {useDispatch, useSelector} from 'react-redux'
|
import {useDispatch, useSelector} from 'react-redux'
|
||||||
import {Loader} from '../Loader/Loader'
|
import {Loader} from '../Loader/Loader'
|
||||||
import {auth} from '../../redux/outstaffingSlice'
|
import {auth} from '../../redux/outstaffingSlice'
|
||||||
@ -11,7 +11,7 @@ export const LogoutButton = () => {
|
|||||||
const [isLoggingOut, setIsLoggingOut] = useState(false);
|
const [isLoggingOut, setIsLoggingOut] = useState(false);
|
||||||
const dispatch = useDispatch();
|
const dispatch = useDispatch();
|
||||||
const userRole = useSelector(getRole);
|
const userRole = useSelector(getRole);
|
||||||
const history = useHistory();
|
const navigate = useNavigate();
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<div className='logout-button'>
|
<div className='logout-button'>
|
||||||
@ -21,7 +21,7 @@ export const LogoutButton = () => {
|
|||||||
localStorage.clear();
|
localStorage.clear();
|
||||||
dispatch(auth(false));
|
dispatch(auth(false));
|
||||||
setIsLoggingOut(false);
|
setIsLoggingOut(false);
|
||||||
history.push(userRole === 'ROLE_DEV' ? '/authdev' : '/auth')
|
navigate(userRole === 'ROLE_DEV' ? '/authdev' : '/auth')
|
||||||
}}
|
}}
|
||||||
>
|
>
|
||||||
{isLoggingOut ? <Loader/> : 'Выйти'}{' '}
|
{isLoggingOut ? <Loader/> : 'Выйти'}{' '}
|
||||||
|
@ -1,12 +1,11 @@
|
|||||||
import React, { useState } from 'react'
|
import React from 'react'
|
||||||
import { useSelector, useDispatch } from 'react-redux'
|
import { useSelector, useDispatch } from 'react-redux'
|
||||||
|
|
||||||
import OutstaffingBlock from '../OutstaffingBlock/OutstaffingBlock'
|
import OutstaffingBlock from '../OutstaffingBlock/OutstaffingBlock'
|
||||||
import TagSelect from '../Select/TagSelect'
|
import TagSelect from '../Select/TagSelect'
|
||||||
import {
|
|
||||||
selectTags,
|
import { selectTags, getPositionId, setPositionId} from '../../redux/outstaffingSlice'
|
||||||
getPositionId,
|
|
||||||
setPositionId
|
|
||||||
} from '../../redux/outstaffingSlice'
|
|
||||||
import front from '../../images/front_end.png'
|
import front from '../../images/front_end.png'
|
||||||
import back from '../../images/back_end.png'
|
import back from '../../images/back_end.png'
|
||||||
import design from '../../images/design.png'
|
import design from '../../images/design.png'
|
||||||
@ -21,18 +20,18 @@ const createSelectPositionHandler =
|
|||||||
} else {
|
} else {
|
||||||
dispatch(setPositionId(id))
|
dispatch(setPositionId(id))
|
||||||
}
|
}
|
||||||
}
|
};
|
||||||
|
|
||||||
const Outstaffing = () => {
|
const Outstaffing = () => {
|
||||||
const dispatch = useDispatch()
|
const dispatch = useDispatch();
|
||||||
const positionId = useSelector(getPositionId)
|
const positionId = useSelector(getPositionId);
|
||||||
const tagsArr = useSelector(selectTags)
|
const tagsArr = useSelector(selectTags);
|
||||||
|
|
||||||
const onSelectPosition = createSelectPositionHandler({
|
const onSelectPosition = createSelectPositionHandler({
|
||||||
positionId,
|
positionId,
|
||||||
setPositionId,
|
setPositionId,
|
||||||
dispatch
|
dispatch
|
||||||
})
|
});
|
||||||
return (
|
return (
|
||||||
<>
|
<>
|
||||||
<section className='outstaffing'>
|
<section className='outstaffing'>
|
||||||
@ -91,6 +90,6 @@ const Outstaffing = () => {
|
|||||||
<TagSelect />
|
<TagSelect />
|
||||||
</>
|
</>
|
||||||
)
|
)
|
||||||
}
|
};
|
||||||
|
|
||||||
export default Outstaffing
|
export default Outstaffing
|
||||||
|
@ -1,77 +1,71 @@
|
|||||||
import React from 'react'
|
import React from 'react'
|
||||||
import OutsideClickHandler from 'react-outside-click-handler'
|
import OutsideClickHandler from 'react-outside-click-handler'
|
||||||
import { useDispatch, useSelector } from 'react-redux'
|
import {useDispatch, useSelector} from 'react-redux'
|
||||||
import {
|
import {
|
||||||
selectItems,
|
selectItems,
|
||||||
selectedItems,
|
selectedItems,
|
||||||
filteredCandidates,
|
filteredCandidates,
|
||||||
auth
|
|
||||||
} from '../../redux/outstaffingSlice'
|
} from '../../redux/outstaffingSlice'
|
||||||
|
|
||||||
import { fetchGet } from '../../server/server'
|
import {useRequest} from "../../hooks/useRequest";
|
||||||
import { useHistory } from 'react-router-dom'
|
|
||||||
import { getRole } from '../../redux/roleSlice'
|
|
||||||
|
|
||||||
import './outstaffingBlock.scss'
|
import './outstaffingBlock.scss'
|
||||||
|
|
||||||
const handlePositionClick = ({
|
|
||||||
dispatch,
|
const handlePositionClick = (
|
||||||
positionId,
|
{
|
||||||
isSelected,
|
dispatch,
|
||||||
onSelect,
|
positionId,
|
||||||
history,
|
isSelected,
|
||||||
role
|
onSelect,
|
||||||
}) => {
|
apiRequest
|
||||||
|
}) => {
|
||||||
if (isSelected) {
|
if (isSelected) {
|
||||||
fetchGet({
|
apiRequest('/profile', {
|
||||||
link: `${process.env.REACT_APP_API_URL}/api/profile?limit=`,
|
params: {limit: 1000},
|
||||||
params: 4,
|
|
||||||
history,
|
|
||||||
role,
|
|
||||||
logout: () => dispatch(auth(false))
|
|
||||||
}).then((profileArr) => {
|
}).then((profileArr) => {
|
||||||
dispatch(filteredCandidates(profileArr))
|
dispatch(filteredCandidates(profileArr));
|
||||||
dispatch(selectedItems([]))
|
dispatch(selectedItems([]));
|
||||||
onSelect(positionId)
|
onSelect(positionId)
|
||||||
})
|
})
|
||||||
} else {
|
} else {
|
||||||
fetchGet({
|
apiRequest('/profile', {
|
||||||
link: `${process.env.REACT_APP_API_URL}/api/profile?position_id=`,
|
params: {
|
||||||
params: positionId,
|
limit: '1000',
|
||||||
history,
|
position_id: positionId},
|
||||||
role,
|
|
||||||
logout: () => dispatch(auth(false))
|
|
||||||
}).then((el) => {
|
}).then((el) => {
|
||||||
dispatch(filteredCandidates(el))
|
dispatch(filteredCandidates(el));
|
||||||
dispatch(selectedItems([]))
|
dispatch(selectedItems([]));
|
||||||
onSelect(positionId)
|
onSelect(positionId)
|
||||||
})
|
})
|
||||||
}
|
}
|
||||||
}
|
};
|
||||||
|
|
||||||
const OutstaffingBlock = ({
|
const OutstaffingBlock = (
|
||||||
dataTags = [],
|
{
|
||||||
selected,
|
dataTags = [],
|
||||||
img,
|
selected,
|
||||||
header,
|
img,
|
||||||
positionId,
|
header,
|
||||||
isSelected,
|
positionId,
|
||||||
onSelect
|
isSelected,
|
||||||
}) => {
|
onSelect
|
||||||
const history = useHistory()
|
}) => {
|
||||||
const role = useSelector(getRole)
|
|
||||||
|
|
||||||
const dispatch = useDispatch()
|
|
||||||
|
|
||||||
const itemsArr = useSelector(selectItems)
|
const dispatch = useDispatch();
|
||||||
|
|
||||||
|
const itemsArr = useSelector(selectItems);
|
||||||
|
|
||||||
|
const {apiRequest} = useRequest();
|
||||||
|
|
||||||
const handleBlockClick = (item, id) => {
|
const handleBlockClick = (item, id) => {
|
||||||
if (!itemsArr.find((el) => item === el.value)) {
|
if (!itemsArr.find((el) => item === el.value)) {
|
||||||
dispatch(selectedItems([...itemsArr, { id, value: item, label: item }]))
|
dispatch(selectedItems([...itemsArr, {id, value: item, label: item}]))
|
||||||
}
|
}
|
||||||
}
|
};
|
||||||
|
|
||||||
let classes
|
let classes;
|
||||||
|
|
||||||
dataTags.forEach((el) => {
|
dataTags.forEach((el) => {
|
||||||
if (el.name === 'skills_back') {
|
if (el.name === 'skills_back') {
|
||||||
@ -81,61 +75,60 @@ const OutstaffingBlock = ({
|
|||||||
} else if (el.name === 'skills_front') {
|
} else if (el.name === 'skills_front') {
|
||||||
classes = 'front'
|
classes = 'front'
|
||||||
}
|
}
|
||||||
})
|
});
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<OutsideClickHandler
|
<OutsideClickHandler
|
||||||
onOutsideClick={() => {
|
onOutsideClick={() => {
|
||||||
isSelected && onSelect(null)
|
isSelected && onSelect(null)
|
||||||
}}
|
}}
|
||||||
>
|
|
||||||
<div
|
|
||||||
className={`outstaffing-block${
|
|
||||||
isSelected ? ' outstaffing-block__selected' : ''
|
|
||||||
}`}
|
|
||||||
>
|
>
|
||||||
<div
|
<div
|
||||||
className={`outstaffing-block__img ${
|
className={`outstaffing-block${
|
||||||
selected ? ' outstaffing-block__border' : ''
|
isSelected ? ' outstaffing-block__selected' : ''
|
||||||
}`}
|
}`}
|
||||||
onClick={() =>
|
|
||||||
handlePositionClick({
|
|
||||||
dispatch,
|
|
||||||
positionId,
|
|
||||||
isSelected,
|
|
||||||
onSelect,
|
|
||||||
history,
|
|
||||||
role
|
|
||||||
})
|
|
||||||
}
|
|
||||||
>
|
>
|
||||||
<h3>{header}</h3>
|
<div
|
||||||
<img className={classes} src={img} alt='img' />
|
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>
|
||||||
<div
|
</OutsideClickHandler>
|
||||||
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>
|
|
||||||
)
|
)
|
||||||
}
|
};
|
||||||
|
|
||||||
export default OutstaffingBlock
|
export default OutstaffingBlock
|
||||||
|
@ -3,6 +3,7 @@
|
|||||||
color: #f9f9f9;
|
color: #f9f9f9;
|
||||||
}
|
}
|
||||||
.outstaffing-block {
|
.outstaffing-block {
|
||||||
|
|
||||||
margin-top: 60px;
|
margin-top: 60px;
|
||||||
display: flex;
|
display: flex;
|
||||||
flex-direction: column;
|
flex-direction: column;
|
||||||
@ -28,6 +29,7 @@
|
|||||||
}
|
}
|
||||||
|
|
||||||
&__img {
|
&__img {
|
||||||
|
cursor: pointer;
|
||||||
min-width: 260px;
|
min-width: 260px;
|
||||||
min-height: 120px;
|
min-height: 120px;
|
||||||
background-color: #f9f9f9;
|
background-color: #f9f9f9;
|
||||||
|
81
src/components/ProfileCalendar/ProfileCalendar.js
Normal 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>
|
||||||
|
)
|
||||||
|
};
|
119
src/components/ProfileCalendar/ProfileCalendarComponent.js
Normal 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>
|
||||||
|
)
|
||||||
|
}
|
||||||
|
|
43
src/components/ProfileCalendar/profileCalendar.scss
Normal 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);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
71
src/components/ProfileHeader/ProfileHeader.js
Normal 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>
|
||||||
|
)
|
||||||
|
};
|
101
src/components/ProfileHeader/profileHeader.scss
Normal 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;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
@ -8,16 +8,16 @@ export const ProtectedRoute = ({ component: Component, ...rest }) => {
|
|||||||
const existingToken = localStorage.getItem('auth_token')
|
const existingToken = localStorage.getItem('auth_token')
|
||||||
const expiresAt = localStorage.getItem('access_token_expired_at')
|
const expiresAt = localStorage.getItem('access_token_expired_at')
|
||||||
|
|
||||||
const isTokenAlive = (existingToken && expiresAt && new Date(expiresAt).getTime() > (new Date()).getTime());
|
const isTokenAlive = !isAuth && (existingToken && expiresAt && new Date(expiresAt).getTime() > (new Date()).getTime());
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<Route
|
<Route
|
||||||
{...rest}
|
{...rest}
|
||||||
render={props =>
|
render={props =>
|
||||||
( isAuth || isTokenAlive) ? (
|
// ( isAuth || isTokenAlive) ? (
|
||||||
<Component {...props} />
|
<Component {...props} />
|
||||||
) : <Redirect to='/auth' />
|
// ) : <Redirect to='/auth' />
|
||||||
}
|
}
|
||||||
/>
|
/>
|
||||||
);
|
);
|
||||||
}
|
};
|
@ -1,185 +1,204 @@
|
|||||||
import React, { useState } from 'react'
|
import React, {useState} from 'react'
|
||||||
import { useSelector, useDispatch } from 'react-redux'
|
import {useSelector} from 'react-redux'
|
||||||
import { fetchPost } from '../../server/server'
|
import {Link} from 'react-router-dom'
|
||||||
import { useHistory, useParams, Redirect } from 'react-router-dom'
|
|
||||||
import { Loader } from '../Loader/Loader'
|
import {Loader} from '../Loader/Loader'
|
||||||
import { auth } from '../../redux/outstaffingSlice'
|
import {currentMonthAndDay} from '../Calendar/calendarHelper'
|
||||||
import { getRole } from '../../redux/roleSlice'
|
import {Footer} from "../Footer/Footer";
|
||||||
|
import {ProfileHeader} from "../ProfileHeader/ProfileHeader";
|
||||||
|
|
||||||
|
import {useRequest} from "../../hooks/useRequest";
|
||||||
|
|
||||||
|
import {getReportDate} from '../../redux/reportSlice'
|
||||||
|
|
||||||
import calendarIcon from '../../images/calendar_icon.png'
|
import calendarIcon from '../../images/calendar_icon.png'
|
||||||
import ellipse from '../../images/ellipse.png'
|
import ellipse from '../../images/ellipse.png'
|
||||||
import remove from '../../images/remove.png'
|
import remove from '../../images/remove.png'
|
||||||
import addIcon from '../../images/addIcon.png'
|
import addIcon from '../../images/addIcon.png'
|
||||||
import { currentMonthAndDayReportPage } from '../Calendar/calendarHelper'
|
import arrow from "../../images/right-arrow.png";
|
||||||
|
|
||||||
import './reportForm.scss'
|
import './reportForm.scss'
|
||||||
|
|
||||||
const getCreatedDate = () => {
|
|
||||||
const date = new Date();
|
|
||||||
const dd = String(date.getDate()).padStart(2, '0')
|
|
||||||
const mm = String(date.getMonth() + 1).padStart(2, '0')
|
|
||||||
const yyyy = date.getFullYear()
|
|
||||||
|
|
||||||
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 ReportForm = () => {
|
||||||
const dispatch = useDispatch()
|
const reportDate = useSelector(getReportDate);
|
||||||
const history = useHistory()
|
|
||||||
const role = useSelector(getRole)
|
|
||||||
|
|
||||||
const [isFetching, setIsFetching] = useState(false)
|
const {apiRequest} = useRequest();
|
||||||
const [reportSuccess, setReportSuccess] = useState(false)
|
const [isFetching, setIsFetching] = useState(false);
|
||||||
|
const [reportSuccess, setReportSuccess] = useState(false);
|
||||||
|
|
||||||
const [inputs, setInputs] = useState([ { task: '', hours_spent: '', minutes_spent: 0 } ]);
|
const [inputs, setInputs] = useState([{task: '', hours_spent: '', minutes_spent: 0}]);
|
||||||
const [troublesInputValue, setTroublesInputValue] = useState('');
|
const [troublesInputValue, setTroublesInputValue] = useState('');
|
||||||
const [scheduledInputValue, setScheduledInputValue] = useState('');
|
const [scheduledInputValue, setScheduledInputValue] = useState('');
|
||||||
|
|
||||||
const addInput = () => {
|
const addInput = () => {
|
||||||
setInputs((prev) => [...prev, { task: '', hours_spent: '', minutes_spent: 0 }])
|
setInputs((prev) => [...prev, {task: '', hours_spent: '', minutes_spent: 0}])
|
||||||
}
|
};
|
||||||
|
|
||||||
const totalHours = inputs.reduce((a,b) => a + b.hours_spent, 0)
|
const totalHours = inputs.reduce((a, b) => a + b.hours_spent, 0);
|
||||||
|
|
||||||
const deleteInput = (indexRemove) => {
|
const deleteInput = (indexRemove) => {
|
||||||
if (indexRemove !== 0) {
|
if (indexRemove !== 0) {
|
||||||
setInputs((prev) => prev.filter((el, index) => index !== indexRemove))
|
setInputs((prev) => prev.filter((el, index) => index !== indexRemove))
|
||||||
}
|
}
|
||||||
}
|
};
|
||||||
|
|
||||||
|
const handler = () => {
|
||||||
|
apiRequest('/reports/create', {
|
||||||
|
method: 'POST',
|
||||||
|
body: {
|
||||||
|
tasks: inputs,
|
||||||
|
difficulties: troublesInputValue,
|
||||||
|
tomorrow: scheduledInputValue,
|
||||||
|
created_at: getCreatedDate(reportDate),
|
||||||
|
status: 1,
|
||||||
|
},
|
||||||
|
}).then((res) => {
|
||||||
|
if (res.status === 200) {
|
||||||
|
setReportSuccess(true);
|
||||||
|
setTimeout(() => setReportSuccess(false), 2000)
|
||||||
|
}
|
||||||
|
setInputs(() => []);
|
||||||
|
setTroublesInputValue('');
|
||||||
|
setScheduledInputValue('');
|
||||||
|
setIsFetching(false);
|
||||||
|
setInputs(() => [{task: '', hours_spent: '', minutes_spent: 0}]);
|
||||||
|
})
|
||||||
|
};
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<section className='report-form'>
|
<section className='report-form'>
|
||||||
<div className='row'>
|
<ProfileHeader/>
|
||||||
<div className='col-xl-12'>
|
<div className='container'>
|
||||||
<div className='report-form__block'>
|
<h2 className='summary__title'>Ваши отчеты - <span>добавить отчет</span></h2>
|
||||||
<div className='report-form__block-title'>
|
<div>
|
||||||
<h2>Добавить отчет</h2>
|
<div className='report__head'>
|
||||||
<h3>Дата заполнения отчета:</h3>
|
<Link className='calendar__back' to={`/profile/calendar`}>
|
||||||
|
<img src={arrow} alt=''/><p>Вернуться</p>
|
||||||
|
</Link>
|
||||||
</div>
|
</div>
|
||||||
<div className='report-form__block-img'>
|
</div>
|
||||||
<img
|
|
||||||
className='report-form__calendar-icon'
|
<div className='report-form__content'>
|
||||||
src={calendarIcon}
|
<div className='report-form__block'>
|
||||||
alt=''
|
<div className='report-form__block-title'>
|
||||||
/>
|
<h2>Добавление отчета за день</h2>
|
||||||
{currentMonthAndDayReportPage()}
|
<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>
|
||||||
<div className='report-form__task-list'>
|
|
||||||
<img src={ellipse} alt='' />
|
<div className='row'>
|
||||||
<span>Какие задачи были выполнены?</span>
|
<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>
|
</div>
|
||||||
</div>
|
<div className='row'>
|
||||||
|
<div className='col-12'>
|
||||||
<div className='row'>
|
<div className='report-form__footer'>
|
||||||
<div className='col-8'>
|
<button className='report-form__footer-btn' onClick={() => handler()}>
|
||||||
<div className='report-form__task-header'>
|
{isFetching ? <Loader/> : 'Отправить'}
|
||||||
<p className='report-form__task-title--description'>
|
</button>
|
||||||
Краткое описание задачи
|
<p className='report-form__footer-text'>
|
||||||
</p>
|
Всего за день : <span>{totalHours} часов</span>
|
||||||
<p className='report-form__task-title--hours'>Количество часов</p>
|
</p>
|
||||||
</div>
|
{reportSuccess &&
|
||||||
|
|
||||||
{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 &&
|
|
||||||
<p className='report-form__footer-done'>Отчет отправлен</p>
|
<p className='report-form__footer-done'>Отчет отправлен</p>
|
||||||
}
|
}
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
<Footer/>
|
||||||
</section>
|
</section>
|
||||||
)
|
)
|
||||||
}
|
};
|
||||||
|
|
||||||
export default ReportForm
|
export default ReportForm
|
||||||
|
@ -1,9 +1,51 @@
|
|||||||
.report-form {
|
.report-form {
|
||||||
|
background: #F1F1F1;
|
||||||
|
height: 100%;
|
||||||
|
min-height: 100vh;
|
||||||
|
font-family: "LabGrotesque", sans-serif;
|
||||||
|
|
||||||
|
.container {
|
||||||
|
max-width: 1160px;
|
||||||
|
margin-top: 23px;
|
||||||
|
|
||||||
|
@media (max-width: 570px) {
|
||||||
|
margin-top: 0;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
&__content {
|
||||||
|
background: #FFFFFF;
|
||||||
|
border-radius: 12px;
|
||||||
|
margin: 25px 0 80px;
|
||||||
|
padding: 50px 40px;
|
||||||
|
}
|
||||||
|
|
||||||
|
.report__head {
|
||||||
|
margin-top: 20px;
|
||||||
|
|
||||||
|
a {
|
||||||
|
display: flex;
|
||||||
|
align-items: center;
|
||||||
|
grid-column-gap: 30px;
|
||||||
|
column-gap: 30px;
|
||||||
|
margin-top: 20px;
|
||||||
|
cursor: pointer;
|
||||||
|
text-decoration: none;
|
||||||
|
|
||||||
|
p {
|
||||||
|
margin-bottom: 0;
|
||||||
|
font-size: 14px;
|
||||||
|
line-height: 32px;
|
||||||
|
font-weight: 500;
|
||||||
|
color: black;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
&__block-title {
|
&__block-title {
|
||||||
margin-top: 76px;
|
|
||||||
|
|
||||||
h2 {
|
h2 {
|
||||||
color: #282828;
|
color: #52B709;
|
||||||
font-family: 'GT Eesti Pro Display';
|
font-family: 'GT Eesti Pro Display';
|
||||||
font-size: 3.3em;
|
font-size: 3.3em;
|
||||||
font-weight: 700;
|
font-weight: 700;
|
||||||
|
@ -1,90 +1,87 @@
|
|||||||
import React, { useState } from 'react'
|
import React, {useState} from 'react'
|
||||||
import { useSelector, useDispatch } from 'react-redux'
|
import {useSelector, useDispatch} from 'react-redux'
|
||||||
import Select from 'react-select'
|
import Select from 'react-select'
|
||||||
import { Loader } from '../Loader/Loader'
|
import {Loader} from '../Loader/Loader'
|
||||||
import style from './TagSelect.module.css'
|
import {useRequest} from "../../hooks/useRequest";
|
||||||
import {
|
import {
|
||||||
selectedItems,
|
selectedItems,
|
||||||
selectItems,
|
selectItems,
|
||||||
selectTags,
|
selectTags,
|
||||||
filteredCandidates,
|
filteredCandidates,
|
||||||
setPositionId,
|
setPositionId
|
||||||
auth
|
|
||||||
} from '../../redux/outstaffingSlice'
|
} from '../../redux/outstaffingSlice'
|
||||||
import { fetchGet } from '../../server/server'
|
|
||||||
import { useHistory } from 'react-router-dom'
|
import style from './TagSelect.module.css'
|
||||||
import { getRole } from '../../redux/roleSlice'
|
|
||||||
|
|
||||||
const TagSelect = () => {
|
const TagSelect = () => {
|
||||||
const history = useHistory
|
|
||||||
const role = useSelector(getRole)
|
|
||||||
const [searchLoading, setSearchLoading] = useState(false)
|
|
||||||
const dispatch = useDispatch()
|
|
||||||
|
|
||||||
const itemsArr = useSelector(selectItems)
|
const [searchLoading, setSearchLoading] = useState(false);
|
||||||
|
const dispatch = useDispatch();
|
||||||
|
|
||||||
const tagsArr = useSelector(selectTags)
|
const {apiRequest} = useRequest();
|
||||||
|
|
||||||
const handleSubmit = ({ dispatch, setSearchLoading }) => {
|
const itemsArr = useSelector(selectItems);
|
||||||
setSearchLoading(true)
|
const tagsArr = useSelector(selectTags);
|
||||||
|
|
||||||
dispatch(setPositionId(null))
|
const handleSubmit = ({dispatch, setSearchLoading}) => {
|
||||||
const filterItemsId = itemsArr.map((item) => item.id).join()
|
setSearchLoading(true);
|
||||||
|
|
||||||
fetchGet({
|
dispatch(setPositionId(null));
|
||||||
link: `${process.env.REACT_APP_API_URL}/api/profile?skills=`,
|
const filterItemsId = itemsArr.map((item) => item.id).join();
|
||||||
params: filterItemsId,
|
const params = filterItemsId ? {skill: filterItemsId} : '';
|
||||||
history,
|
|
||||||
role,
|
|
||||||
logout: () => dispatch(auth(false))
|
apiRequest('/profile', {
|
||||||
|
params: {...params, limit: 1000},
|
||||||
}).then((el) => {
|
}).then((el) => {
|
||||||
dispatch(filteredCandidates(el))
|
dispatch(filteredCandidates(el));
|
||||||
setSearchLoading(false)
|
setSearchLoading(false)
|
||||||
})
|
})
|
||||||
|
|
||||||
// dispatch(selectedItems([]));
|
// dispatch(selectedItems([]));
|
||||||
}
|
};
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<>
|
<>
|
||||||
<section className={style.search}>
|
<section className={style.search}>
|
||||||
<div className='row'>
|
<div className='row'>
|
||||||
<div className='col-12'>
|
<div className='col-12'>
|
||||||
<h2 className={style.search__title}>
|
<h2 className={style.search__title}>
|
||||||
Найти специалиста по навыкам
|
Найти специалиста по навыкам
|
||||||
</h2>
|
</h2>
|
||||||
<div className={style.search__box}>
|
<div className={style.search__box}>
|
||||||
<Select
|
<Select
|
||||||
value={itemsArr}
|
value={itemsArr}
|
||||||
onChange={(value) => dispatch(selectedItems(value))}
|
onChange={(value) => {console.log(value) ;return dispatch(selectedItems(value))}}
|
||||||
isMulti
|
isMulti
|
||||||
name='tags'
|
name='tags'
|
||||||
className={style.select}
|
className={style.select}
|
||||||
classNamePrefix={style.select}
|
classNamePrefix={style.select}
|
||||||
options={
|
options={
|
||||||
tagsArr &&
|
tagsArr &&
|
||||||
tagsArr.flat().map((item) => {
|
tagsArr.flat().map((item) => {
|
||||||
return {
|
return {
|
||||||
id: item.id,
|
id: item.id,
|
||||||
value: item.value,
|
value: item.value,
|
||||||
label: item.value
|
label: item.value
|
||||||
|
}
|
||||||
|
})
|
||||||
}
|
}
|
||||||
})
|
/>
|
||||||
}
|
<button
|
||||||
/>
|
onClick={() => handleSubmit({dispatch, setSearchLoading})}
|
||||||
<button
|
type='submit'
|
||||||
onClick={() => handleSubmit({ dispatch, setSearchLoading })}
|
className={style.search__submit}
|
||||||
type='submit'
|
>
|
||||||
className={style.search__submit}
|
{searchLoading ? <Loader width={30} height={30}/> : 'Поиск'}
|
||||||
>
|
</button>
|
||||||
{searchLoading ? <Loader width={30} height={30} /> : 'Поиск'}
|
</div>
|
||||||
</button>
|
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</section>
|
||||||
</section>
|
</>
|
||||||
</>
|
|
||||||
)
|
)
|
||||||
}
|
};
|
||||||
|
|
||||||
export default TagSelect
|
export default TagSelect
|
||||||
|
@ -1,102 +1,101 @@
|
|||||||
import React, { useEffect, useState } from 'react';
|
import React, {useEffect, useState} from 'react';
|
||||||
import { ContentTitle } from "../ContentTitle/ContentTitle"
|
import {Link} from "react-router-dom"
|
||||||
import { ContentButton } from "../ContentButton/ContentButton"
|
import {ContentTitle} from "../ContentTitle/ContentTitle"
|
||||||
import { BookkeepingFormField } from "../BookkeepingFormField/BookkeepingFormField"
|
import {ContentButton} from "../ContentButton/ContentButton"
|
||||||
import { BookkepingSelect } from '../BookkepingSelect/BookkepingSelect';
|
import {BookkeepingFormField} from "../BookkeepingFormField/BookkeepingFormField"
|
||||||
import { BookkepingInput } from '../BookkepingInput/BookkepingInput';
|
import {BookkepingSelect} from '../BookkepingSelect/BookkepingSelect';
|
||||||
import { fetchGet } from '../../../../server/server'
|
import {BookkepingInput} from '../BookkepingInput/BookkepingInput';
|
||||||
import { Link } from "react-router-dom"
|
|
||||||
|
import {useRequest} from "../../../../hooks/useRequest";
|
||||||
|
|
||||||
import "./actContent.css"
|
import "./actContent.css"
|
||||||
|
|
||||||
export const ActContent = ()=> {
|
export const ActContent = () => {
|
||||||
|
|
||||||
const [templates, setTemplates] = useState([])
|
|
||||||
const [selectedTemplate, setSelectedTemplate] = useState()
|
|
||||||
const [templatedFields, setTemplatedFields] = useState([])
|
|
||||||
|
|
||||||
useEffect(() => {
|
const [templates, setTemplates] = useState([]);
|
||||||
fetchGet({
|
const [selectedTemplate, setSelectedTemplate] = useState();
|
||||||
link: `${process.env.REACT_APP_API_URL}/api/template/get-template-list`,
|
const [templatedFields, setTemplatedFields] = useState([]);
|
||||||
}).then((res) => {
|
|
||||||
setTemplates(res)
|
|
||||||
})
|
|
||||||
}, [])
|
|
||||||
|
|
||||||
useEffect(() => {
|
const {apiRequest} = useRequest();
|
||||||
if (selectedTemplate === undefined) {
|
|
||||||
return
|
useEffect(() => {
|
||||||
}
|
apiRequest('/template/get-template-list')
|
||||||
fetchGet({
|
.then(res => setTemplates(res))
|
||||||
link: `${process.env.REACT_APP_API_URL}/api/template/get-template-fields?template_id=${selectedTemplate}`,
|
}, []);
|
||||||
})
|
|
||||||
.then((res) => {
|
useEffect(() => {
|
||||||
setTemplatedFields(res[0].templateDocumentFields)
|
if (selectedTemplate === undefined) {
|
||||||
})
|
return
|
||||||
}, [selectedTemplate])
|
}
|
||||||
|
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(
|
<BookkeepingFormField
|
||||||
<div>
|
title="Шаблон документа"
|
||||||
<div className="content__info">
|
Component={BookkepingSelect}
|
||||||
<ContentTitle title="Создание акта" description="# Описание" />
|
innerComponentProps={{
|
||||||
<div className="content__info-main">
|
onSelect: setSelectedTemplate,
|
||||||
<form className='contract'>
|
textField: "title",
|
||||||
<div className="contract__create">
|
options: templates,
|
||||||
<div className="contract__title">Создание акта №</div>
|
defaultIndexSelected: 0,
|
||||||
<input type="text" className="contract__number" placeholder="#" />
|
}}
|
||||||
<span>от</span>
|
action={{
|
||||||
<input type="date" className="contract__date" />
|
text: "Добавить свой шаблон",
|
||||||
</div>
|
method: () => {
|
||||||
|
}
|
||||||
|
}}
|
||||||
|
/>
|
||||||
|
|
||||||
<BookkeepingFormField title="Шаблон документа"
|
{templatedFields.map((field, index) =>
|
||||||
Component={BookkepingSelect}
|
<BookkeepingFormField
|
||||||
innerComponentProps={{
|
title={field.field.title} key={index}
|
||||||
onSelect: setSelectedTemplate,
|
Component={BookkepingInput}
|
||||||
textField: "title",
|
innerComponentProps={{
|
||||||
options: templates,
|
placeholder: "Введите данные",
|
||||||
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">
|
<div className="content__btn-list">
|
||||||
<ContentButton styles={{ width: "290px",
|
<ContentButton styles={{
|
||||||
height: "75px",
|
width: "290px",
|
||||||
boxShadow: "6px 5px 20px rgba(182, 75, 62, 0.21)",
|
height: "75px",
|
||||||
borderRadius: "38px",
|
boxShadow: "6px 5px 20px rgba(182, 75, 62, 0.21)",
|
||||||
backgroundColor: "#b64b3e",
|
borderRadius: "38px",
|
||||||
border: "none",
|
backgroundColor: "#b64b3e",
|
||||||
color: "#ffffff",
|
border: "none",
|
||||||
}}>Сохранить</ContentButton>
|
color: "#ffffff",
|
||||||
<Link to="/documents" className="link-act-button">
|
}}>Сохранить</ContentButton>
|
||||||
<div className='act-Button'>
|
<Link to="/documents" className="link-act-button">
|
||||||
<ContentButton styles={{color: "#282828",
|
<div className='act-Button'>
|
||||||
marginLeft: "40px",
|
<ContentButton styles={{
|
||||||
background: "none",
|
color: "#282828",
|
||||||
border: "none"
|
marginLeft: "40px",
|
||||||
}}>Отменить</ContentButton>
|
background: "none",
|
||||||
</div>
|
border: "none"
|
||||||
</Link>
|
}}>Отменить</ContentButton>
|
||||||
</div>
|
</div>
|
||||||
</form>
|
</Link>
|
||||||
</div>
|
</div>
|
||||||
|
</form>
|
||||||
|
</div>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
)
|
)
|
||||||
}
|
};
|
@ -1,97 +1,98 @@
|
|||||||
import React, { useEffect, useState } from "react";
|
import React, {useEffect, useState} from "react";
|
||||||
import { ContentTitle } from "../ContentTitle/ContentTitle"
|
import {ContentTitle} from "../ContentTitle/ContentTitle"
|
||||||
import { ContentButton } from "../ContentButton/ContentButton"
|
import {ContentButton} from "../ContentButton/ContentButton"
|
||||||
import { BookkeepingFormField } from "../BookkeepingFormField/BookkeepingFormField"
|
import {BookkeepingFormField} from "../BookkeepingFormField/BookkeepingFormField"
|
||||||
import { BookkepingSelect } from '../BookkepingSelect/BookkepingSelect';
|
import {BookkepingSelect} from '../BookkepingSelect/BookkepingSelect';
|
||||||
import { BookkepingInput } from '../BookkepingInput/BookkepingInput';
|
import {BookkepingInput} from '../BookkepingInput/BookkepingInput';
|
||||||
import { fetchGet } from '../../../../server/server'
|
import {Link} from "react-router-dom"
|
||||||
import { Link } from "react-router-dom"
|
import {useRequest} from "../../../../hooks/useRequest";
|
||||||
|
|
||||||
export const ContractContent = () => {
|
export const ContractContent = () => {
|
||||||
|
|
||||||
const [templates, setTemplates] = useState([])
|
const [templates, setTemplates] = useState([]);
|
||||||
const [selectedTemplate, setSelectedTemplate] = useState()
|
const [selectedTemplate, setSelectedTemplate] = useState();
|
||||||
const [templatedFields, setTemplatedFields] = useState([])
|
const [templatedFields, setTemplatedFields] = useState([]);
|
||||||
|
|
||||||
useEffect(() => {
|
|
||||||
fetchGet({
|
|
||||||
link: `${process.env.REACT_APP_API_URL}/api/document/get-document-list`,
|
|
||||||
}).then((res) => {
|
|
||||||
setTemplates(res)
|
|
||||||
})
|
|
||||||
}, [])
|
|
||||||
|
|
||||||
useEffect(() => {
|
const {apiRequest} = useRequest();
|
||||||
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])
|
|
||||||
|
|
||||||
return (
|
useEffect(() => {
|
||||||
<div>
|
apiRequest(`/document/get-document-list`)
|
||||||
<div className="content__info">
|
.then(res => setTemplates(res))
|
||||||
<ContentTitle title="Создание договора" description="# Описание" />
|
}, []);
|
||||||
<div className="content__info-main">
|
|
||||||
|
|
||||||
|
|
||||||
<form className='contract'>
|
useEffect(() => {
|
||||||
<div className="contract__create">
|
if (selectedTemplate === undefined) {
|
||||||
<div className="contract__title">Создание договора №</div>
|
return
|
||||||
<input type="text" className="contract__number" placeholder="#" />
|
}
|
||||||
<span>от</span>
|
apiRequest(`/document/get-document?document_id=${selectedTemplate}`)
|
||||||
<input type="date" className="contract__date" />
|
.then(res => setTemplatedFields(res[0].templateDocumentFields)
|
||||||
</div>
|
)
|
||||||
<BookkeepingFormField title="Шаблон документа"
|
}, [selectedTemplate]);
|
||||||
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">
|
return (
|
||||||
<ContentButton styles={{ width: "290px",
|
<div>
|
||||||
height: "75px",
|
<div className="content__info">
|
||||||
boxShadow: "6px 5px 20px rgba(182, 75, 62, 0.21)",
|
<ContentTitle title="Создание договора" description="# Описание"/>
|
||||||
borderRadius: "38px",
|
<div className="content__info-main">
|
||||||
backgroundColor: "#b64b3e",
|
|
||||||
border: "none",
|
|
||||||
color: "#ffffff",
|
<form className='contract'>
|
||||||
}}>Сохранить</ContentButton>
|
<div className="contract__create">
|
||||||
<Link to="/documents" className="link-act-button">
|
<div className="contract__title">Создание договора №</div>
|
||||||
<div className='act-Button'>
|
<input type="text" className="contract__number" placeholder="#"/>
|
||||||
<ContentButton styles={{color: "#282828",
|
<span>от</span>
|
||||||
marginLeft: "40px",
|
<input type="date" className="contract__date"/>
|
||||||
background: "none",
|
</div>
|
||||||
border: "none"
|
<BookkeepingFormField
|
||||||
}}>Отменить</ContentButton>
|
title="Шаблон документа"
|
||||||
</div>
|
Component={BookkepingSelect}
|
||||||
</Link>
|
innerComponentProps={{
|
||||||
</div>
|
onSelect: setSelectedTemplate,
|
||||||
</form>
|
textField: "title",
|
||||||
</div>
|
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>
|
</div>
|
||||||
)
|
)
|
||||||
}
|
};
|
@ -6,7 +6,7 @@ import {selectedTest, selectUserInfo} from "../../../redux/quizSlice";
|
|||||||
|
|
||||||
export const HeaderPageTestsQuiz = ({isVisibilityButton}) => {
|
export const HeaderPageTestsQuiz = ({isVisibilityButton}) => {
|
||||||
|
|
||||||
const test = useSelector(selectedTest)
|
const test = useSelector(selectedTest);
|
||||||
const userInfo = useSelector(selectUserInfo);
|
const userInfo = useSelector(selectUserInfo);
|
||||||
|
|
||||||
return (
|
return (
|
||||||
@ -23,8 +23,8 @@ export const HeaderPageTestsQuiz = ({isVisibilityButton}) => {
|
|||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
{isVisibilityButton &&
|
{isVisibilityButton &&
|
||||||
<Link to={'/quiz-instruction'} className='quiz-btn quiz-btn_restriction'>Пройти</Link>}
|
<Link to={'/quiz/instruction'} className='quiz-btn quiz-btn_restriction'>Пройти</Link>}
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
)
|
)
|
||||||
}
|
};
|
@ -1,50 +1,46 @@
|
|||||||
import {useEffect} from 'react'
|
import React, {useEffect} from 'react'
|
||||||
import {useDispatch} from 'react-redux'
|
import {useDispatch, useSelector} from 'react-redux'
|
||||||
import {useSelector} from 'react-redux'
|
import {selectUserInfo, setQuestionnairesList, setUserInfo} from "../../../redux/quizSlice";
|
||||||
import {fetchGet} from '../../../server/server'
|
import {useRequest} from "../../../hooks/useRequest";
|
||||||
import './quiz.scss'
|
import './quiz.scss'
|
||||||
import {selectUserInfo, setQuestionnairesList, setUserInfo,} from "../../../redux/quizSlice";
|
|
||||||
|
|
||||||
export const HeaderQuiz = ({header}) => {
|
export const HeaderQuiz = ({header}) => {
|
||||||
|
|
||||||
const dispatch = useDispatch()
|
const dispatch = useDispatch();
|
||||||
const userId = localStorage.getItem('id');
|
const userId = localStorage.getItem('id');
|
||||||
const userInfo = useSelector(selectUserInfo);
|
const userInfo = useSelector(selectUserInfo);
|
||||||
|
|
||||||
useEffect(() => {
|
const {apiRequest} = useRequest();
|
||||||
dispatch(setUserInfo(userId))
|
|
||||||
}, [dispatch])
|
|
||||||
|
|
||||||
useEffect(() => {
|
useEffect(() => {
|
||||||
fetchGet({
|
dispatch(setUserInfo(userId))
|
||||||
link: `${process.env.REACT_APP_API_URL}/api/user-questionnaire/questionnaires-list?user_id=${userId}`,
|
}, [dispatch]);
|
||||||
Origin: `${process.env.REACT_APP_BASE_URL}`,
|
|
||||||
}
|
|
||||||
).then(response => {
|
|
||||||
dispatch(setQuestionnairesList(response))
|
|
||||||
})
|
|
||||||
}, [dispatch])
|
|
||||||
|
|
||||||
return (
|
useEffect(() => {
|
||||||
<div>
|
apiRequest(`/user-questionnaire/questionnaires-list?user_id=${userId}`)
|
||||||
{ userInfo?.status === 500 ? <div className="error-msg">{userInfo.message}</div> :
|
.then(res => dispatch(setQuestionnairesList(res)))
|
||||||
<div className="header-quiz">
|
}, [dispatch]);
|
||||||
<div className="header-quiz__container">
|
|
||||||
{!userInfo ? <h2>Loading...</h2> :
|
return (
|
||||||
<>
|
<div>
|
||||||
{header && <h2 className={'header-quiz__title-main'}>Добрый день, {userInfo.fio}</h2>}
|
{userInfo?.status === 500 ? <div className="error-msg">{userInfo.message}</div> :
|
||||||
<div className="header-quiz__body header-quiz__body_interjacent">
|
<div className="header-quiz">
|
||||||
<div className="header-quiz__avatar">
|
<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}/>
|
<img src={userInfo.photo} alt={userInfo.photo}/>
|
||||||
</div>
|
</div>
|
||||||
<div className="header-quiz__name-user">{userInfo.fio}</div>
|
<div className="header-quiz__name-user">{userInfo.fio}</div>
|
||||||
<div className="header-quiz__title">{userInfo.position_name}</div>
|
<div className="header-quiz__title">{userInfo.position_name}</div>
|
||||||
</div>
|
</div>
|
||||||
</>
|
</>
|
||||||
}
|
}
|
||||||
|
</div>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
}
|
||||||
}
|
</div>
|
||||||
</div>
|
)
|
||||||
)
|
};
|
||||||
}
|
|
||||||
|
@ -1,27 +1,28 @@
|
|||||||
import {Link} from 'react-router-dom'
|
import {Link} from 'react-router-dom'
|
||||||
import {CodeSnippetlighter} from '../../../pages/CodeSnippetPage'
|
|
||||||
import comment from './../../../images/comment.jpg'
|
import comment from './../../../images/comment.jpg'
|
||||||
import './quiz.scss'
|
import './quiz.scss'
|
||||||
import {useEffect, useState} from "react";
|
import {useEffect, useState} from "react";
|
||||||
import {useSelector} from "react-redux";
|
import {useSelector} from "react-redux";
|
||||||
import {selectedTest} from "../../../redux/quizSlice";
|
import {selectedTest} from "../../../redux/quizSlice";
|
||||||
import {fetchGet} from "../../../server/server";
|
|
||||||
|
import {useRequest} from "../../../hooks/useRequest";
|
||||||
|
|
||||||
export const Instruction = () => {
|
export const Instruction = () => {
|
||||||
|
|
||||||
const [countQuestions, setCountQuestions] = useState(null)
|
const [countQuestions, setCountQuestions] = useState(null);
|
||||||
const test = useSelector(selectedTest)
|
const test = useSelector(selectedTest);
|
||||||
|
|
||||||
useEffect(async () => {
|
const {apiRequest} = useRequest();
|
||||||
|
|
||||||
const response = await fetchGet({
|
useEffect( () => {
|
||||||
link: `${process.env.REACT_APP_API_URL}/api/user-questionnaire/get-question-number?user_questionnaire_uuid=${test.uuid}`,
|
|
||||||
Origin: `${process.env.REACT_APP_BASE_URL}`,
|
|
||||||
}
|
|
||||||
)
|
|
||||||
setCountQuestions(response.question_number)
|
|
||||||
|
|
||||||
}, [])
|
apiRequest('/user-questionnaire/get-question-number', {
|
||||||
|
params: {user_questionnaire_uuid: test.uuid},
|
||||||
|
}
|
||||||
|
).then((res)=> setCountQuestions(res.question_number))
|
||||||
|
|
||||||
|
|
||||||
|
}, []);
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<div className="instruction">
|
<div className="instruction">
|
||||||
@ -36,7 +37,7 @@ export const Instruction = () => {
|
|||||||
e
|
e
|
||||||
lit, sed do eiusmod tempo
|
lit, sed do eiusmod tempo
|
||||||
</div>
|
</div>
|
||||||
<Link to="/quiz-test" className='instruction__btn quiz-btn quiz-btn_restriction'>Далее</Link>
|
<Link to="/quiz/test" className='instruction__btn quiz-btn quiz-btn_restriction'>Далее</Link>
|
||||||
<div className="instruction__info">
|
<div className="instruction__info">
|
||||||
<div className="instruction__icon">
|
<div className="instruction__icon">
|
||||||
<img src={comment} alt=""/>
|
<img src={comment} alt=""/>
|
||||||
@ -49,5 +50,5 @@ export const Instruction = () => {
|
|||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
)
|
)
|
||||||
}
|
};
|
||||||
|
|
||||||
|
@ -7,7 +7,7 @@ import {setSelectedTest} from "../../../redux/quizSlice";
|
|||||||
export const MyTestsQuiz = ({listTests}) => {
|
export const MyTestsQuiz = ({listTests}) => {
|
||||||
|
|
||||||
const formationEndingOfScore = (score) => {
|
const formationEndingOfScore = (score) => {
|
||||||
const lastNumber = score % 10
|
const lastNumber = score % 10;
|
||||||
if(score === 11 ||score === 12 ||score === 13 ||score === 14 ){
|
if(score === 11 ||score === 12 ||score === 13 ||score === 14 ){
|
||||||
return 'баллов'
|
return 'баллов'
|
||||||
}else if(lastNumber === 2 || lastNumber === 3 || lastNumber === 4 ){
|
}else if(lastNumber === 2 || lastNumber === 3 || lastNumber === 4 ){
|
||||||
@ -17,10 +17,10 @@ export const MyTestsQuiz = ({listTests}) => {
|
|||||||
}else{
|
}else{
|
||||||
return 'баллов'
|
return 'баллов'
|
||||||
}
|
}
|
||||||
}
|
};
|
||||||
|
|
||||||
const dispatch = useDispatch()
|
const dispatch = useDispatch();
|
||||||
const recordSelectedTest = (item) => dispatch(setSelectedTest(item))
|
const recordSelectedTest = (item) => dispatch(setSelectedTest(item));
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<div className="my-tests">
|
<div className="my-tests">
|
||||||
@ -35,10 +35,10 @@ export const MyTestsQuiz = ({listTests}) => {
|
|||||||
{item.questionnaire_title}
|
{item.questionnaire_title}
|
||||||
</h3>
|
</h3>
|
||||||
<div className="item-test__body test-data">
|
<div className="item-test__body test-data">
|
||||||
<Link to={'/quiz-interjacent'} className='quiz-btn'
|
<Link to={'/quiz/interjacent'} className='quiz-btn'
|
||||||
onClick={() => recordSelectedTest(item)}>Пройти</Link>
|
onClick={() => recordSelectedTest(item)}>Пройти</Link>
|
||||||
</div>
|
</div>
|
||||||
</article>
|
</article>;
|
||||||
case 2:
|
case 2:
|
||||||
return <article className="my-tests__item item-test" key={item.questionnaire_title}>
|
return <article className="my-tests__item item-test" key={item.questionnaire_title}>
|
||||||
<h3 className="item-test__name-test">
|
<h3 className="item-test__name-test">
|
||||||
@ -52,7 +52,7 @@ export const MyTestsQuiz = ({listTests}) => {
|
|||||||
<div className="test-data__hr"></div>
|
<div className="test-data__hr"></div>
|
||||||
<div className="test-data__score quiz-text">{item.score} {formationEndingOfScore(item.score)}</div>
|
<div className="test-data__score quiz-text">{item.score} {formationEndingOfScore(item.score)}</div>
|
||||||
</div>
|
</div>
|
||||||
</article>
|
</article>;
|
||||||
case 3:
|
case 3:
|
||||||
return <article className="my-tests__item item-test" key={item.questionnaire_title}>
|
return <article className="my-tests__item item-test" key={item.questionnaire_title}>
|
||||||
<h3 className="item-test__name-test">
|
<h3 className="item-test__name-test">
|
||||||
@ -61,7 +61,7 @@ export const MyTestsQuiz = ({listTests}) => {
|
|||||||
<div className="item-test__body test-data">
|
<div className="item-test__body test-data">
|
||||||
<div className='quiz-btn'>На проверке</div>
|
<div className='quiz-btn'>На проверке</div>
|
||||||
</div>
|
</div>
|
||||||
</article>
|
</article>;
|
||||||
default:
|
default:
|
||||||
break
|
break
|
||||||
}
|
}
|
||||||
@ -70,4 +70,4 @@ export const MyTestsQuiz = ({listTests}) => {
|
|||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
)
|
)
|
||||||
}
|
};
|
@ -1,5 +1,4 @@
|
|||||||
import { Link } from 'react-router-dom'
|
import React from 'react'
|
||||||
import avatar from './../../../images/medium_male.png'
|
|
||||||
import './quiz.scss'
|
import './quiz.scss'
|
||||||
|
|
||||||
export const Progressbar = ({indexQuestion, width}) => {
|
export const Progressbar = ({indexQuestion, width}) => {
|
||||||
@ -15,4 +14,4 @@ export const Progressbar = ({indexQuestion, width}) => {
|
|||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
)
|
)
|
||||||
}
|
};
|
@ -1,37 +1,35 @@
|
|||||||
import React, {useEffect, useState} from 'react';
|
import React, {useEffect, useState} from 'react';
|
||||||
import {useDispatch, useSelector} from "react-redux";
|
import {useDispatch, useSelector} from "react-redux";
|
||||||
import {fetchResultTest, selectedTest, selectResult} from "../../../redux/quizSlice";
|
import {fetchResultTest, selectedTest, selectResult} from "../../../redux/quizSlice";
|
||||||
import {fetchGet} from "../../../server/server";
|
import {useRequest} from "../../../hooks/useRequest";
|
||||||
|
|
||||||
|
|
||||||
export const Results = () => {
|
export const Results = () => {
|
||||||
const result = useSelector(selectResult)
|
const result = useSelector(selectResult);
|
||||||
const test = useSelector(selectedTest)
|
const test = useSelector(selectedTest);
|
||||||
const [maxScore, setMaxScore] = useState('')
|
const [maxScore, setMaxScore] = useState('');
|
||||||
const dispatch = useDispatch()
|
const dispatch = useDispatch();
|
||||||
|
const {apiRequest} = useRequest();
|
||||||
|
|
||||||
useEffect(async () => {
|
useEffect(() => {
|
||||||
dispatch(fetchResultTest(test.uuid))
|
dispatch(fetchResultTest(test.uuid));
|
||||||
const response = await fetchGet({
|
apiRequest(`/user-questionnaire/get-points-number?user_questionnaire_uuid=${test.uuid}`)
|
||||||
link: `${process.env.REACT_APP_API_URL}/api/user-questionnaire/get-points-number?user_questionnaire_uuid=${test.uuid}`,
|
.then((res) => setMaxScore(res.sum_point));
|
||||||
Origin: `${process.env.REACT_APP_BASE_URL}`,
|
|
||||||
}
|
|
||||||
)
|
|
||||||
setMaxScore(response.sum_point)
|
|
||||||
}, [])
|
|
||||||
|
|
||||||
return (
|
}, [apiRequest, dispatch, test]);
|
||||||
<div className={'result _container'}>
|
|
||||||
|
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">Благодарим за прохождение теста</div>
|
||||||
<div className="result__text">Ваш Результат: <span
|
<div className="result__text">Ваш Результат: <span
|
||||||
className="result__score">{result.score}</span> из {maxScore} </div>
|
className="result__score">{result.score}</span> из {maxScore} </div>
|
||||||
</div>
|
</div>
|
||||||
}
|
}
|
||||||
</div>
|
</div>
|
||||||
);
|
);
|
||||||
};
|
};
|
||||||
|
|
||||||
|
@ -1,149 +1,149 @@
|
|||||||
import React, {useEffect} from 'react'
|
import React, {useEffect, useState} from 'react'
|
||||||
import {useHistory} from "react-router"
|
import {useNavigate} from "react-router-dom"
|
||||||
import {CodeSnippetlighter} from '../../../pages/CodeSnippetPage'
|
import {useSelector, useDispatch} from 'react-redux'
|
||||||
import './quiz.scss'
|
|
||||||
import {useDispatch} from 'react-redux'
|
|
||||||
import {useState} from 'react'
|
|
||||||
import {
|
|
||||||
fetchGetAnswers,
|
|
||||||
selectAnswer,
|
|
||||||
selectedTest
|
|
||||||
} from '../../../redux/quizSlice'
|
|
||||||
import {useSelector} from 'react-redux'
|
|
||||||
import {Progressbar} from './ProgressbarQuiz'
|
|
||||||
import {fetchUserAnswersMany, fetchUserAnswerOne} from './../../../redux/quizSlice'
|
|
||||||
import {GetOptionTask} from './GetOptionTask'
|
|
||||||
import {fetchGet} from "../../../server/server";
|
|
||||||
|
|
||||||
|
import {useRequest} from "../../../hooks/useRequest";
|
||||||
|
import {Progressbar} from './ProgressbarQuiz'
|
||||||
|
import {GetOptionTask} from './GetOptionTask'
|
||||||
|
|
||||||
|
import {
|
||||||
|
fetchUserAnswersMany, fetchUserAnswerOne, fetchGetAnswers, selectAnswer, selectedTest
|
||||||
|
} from './../../../redux/quizSlice'
|
||||||
|
|
||||||
|
import './quiz.scss'
|
||||||
|
|
||||||
export const TaskQuiz = () => {
|
export const TaskQuiz = () => {
|
||||||
|
|
||||||
const history = useHistory();
|
const navigate = useNavigate();
|
||||||
const dispatch = useDispatch()
|
const dispatch = useDispatch();
|
||||||
const listAnswers = useSelector(selectAnswer)
|
|
||||||
const dataTest = useSelector(selectedTest)
|
|
||||||
const [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([])
|
|
||||||
|
|
||||||
useEffect(async () => {
|
const listAnswers = useSelector(selectAnswer);
|
||||||
const response = await fetchGet({
|
const dataTest = useSelector(selectedTest);
|
||||||
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 [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) => {
|
const {apiRequest} = useRequest();
|
||||||
e.preventDefault()
|
|
||||||
|
|
||||||
//Проверка на валидацию ответов
|
const id = localStorage.getItem('id');
|
||||||
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;
|
|
||||||
}
|
|
||||||
|
|
||||||
//Проверка на существование следующего запроса
|
useEffect(() => {
|
||||||
if (index < questions.length - 1) {
|
apiRequest(`/question/get-questions?uuid=${dataTest.uuid}`)
|
||||||
await dispatch(fetchGetAnswers(questions[index + 1].id))
|
.then((response) => {
|
||||||
setIndex(prev => prev >= questions.length - 1 ? prev : prev + 1)
|
console.log(response)
|
||||||
setStripValue((prev => prev + (100 / questions.length)))
|
setQuestions(response);
|
||||||
setCheckedValues([]);
|
dispatch(fetchGetAnswers(response[0].id));
|
||||||
setInputValue('')
|
setStripValue((+index + 1) * 100 / response.length)
|
||||||
} else {
|
})
|
||||||
history.push(`/quiz-result`)
|
}, [dispatch]);
|
||||||
alert("Тест пройден!")
|
|
||||||
}
|
|
||||||
|
|
||||||
} else {
|
const nextQuestion = async (e) => {
|
||||||
alert("Вы не ответили на вопрос")
|
e.preventDefault();
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
const handleChange = (e) => {
|
//Проверка на валидацию ответов
|
||||||
const checked = e.target.checked;
|
if (checkedValues.length || inputValue) {
|
||||||
switch (questions[index].question_type_id) {
|
switch (questions[index].question_type_id) {
|
||||||
case '3':
|
case '3':
|
||||||
checked ? setCheckedValues(prev => [...prev, {
|
dispatch(fetchUserAnswersMany(checkedValues));
|
||||||
user_id: id,
|
break;
|
||||||
user_questionnaire_uuid: dataTest.uuid,
|
case '2':
|
||||||
question_id: questions[index].id,
|
case '1':
|
||||||
response_body: e.target.value
|
case '4':
|
||||||
}]) :
|
dispatch(fetchUserAnswerOne(checkedValues));
|
||||||
setCheckedValues(prev => [...prev.filter(item => item.response_body !== e.target.value)])
|
break;
|
||||||
break
|
default:
|
||||||
case '1':
|
break;
|
||||||
case '2':
|
|
||||||
case '4':
|
|
||||||
setCheckedValues([{
|
|
||||||
user_id: id,
|
|
||||||
user_questionnaire_uuid: dataTest.uuid,
|
|
||||||
question_id: questions[index].id,
|
|
||||||
response_body: e.target.value
|
|
||||||
}])
|
|
||||||
}
|
}
|
||||||
};
|
|
||||||
|
//Проверка на существование следующего запроса
|
||||||
|
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 (
|
return (
|
||||||
<React.StrictMode>
|
<React.StrictMode>
|
||||||
<Progressbar indexQuestion={index + 1} width={stripValue}/>
|
<Progressbar indexQuestion={index + 1} width={stripValue}/>
|
||||||
<div className="task">
|
<div className="task">
|
||||||
{!questions.length || !stripValue || !listAnswers.length ?
|
{!questions.length || !stripValue || !listAnswers.length ?
|
||||||
<h1 className={'_container'} style={{display: "block"}}>Loading....</h1>
|
<h1 className={'_container'} style={{display: "block"}}>Loading....</h1>
|
||||||
:
|
:
|
||||||
<div className="task__container">
|
<div className="task__container">
|
||||||
<div className="task__code code">
|
<div className="task__code code">
|
||||||
{/* <CodeSnippetlighter /> */}
|
{/* <CodeSnippetlighter /> */}
|
||||||
</div>
|
</div>
|
||||||
<h3 className="task__title quiz-title_h3">{questions[index].question_body}</h3>
|
<h3 className="task__title quiz-title_h3">{questions[index].question_body}</h3>
|
||||||
<div className="task__body">
|
<div className="task__body">
|
||||||
<form className='task__form form-task'>
|
<form className='task__form form-task'>
|
||||||
{
|
{
|
||||||
questions[index].question_type_id === 1 ?
|
questions[index].question_type_id === 1 ?
|
||||||
<GetOptionTask
|
<GetOptionTask
|
||||||
type={1}
|
type={1}
|
||||||
inputValue={checkedValues.length ? checkedValues[0].response_body : ''}
|
inputValue={checkedValues.length ? checkedValues[0].response_body : ''}
|
||||||
handleChange={handleChange}
|
handleChange={handleChange}
|
||||||
/>
|
/>
|
||||||
:
|
:
|
||||||
listAnswers.map((answer) => (
|
listAnswers.map((answer) => (
|
||||||
<GetOptionTask
|
<GetOptionTask
|
||||||
key={answer.id}
|
key={answer.id}
|
||||||
type={questions[index].question_type_id}
|
type={questions[index].question_type_id}
|
||||||
handleChange={handleChange}
|
handleChange={handleChange}
|
||||||
answer={answer}
|
answer={answer}
|
||||||
/>
|
/>
|
||||||
))
|
))
|
||||||
}
|
}
|
||||||
<div className="form-task__buttons">
|
<div className="form-task__buttons">
|
||||||
{questions.length !== index + 1 &&
|
{questions.length !== index + 1 &&
|
||||||
<button type='submit' className='quiz-btn'
|
<button type='submit' className='quiz-btn'
|
||||||
onClick={(e) => nextQuestion(e)}>Далее</button>}
|
onClick={(e) => nextQuestion(e)}>Далее</button>}
|
||||||
{questions.length === index + 1 && <button onClick={(e) => nextQuestion(e)}
|
{questions.length === index + 1 &&
|
||||||
className='quiz-btn quiz-btn_dark-green'>Завершить</button>}
|
<button onClick={(e) => nextQuestion(e)}
|
||||||
</div>
|
className='quiz-btn quiz-btn_dark-green'>Завершить</button>}
|
||||||
</form>
|
</div>
|
||||||
|
</form>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
}
|
}
|
||||||
</div>
|
</div>
|
||||||
</React.StrictMode>
|
</React.StrictMode>
|
||||||
)
|
)
|
||||||
}
|
};
|
||||||
|
BIN
src/fonts/LabGrotesque-Bold.eot
Normal file
BIN
src/fonts/LabGrotesque-Bold.ttf
Normal file
BIN
src/fonts/LabGrotesque-Bold.woff
Normal file
BIN
src/fonts/LabGrotesque-Bold.woff2
Normal file
BIN
src/fonts/LabGrotesque-Light.eot
Normal file
BIN
src/fonts/LabGrotesque-Light.ttf
Normal file
BIN
src/fonts/LabGrotesque-Light.woff
Normal file
BIN
src/fonts/LabGrotesque-Light.woff2
Normal file
BIN
src/fonts/LabGrotesque-Medium.eot
Normal file
BIN
src/fonts/LabGrotesque-Medium.ttf
Normal file
BIN
src/fonts/LabGrotesque-Medium.woff
Normal file
BIN
src/fonts/LabGrotesque-Medium.woff2
Normal file
BIN
src/fonts/LabGrotesque-Regular.eot
Normal file
BIN
src/fonts/LabGrotesque-Regular.ttf
Normal file
BIN
src/fonts/LabGrotesque-Regular.woff
Normal file
BIN
src/fonts/LabGrotesque-Regular.woff2
Normal file
@ -353,3 +353,44 @@
|
|||||||
font-weight: bold;
|
font-weight: bold;
|
||||||
font-style: normal;
|
font-style: normal;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
|
@font-face {
|
||||||
|
font-family: 'LabGrotesque';
|
||||||
|
src: url('LabGrotesque-Regular.eot');
|
||||||
|
src: local('LabGrotesque-Regular'), local('LabGrotesque-Regular'),
|
||||||
|
url('LabGrotesque-Regular.eot?#iefix') format('embedded-opentype'),
|
||||||
|
url('LabGrotesque-Regular.woff2') format('woff2'), url('LabGrotesque-Regular.woff') format('woff'),
|
||||||
|
url('LabGrotesque-Regular.ttf') format('truetype');
|
||||||
|
font-weight: 400;
|
||||||
|
}
|
||||||
|
|
||||||
|
@font-face {
|
||||||
|
font-family: 'LabGrotesque';
|
||||||
|
src: url('LabGrotesque-Medium.eot');
|
||||||
|
src: local('LabGrotesque-Medium'), local('LabGrotesque-Medium'),
|
||||||
|
url('LabGrotesque-Medium.eot?#iefix') format('embedded-opentype'),
|
||||||
|
url('LabGrotesque-Medium.woff2') format('woff2'), url('LabGrotesque-Medium.woff') format('woff'),
|
||||||
|
url('LabGrotesque-Medium.ttf') format('truetype');
|
||||||
|
font-weight: 500;
|
||||||
|
}
|
||||||
|
|
||||||
|
@font-face {
|
||||||
|
font-family: 'LabGrotesque';
|
||||||
|
src: url('LabGrotesque-Bold.eot');
|
||||||
|
src: local('LabGrotesque-Bold'), local('LabGrotesque-Bold'),
|
||||||
|
url('LabGrotesque-Bold.eot?#iefix') format('embedded-opentype'),
|
||||||
|
url('LabGrotesque-Bold.woff2') format('woff2'), url('LabGrotesque-Bold.woff') format('woff'),
|
||||||
|
url('LabGrotesque-Bold.ttf') format('truetype');
|
||||||
|
font-weight: 700;
|
||||||
|
}
|
||||||
|
|
||||||
|
@font-face {
|
||||||
|
font-family: 'LabGrotesque';
|
||||||
|
src: url('LabGrotesque-Light.eot');
|
||||||
|
src: local('LabGrotesque-Light'), local('LabGrotesque-Light'),
|
||||||
|
url('LabGrotesque-Light.eot?#iefix') format('embedded-opentype'),
|
||||||
|
url('LabGrotesque-Light.woff2') format('woff2'), url('LabGrotesque-Light.woff') format('woff'),
|
||||||
|
url('LabGrotesque-Light.ttf') format('truetype');
|
||||||
|
font-weight: 300;
|
||||||
|
}
|
||||||
|
37
src/helper.js
Normal file
@ -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
@ -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
@ -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
After Width: | Height: | Size: 240 B |
BIN
src/images/gitItemImg.png
Normal file
After Width: | Height: | Size: 1.0 KiB |
BIN
src/images/paymentIcon.png
Normal file
After Width: | Height: | Size: 966 B |
BIN
src/images/reports.png
Normal file
After Width: | Height: | Size: 845 B |
BIN
src/images/settingIcon.png
Normal file
After Width: | Height: | Size: 836 B |
BIN
src/images/summaryIcon.png
Normal file
After Width: | Height: | Size: 1.4 KiB |
BIN
src/images/timerIcon.png
Normal file
After Width: | Height: | Size: 1.1 KiB |
@ -1,6 +1,6 @@
|
|||||||
import React from 'react'
|
import React from 'react'
|
||||||
|
|
||||||
import { AuthBox } from '../AuthBox/AuthBox'
|
import { AuthBox } from '../../components/AuthBox/AuthBox'
|
||||||
|
|
||||||
import { useSelector } from 'react-redux'
|
import { useSelector } from 'react-redux'
|
||||||
import arrow from '../../images/arrow__login_page.png'
|
import arrow from '../../images/arrow__login_page.png'
|
||||||
@ -10,16 +10,18 @@ import text from '../../images/Body_Text.png'
|
|||||||
import vector from '../../images/Vector_Smart_Object.png'
|
import vector from '../../images/Vector_Smart_Object.png'
|
||||||
|
|
||||||
import { selectAuth } from '../../redux/outstaffingSlice'
|
import { selectAuth } from '../../redux/outstaffingSlice'
|
||||||
import { Redirect } from 'react-router-dom'
|
import { useNavigate} from 'react-router-dom'
|
||||||
import { Footer } from '../Footer/Footer'
|
import { Footer } from '../../components/Footer/Footer'
|
||||||
|
|
||||||
import './authForDevelopers.scss'
|
import './authForDevelopers.scss'
|
||||||
|
|
||||||
const AuthForDevelopers = () => {
|
const AuthForDevelopers = () => {
|
||||||
const isAuth = useSelector(selectAuth)
|
|
||||||
|
const isAuth = useSelector(selectAuth);
|
||||||
|
let navigate = useNavigate();
|
||||||
|
|
||||||
if (isAuth) {
|
if (isAuth) {
|
||||||
return <Redirect to='/report' />
|
navigate('/profile')
|
||||||
}
|
}
|
||||||
|
|
||||||
return (
|
return (
|
||||||
@ -89,6 +91,6 @@ const AuthForDevelopers = () => {
|
|||||||
</div>
|
</div>
|
||||||
</section>
|
</section>
|
||||||
)
|
)
|
||||||
}
|
};
|
||||||
|
|
||||||
export default AuthForDevelopers
|
export default AuthForDevelopers
|
@ -7,18 +7,19 @@ import vector from '../../images/Vector_Smart_Object.png'
|
|||||||
import vectorBlack from '../../images/Vector_Smart_Object_black.png'
|
import vectorBlack from '../../images/Vector_Smart_Object_black.png'
|
||||||
import { useSelector } from 'react-redux'
|
import { useSelector } from 'react-redux'
|
||||||
import { selectAuth } from '../../redux/outstaffingSlice'
|
import { selectAuth } from '../../redux/outstaffingSlice'
|
||||||
import { Redirect } from 'react-router-dom'
|
import { useNavigate} from 'react-router-dom'
|
||||||
|
|
||||||
import { Footer } from '../Footer/Footer'
|
import { Footer } from '../../components/Footer/Footer'
|
||||||
import { AuthBox } from '../AuthBox/AuthBox'
|
import { AuthBox } from '../../components/AuthBox/AuthBox'
|
||||||
|
|
||||||
import './authForPartners.scss'
|
import './authForPartners.scss'
|
||||||
|
|
||||||
const AuthForPartners = () => {
|
const AuthForPartners = () => {
|
||||||
const isAuth = useSelector(selectAuth)
|
const isAuth = useSelector(selectAuth);
|
||||||
|
let navigate = useNavigate();
|
||||||
|
|
||||||
if (isAuth) {
|
if (isAuth) {
|
||||||
return <Redirect to='/' />
|
navigate('/')
|
||||||
}
|
}
|
||||||
|
|
||||||
return (
|
return (
|
||||||
@ -81,6 +82,6 @@ const AuthForPartners = () => {
|
|||||||
</div>
|
</div>
|
||||||
</section>
|
</section>
|
||||||
)
|
)
|
||||||
}
|
};
|
||||||
|
|
||||||
export default AuthForPartners
|
export default AuthForPartners
|
@ -1,8 +0,0 @@
|
|||||||
import React from 'react';
|
|
||||||
import AuthForDevelopers from '../components/Auth/AuthForDevelopers';
|
|
||||||
|
|
||||||
const AuthPageForDevelopers = () => {
|
|
||||||
return <AuthForDevelopers />;
|
|
||||||
};
|
|
||||||
|
|
||||||
export default AuthPageForDevelopers;
|
|
@ -1,8 +0,0 @@
|
|||||||
import React from 'react';
|
|
||||||
import AuthForPartners from '../components/Auth/AuthForPartners';
|
|
||||||
|
|
||||||
const AuthPageForPartners = () => {
|
|
||||||
return <AuthForPartners />;
|
|
||||||
};
|
|
||||||
|
|
||||||
export default AuthPageForPartners;
|
|
@ -1,11 +1,11 @@
|
|||||||
import React from 'react';
|
import React from 'react';
|
||||||
import { useHistory } from 'react-router';
|
import { useNavigate } from 'react-router-dom';
|
||||||
import { WithLogout } from '../hoc/withLogout';
|
import { WithLogout } from '../hoc/withLogout';
|
||||||
import Calendar from '../components/Calendar/Calendar';
|
import Calendar from '../components/Calendar/Calendar';
|
||||||
|
|
||||||
const CalendarPage = () => {
|
const CalendarPage = () => {
|
||||||
const history = useHistory();
|
const navigate = useNavigate();
|
||||||
return <WithLogout><Calendar onSelect={() => { history.push('/report/0') }} /></WithLogout>;
|
return <WithLogout><Calendar onSelect={() => { navigate('/report/0') }} /></WithLogout>;
|
||||||
};
|
};
|
||||||
|
|
||||||
export default CalendarPage;
|
export default CalendarPage;
|
||||||
|
@ -1,99 +1,97 @@
|
|||||||
import React, { useState } from 'react'
|
import React from 'react'
|
||||||
import { useDispatch, useSelector } from 'react-redux'
|
import {useDispatch, useSelector} from 'react-redux'
|
||||||
import { useHistory, useParams, Link } from 'react-router-dom'
|
import {useParams, useNavigate} from 'react-router-dom'
|
||||||
import {
|
import {
|
||||||
currentCandidate,
|
currentCandidate,
|
||||||
selectCurrentCandidate,
|
selectCurrentCandidate,
|
||||||
auth
|
|
||||||
} from '../redux/outstaffingSlice'
|
} from '../redux/outstaffingSlice'
|
||||||
import SVG from 'react-inlinesvg'
|
import SVG from 'react-inlinesvg'
|
||||||
import { WithLogout } from '../hoc/withLogout'
|
import {WithLogout} from '../hoc/withLogout'
|
||||||
import Form from '../components/Form/Form'
|
import Form from '../components/Form/Form'
|
||||||
import { LEVELS, SKILLS } from '../components/constants/constants'
|
import {LEVELS, SKILLS} from '../constants/constants'
|
||||||
import { fetchGet } from '../server/server'
|
import {Footer} from '../components/Footer/Footer'
|
||||||
import { Footer } from '../components/Footer/Footer'
|
|
||||||
|
|
||||||
import arrow from '../images/right-arrow.png'
|
import arrow from '../images/right-arrow.png'
|
||||||
import rectangle from '../images/rectangle_secondPage.png'
|
import rectangle from '../images/rectangle_secondPage.png'
|
||||||
import telegramIcon from '../images/telegram-icon.svg'
|
import telegramIcon from '../images/telegram-icon.svg'
|
||||||
|
|
||||||
import './formPage.scss'
|
import './formPage.scss'
|
||||||
import { getRole } from '../redux/roleSlice'
|
import {useRequest} from "../hooks/useRequest";
|
||||||
|
|
||||||
const goBack = (history) => {
|
|
||||||
history.goBack()
|
|
||||||
}
|
|
||||||
|
|
||||||
const FormPage = () => {
|
const FormPage = () => {
|
||||||
const params = useParams()
|
const params = useParams();
|
||||||
const history = useHistory()
|
const navigate = useNavigate();
|
||||||
const dispatch = useDispatch()
|
const dispatch = useDispatch();
|
||||||
const candidate = useSelector(selectCurrentCandidate)
|
const candidate = useSelector(selectCurrentCandidate);
|
||||||
const role = useSelector(getRole)
|
|
||||||
|
|
||||||
|
const {apiRequest} = useRequest();
|
||||||
|
|
||||||
|
const goBack = () => {
|
||||||
|
navigate(-1)
|
||||||
|
};
|
||||||
|
|
||||||
if (!candidate.id) {
|
if (!candidate.id) {
|
||||||
fetchGet({
|
apiRequest('/profile', {
|
||||||
link: `${process.env.REACT_APP_API_URL}/api/profile/`,
|
params: Number(params.id)
|
||||||
params: Number(params.id),
|
})
|
||||||
history,
|
.then((el) => dispatch(currentCandidate(el)))
|
||||||
role,
|
|
||||||
logout: () => dispatch(auth(false))
|
|
||||||
}).then((el) => dispatch(currentCandidate(el)))
|
|
||||||
}
|
}
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<WithLogout>
|
<WithLogout>
|
||||||
<div className='form-page'>
|
<div className='form-page'>
|
||||||
<div className='form-page__back'>
|
<div className='form-page__back'>
|
||||||
<div className='form-page__arrow' onClick={() => goBack(history)}>
|
<div className='form-page__arrow' onClick={goBack}>
|
||||||
<div className='form-page__arrow-img'>
|
<div className='form-page__arrow-img'>
|
||||||
<img src={arrow} alt='' />
|
<img src={arrow} alt=''/>
|
||||||
</div>
|
</div>
|
||||||
<div className='form-page__back-to-candidate'>
|
<div className='form-page__back-to-candidate'>
|
||||||
<span>Вернуться к кандидату</span>
|
<span>Вернуться к кандидату</span>
|
||||||
|
</div>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
<div className='form-page__candidate'>
|
||||||
<div className='form-page__candidate'>
|
<div className='form-page__avatar'>
|
||||||
<div className='form-page__avatar'>
|
<img src={candidate.photo} alt='candidate avatar'/>
|
||||||
<img src={candidate.photo} />
|
</div>
|
||||||
</div>
|
<div className='form-page__candidate-info'>
|
||||||
<div className='form-page__candidate-info'>
|
<div className='form-page__position'>
|
||||||
<div className='form-page__position'>
|
|
||||||
<span>
|
<span>
|
||||||
{candidate.specification} {SKILLS[candidate.position_id]},{' '}
|
{candidate.specification} {SKILLS[candidate.position_id]},{' '}
|
||||||
{LEVELS[candidate.level]}
|
{LEVELS[candidate.level]}
|
||||||
</span>
|
</span>
|
||||||
</div>
|
</div>
|
||||||
<div className='form-page__selected'>
|
<div className='form-page__selected'>
|
||||||
<img src={rectangle} />
|
<img src={rectangle} alt='rectangle'/>
|
||||||
<span>Выбранный кандидат</span>
|
<span>Выбранный кандидат</span>
|
||||||
|
</div>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
|
<div 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>
|
||||||
<div className='form-page__interview'>
|
</WithLogout>
|
||||||
<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>
|
|
||||||
)
|
)
|
||||||
}
|
};
|
||||||
|
|
||||||
export default FormPage
|
export default FormPage
|
||||||
|
100
src/pages/Profile/Profile.js
Normal 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>
|
||||||
|
)
|
||||||
|
};
|
126
src/pages/Profile/profile.scss
Normal 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;
|
||||||
|
}
|
||||||
|
}
|
8
src/pages/ProfileCalendarPage.js
Normal file
@ -0,0 +1,8 @@
|
|||||||
|
import React from 'react';
|
||||||
|
import { ProfileCalendar } from '../../src/components/ProfileCalendar/ProfileCalendar';
|
||||||
|
|
||||||
|
const ProfileCalendarPage = () => {
|
||||||
|
return <ProfileCalendar/>;
|
||||||
|
};
|
||||||
|
|
||||||
|
export default ProfileCalendarPage;
|
@ -2,6 +2,6 @@ import React from 'react';
|
|||||||
import { WithLogout } from '../hoc/withLogout';
|
import { WithLogout } from '../hoc/withLogout';
|
||||||
import ReportForm from '../components/ReportForm/ReportForm';
|
import ReportForm from '../components/ReportForm/ReportForm';
|
||||||
|
|
||||||
const ReportFormPage = () => <WithLogout><ReportForm /></WithLogout>;
|
const ReportFormPage = () => <ReportForm />;
|
||||||
|
|
||||||
export default ReportFormPage;
|
export default ReportFormPage;
|
||||||
|
@ -22,7 +22,7 @@ const tasks = [
|
|||||||
text: 'Задача «83 – Навигационная система – Поиск по почтовому индексу – Добавить экран поиска по почтовому индексу» не может быть завершена, т.к. работа над задачей «82 – Навигационная система – Разработать модуль поиска по почтовому индексу» ещё не начата',
|
text: 'Задача «83 – Навигационная система – Поиск по почтовому индексу – Добавить экран поиска по почтовому индексу» не может быть завершена, т.к. работа над задачей «82 – Навигационная система – Разработать модуль поиска по почтовому индексу» ещё не начата',
|
||||||
hours: 3
|
hours: 3
|
||||||
}
|
}
|
||||||
]
|
];
|
||||||
|
|
||||||
const SingleReportPage = () => {
|
const SingleReportPage = () => {
|
||||||
return (
|
return (
|
||||||
@ -30,7 +30,7 @@ const SingleReportPage = () => {
|
|||||||
<div className='single-report-page'>
|
<div className='single-report-page'>
|
||||||
<div className='single-report-page__back'>
|
<div className='single-report-page__back'>
|
||||||
<div className='single-report-page__back-arrow'>
|
<div className='single-report-page__back-arrow'>
|
||||||
<img src={arrowLeft} />
|
<img src={arrowLeft} alt='arrowLeft'/>
|
||||||
</div>
|
</div>
|
||||||
<div className='single-report-page__back-text'>
|
<div className='single-report-page__back-text'>
|
||||||
Вернуться к списку
|
Вернуться к списку
|
||||||
@ -103,6 +103,6 @@ const SingleReportPage = () => {
|
|||||||
</div>
|
</div>
|
||||||
</WithLogout>
|
</WithLogout>
|
||||||
)
|
)
|
||||||
}
|
};
|
||||||
|
|
||||||
export default SingleReportPage
|
export default SingleReportPage
|
||||||
|
101
src/pages/Summary/Summary.js
Normal 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>
|
||||||
|
)
|
||||||
|
};
|
||||||
|
|
391
src/pages/Summary/summary.scss
Normal 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;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
@ -1,6 +1,6 @@
|
|||||||
import {Redirect} from "react-router-dom"
|
import {useNavigate} from "react-router-dom"
|
||||||
import { HeaderPageTestsQuiz } from "../../components/features/quiz/HeaderPageTests"
|
import {HeaderPageTestsQuiz} from "../../components/features/quiz/HeaderPageTests"
|
||||||
import { Instruction } from "../../components/features/quiz/Instructions"
|
import {Instruction} from "../../components/features/quiz/Instructions"
|
||||||
import React from "react";
|
import React from "react";
|
||||||
import {useSelector} from "react-redux";
|
import {useSelector} from "react-redux";
|
||||||
import {selectedTest} from "../../redux/quizSlice";
|
import {selectedTest} from "../../redux/quizSlice";
|
||||||
@ -8,16 +8,17 @@ import {selectedTest} from "../../redux/quizSlice";
|
|||||||
|
|
||||||
export const InstructionPage = () => {
|
export const InstructionPage = () => {
|
||||||
|
|
||||||
const test = useSelector(selectedTest)
|
const test = useSelector(selectedTest)
|
||||||
|
|
||||||
if(!test){
|
let navigate = useNavigate();
|
||||||
return <Redirect to={'/quiz'} />
|
if (!test) {
|
||||||
}
|
navigate('/quiz')
|
||||||
|
}
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<>
|
<>
|
||||||
<HeaderPageTestsQuiz isVisibilityButton={false}/>
|
<HeaderPageTestsQuiz isVisibilityButton={false}/>
|
||||||
<Instruction />
|
<Instruction/>
|
||||||
</>
|
</>
|
||||||
)
|
)
|
||||||
}
|
};
|
@ -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 {HeaderPageTestsQuiz} from "../../components/features/quiz/HeaderPageTests"
|
||||||
import {MyTestsQuiz} from "../../components/features/quiz/MyTestsQuiz"
|
import {MyTestsQuiz} from "../../components/features/quiz/MyTestsQuiz"
|
||||||
import {useSelector} from "react-redux";
|
|
||||||
import {selectedTest, selectPassedTests} from "../../redux/quizSlice";
|
import {selectedTest, selectPassedTests} from "../../redux/quizSlice";
|
||||||
import React from "react";
|
|
||||||
|
|
||||||
|
|
||||||
export const InterjacentPage = () => {
|
export const InterjacentPage = () => {
|
||||||
|
|
||||||
const test = useSelector(selectedTest)
|
const test = useSelector(selectedTest);
|
||||||
const passedTests = useSelector(selectPassedTests)
|
const passedTests = useSelector(selectPassedTests)
|
||||||
|
let navigate = useNavigate();
|
||||||
|
if (!test) {
|
||||||
|
navigate('/quiz')
|
||||||
|
}
|
||||||
|
|
||||||
if (!test) {
|
return (
|
||||||
return <Redirect to={'/quiz'}/>
|
<>
|
||||||
}
|
|
||||||
|
|
||||||
return (
|
|
||||||
<>
|
|
||||||
<HeaderPageTestsQuiz isVisibilityButton={true}/>
|
<HeaderPageTestsQuiz isVisibilityButton={true}/>
|
||||||
<MyTestsQuiz listTests={passedTests}/>
|
<MyTestsQuiz listTests={passedTests}/>
|
||||||
</>
|
</>
|
||||||
)
|
)
|
||||||
}
|
};
|
@ -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 {HeaderPageTestsQuiz} from '../../components/features/quiz/HeaderPageTests'
|
||||||
import {Progressbar} from '../../components/features/quiz/ProgressbarQuiz'
|
|
||||||
import {TaskQuiz} from '../../components/features/quiz/Task'
|
import {TaskQuiz} from '../../components/features/quiz/Task'
|
||||||
import {useSelector} from "react-redux";
|
import {useSelector} from "react-redux";
|
||||||
import {selectedTest} from "../../redux/quizSlice";
|
import {selectedTest} from "../../redux/quizSlice";
|
||||||
import React from "react";
|
import React from "react";
|
||||||
|
|
||||||
export const QuizTestPage = () => {
|
export const QuizTestPage = () => {
|
||||||
|
let navigate = useNavigate()
|
||||||
|
const test = useSelector(selectedTest)
|
||||||
|
|
||||||
const test = useSelector(selectedTest)
|
if (!test) {
|
||||||
|
navigate('/quiz')
|
||||||
if (!test) {
|
}
|
||||||
return <Redirect to={'/quiz'}/>
|
return (
|
||||||
}
|
<>
|
||||||
return (
|
|
||||||
<>
|
|
||||||
<HeaderPageTestsQuiz isVisibilityButton={false}/>
|
<HeaderPageTestsQuiz isVisibilityButton={false}/>
|
||||||
<TaskQuiz/>
|
<TaskQuiz/>
|
||||||
</>
|
</>
|
||||||
)
|
)
|
||||||
}
|
};
|
@ -1,4 +1,4 @@
|
|||||||
import {Link, Redirect} from "react-router-dom"
|
import {useNavigate} from "react-router-dom"
|
||||||
import {HeaderPageTestsQuiz} from "../../components/features/quiz/HeaderPageTests"
|
import {HeaderPageTestsQuiz} from "../../components/features/quiz/HeaderPageTests"
|
||||||
import {Results} from "../../components/features/quiz/Results";
|
import {Results} from "../../components/features/quiz/Results";
|
||||||
import {useSelector} from "react-redux";
|
import {useSelector} from "react-redux";
|
||||||
@ -8,16 +8,17 @@ import React from "react";
|
|||||||
|
|
||||||
export const ResultPage = () => {
|
export const ResultPage = () => {
|
||||||
|
|
||||||
const test = useSelector(selectedTest)
|
const test = useSelector(selectedTest)
|
||||||
|
|
||||||
if (!test) {
|
let navigate = useNavigate();
|
||||||
return <Redirect to={'/quiz'}/>
|
if (!test) {
|
||||||
}
|
navigate('/quiz')
|
||||||
|
}
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<>
|
<>
|
||||||
<HeaderPageTestsQuiz isVisibilityButton={false}/>
|
<HeaderPageTestsQuiz isVisibilityButton={false}/>
|
||||||
<Results/>
|
<Results/>
|
||||||
</>
|
</>
|
||||||
)
|
)
|
||||||
}
|
};
|
@ -8,6 +8,8 @@ const initialState = {
|
|||||||
currentCandidate: {},
|
currentCandidate: {},
|
||||||
auth: false,
|
auth: false,
|
||||||
positionId: null,
|
positionId: null,
|
||||||
|
profileInfo: {},
|
||||||
|
reportsDates: ''
|
||||||
};
|
};
|
||||||
|
|
||||||
export const outstaffingSlice = createSlice({
|
export const outstaffingSlice = createSlice({
|
||||||
@ -37,11 +39,17 @@ export const outstaffingSlice = createSlice({
|
|||||||
},
|
},
|
||||||
setUserInfo: (state, action) => {
|
setUserInfo: (state, action) => {
|
||||||
state.userInfo = action.payload;
|
state.userInfo = action.payload;
|
||||||
}
|
},
|
||||||
|
setProfileInfo: (state, action) => {
|
||||||
|
state.profileInfo = action.payload;
|
||||||
|
},
|
||||||
|
setReportsDates: (state, action) => {
|
||||||
|
state.reportsDates = action.payload;
|
||||||
|
},
|
||||||
},
|
},
|
||||||
});
|
});
|
||||||
|
|
||||||
export const { tags, profiles, selectedItems, auth, currentCandidate, filteredCandidates, setPositionId, setUserInfo } = outstaffingSlice.actions;
|
export const { tags, profiles, selectedItems, auth, currentCandidate, filteredCandidates, setPositionId, setUserInfo, setProfileInfo, setReportsDates } = outstaffingSlice.actions;
|
||||||
|
|
||||||
export const selectProfiles = (state) => state.outstaffing.profiles;
|
export const selectProfiles = (state) => state.outstaffing.profiles;
|
||||||
export const selectTags = (state) => state.outstaffing.tags;
|
export const selectTags = (state) => state.outstaffing.tags;
|
||||||
@ -50,6 +58,8 @@ export const selectItems = (state) => state.outstaffing.selectedItems;
|
|||||||
export const selectCurrentCandidate = (state) => state.outstaffing.currentCandidate;
|
export const selectCurrentCandidate = (state) => state.outstaffing.currentCandidate;
|
||||||
export const selectAuth = (state) => state.outstaffing.auth;
|
export const selectAuth = (state) => state.outstaffing.auth;
|
||||||
export const getPositionId = (state) => state.outstaffing.positionId;
|
export const getPositionId = (state) => state.outstaffing.positionId;
|
||||||
|
export const getProfileInfo = (state) => state.outstaffing.profileInfo;
|
||||||
export const selectUserInfo = (state) => state.outstaffing.userInfo;
|
export const selectUserInfo = (state) => state.outstaffing.userInfo;
|
||||||
|
export const getReportsDates = (state) => state.outstaffing.reportsDates;
|
||||||
|
|
||||||
export default outstaffingSlice.reducer;
|
export default outstaffingSlice.reducer;
|
||||||
|
@ -1,5 +1,5 @@
|
|||||||
import {createAsyncThunk, createSlice} from '@reduxjs/toolkit';
|
import {createAsyncThunk, createSlice} from '@reduxjs/toolkit';
|
||||||
import {fetchGet, fetchPost} from './../server/server'
|
import {fetchGet} from './../server/server'
|
||||||
import axios from "axios";
|
import axios from "axios";
|
||||||
|
|
||||||
|
|
||||||
@ -17,17 +17,16 @@ export const setUserInfo = createAsyncThunk(
|
|||||||
'userInfo',
|
'userInfo',
|
||||||
async (id) => {
|
async (id) => {
|
||||||
try{
|
try{
|
||||||
const response = await fetchGet({
|
return await fetchGet({
|
||||||
link: `${process.env.REACT_APP_API_URL}/api/profile/get-main-data?user_id=${id}`,
|
link: `${process.env.REACT_APP_API_URL}/api/profile/get-main-data?user_id=${id}`,
|
||||||
Origin: `${process.env.REACT_APP_BASE_URL}`,
|
Origin: `${process.env.REACT_APP_BASE_URL}`,
|
||||||
}
|
}
|
||||||
)
|
)
|
||||||
return response
|
|
||||||
}catch (e) {
|
}catch (e) {
|
||||||
console.log(e)
|
console.log(e)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
)
|
);
|
||||||
|
|
||||||
export const fetchUserAnswersMany = createAsyncThunk(
|
export const fetchUserAnswersMany = createAsyncThunk(
|
||||||
'answersUserMany',
|
'answersUserMany',
|
||||||
@ -38,13 +37,13 @@ export const fetchUserAnswersMany = createAsyncThunk(
|
|||||||
headers: {
|
headers: {
|
||||||
Authorization: `Bearer ${localStorage.getItem('auth_token')}`,
|
Authorization: `Bearer ${localStorage.getItem('auth_token')}`,
|
||||||
}
|
}
|
||||||
})
|
});
|
||||||
return response.data
|
return response.data
|
||||||
}catch (e) {
|
}catch (e) {
|
||||||
console.log(e)
|
console.log(e)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
)
|
);
|
||||||
export const fetchUserAnswerOne = createAsyncThunk(
|
export const fetchUserAnswerOne = createAsyncThunk(
|
||||||
'answersUserOne',
|
'answersUserOne',
|
||||||
async (checkedValues) => {
|
async (checkedValues) => {
|
||||||
@ -54,55 +53,43 @@ export const fetchUserAnswerOne = createAsyncThunk(
|
|||||||
headers: {
|
headers: {
|
||||||
Authorization: `Bearer ${localStorage.getItem('auth_token')}`,
|
Authorization: `Bearer ${localStorage.getItem('auth_token')}`,
|
||||||
}
|
}
|
||||||
})
|
});
|
||||||
return response.data
|
return response.data
|
||||||
}catch (e) {
|
}catch (e) {
|
||||||
console.log(e)
|
console.log(e)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
)
|
);
|
||||||
export const fetchGetAnswers = createAsyncThunk(
|
export const fetchGetAnswers = createAsyncThunk(
|
||||||
'answers',
|
'answers',
|
||||||
async (question_id) => {
|
async (question_id) => {
|
||||||
const resp = await fetchGet({
|
return await fetchGet({
|
||||||
link: `${process.env.REACT_APP_API_URL}/api/answer/get-answers?question_id=${question_id}`,
|
link: `${process.env.REACT_APP_API_URL}/api/answer/get-answers?question_id=${question_id}`,
|
||||||
Origin: `${process.env.REACT_APP_BASE_URL}`,
|
Origin: `${process.env.REACT_APP_BASE_URL}`,
|
||||||
}
|
}
|
||||||
)
|
)
|
||||||
return resp
|
|
||||||
}
|
}
|
||||||
)
|
);
|
||||||
// export const fetchGetQuestion = createAsyncThunk(
|
|
||||||
// 'questions',
|
|
||||||
// async (uuid) => {
|
|
||||||
// const resp = await fetchGet({
|
|
||||||
// link: `${process.env.REACT_APP_API_URL}/api/question/get-questions?uuid=${uuid}`,
|
|
||||||
// Origin: `${process.env.REACT_APP_BASE_URL}`,
|
|
||||||
// }
|
|
||||||
// )
|
|
||||||
// return resp
|
|
||||||
// }
|
|
||||||
// )
|
|
||||||
export const fetchResultTest = createAsyncThunk(
|
export const fetchResultTest = createAsyncThunk(
|
||||||
'result',
|
'result',
|
||||||
async (uuid) => {
|
async (uuid) => {
|
||||||
const resp = await fetchGet({
|
return await fetchGet({
|
||||||
link: `${process.env.REACT_APP_API_URL}/api/user-questionnaire/questionnaire-completed?user_questionnaire_uuid=${uuid}`,
|
link: `${process.env.REACT_APP_API_URL}/api/user-questionnaire/questionnaire-completed?user_questionnaire_uuid=${uuid}`,
|
||||||
Origin: `${process.env.REACT_APP_BASE_URL}`,
|
Origin: `${process.env.REACT_APP_BASE_URL}`,
|
||||||
}
|
}
|
||||||
)
|
)
|
||||||
return resp
|
|
||||||
|
|
||||||
}
|
}
|
||||||
)
|
);
|
||||||
|
|
||||||
export const quizSlice = createSlice({
|
export const quizSlice = createSlice({
|
||||||
name: 'quiz',
|
name: 'quiz',
|
||||||
initialState,
|
initialState,
|
||||||
reducers: {
|
reducers: {
|
||||||
setQuestionnairesList: (state, action) => {
|
setQuestionnairesList: (state, action) => {
|
||||||
state.dataQuestionnairesOfUser = action.payload
|
state.dataQuestionnairesOfUser = action.payload;
|
||||||
state.passedTests = action.payload.filter(item=>item.status === 2)
|
state.passedTests = action.payload.filter(item => item.status === 2)
|
||||||
},
|
},
|
||||||
setSelectedTest: (state, action) => {
|
setSelectedTest: (state, action) => {
|
||||||
state.selectedTest = action.payload
|
state.selectedTest = action.payload
|
||||||
@ -123,11 +110,10 @@ export const quizSlice = createSlice({
|
|||||||
|
|
||||||
export const {setQuestionnairesList, setSelectedTest} = quizSlice.actions;
|
export const {setQuestionnairesList, setSelectedTest} = quizSlice.actions;
|
||||||
|
|
||||||
// export const selectQuestions = (state) => state.quiz.questions;
|
|
||||||
export const selectAnswer = (state) => state.quiz.answer;
|
export const selectAnswer = (state) => state.quiz.answer;
|
||||||
export const selectQuestionnairesOfUser = (state) => state.quiz.dataQuestionnairesOfUser;
|
export const selectQuestionnairesOfUser = (state) => state.quiz.dataQuestionnairesOfUser;
|
||||||
export const selectResult = (state) => state.quiz.result;
|
export const selectResult = (state) => state.quiz.result;
|
||||||
export const selectIsLoading = (state) => state.quiz.isLoading;
|
|
||||||
export const selectedTest = (state) => state.quiz.selectedTest;
|
export const selectedTest = (state) => state.quiz.selectedTest;
|
||||||
export const selectPassedTests = (state) => state.quiz.passedTests;
|
export const selectPassedTests = (state) => state.quiz.passedTests;
|
||||||
export const selectUserInfo = (state) => state.quiz.userInfo;
|
export const selectUserInfo = (state) => state.quiz.userInfo;
|
||||||
|
@ -2,6 +2,7 @@ import { createSlice } from '@reduxjs/toolkit';
|
|||||||
|
|
||||||
const initialState = {
|
const initialState = {
|
||||||
dateSelected: '',
|
dateSelected: '',
|
||||||
|
reportDate: ''
|
||||||
};
|
};
|
||||||
|
|
||||||
export const reportSlice = createSlice({
|
export const reportSlice = createSlice({
|
||||||
@ -11,11 +12,16 @@ export const reportSlice = createSlice({
|
|||||||
dateSelected: (state, action) => {
|
dateSelected: (state, action) => {
|
||||||
state.dateSelected = action.payload;
|
state.dateSelected = action.payload;
|
||||||
},
|
},
|
||||||
|
setReportDate: (state, action) => {
|
||||||
|
state.reportDate = action.payload;
|
||||||
|
},
|
||||||
},
|
},
|
||||||
});
|
});
|
||||||
|
|
||||||
export const { dateSelected, } = reportSlice.actions;
|
export const { dateSelected, setReportDate} = reportSlice.actions;
|
||||||
|
|
||||||
export const selectDate = (state) => state.report.dateSelected;
|
export const selectDate = (state) => state.report.dateSelected;
|
||||||
|
|
||||||
|
export const getReportDate = (state) => state.report.reportDate;
|
||||||
|
|
||||||
export default reportSlice.reducer;
|
export default reportSlice.reducer;
|
||||||
|
@ -1,22 +1,22 @@
|
|||||||
export const withAuthRedirect =
|
export const withAuthRedirect =
|
||||||
(actionCall) =>
|
(actionCall) =>
|
||||||
({ link, params, history, role, logout, body }) => {
|
({ link, params, history, role, logout, body }) => {
|
||||||
const linkWithParams = params
|
// const linkWithParams = params
|
||||||
? `${link}?${new URLSearchParams(params)}`
|
// ? `${link}?${new URLSearchParams(params)}`
|
||||||
: link
|
// : link;
|
||||||
return actionCall(linkWithParams, body)
|
// return actionCall(linkWithParams, body)
|
||||||
.then((res) => {
|
// .then((res) => {
|
||||||
if (res.status && res.status === 401) {
|
// if (res.status && res.status === 401) {
|
||||||
localStorage.clear()
|
// localStorage.clear();
|
||||||
logout && logout()
|
// logout && logout();
|
||||||
history.push(role === 'ROLE_DEV' ? '/authdev' : '/auth')
|
// history.push(role === 'ROLE_DEV' ? '/authdev' : '/auth')
|
||||||
}
|
// }
|
||||||
|
//
|
||||||
return res
|
// return res
|
||||||
})
|
// })
|
||||||
.catch((err) => {
|
// .catch((err) => {
|
||||||
localStorage.clear()
|
// localStorage.clear();
|
||||||
logout && logout()
|
// logout && logout();
|
||||||
history.push(role === 'ROLE_DEV' ? '/authdev' : '/auth')
|
// history.push(role === 'ROLE_DEV' ? '/authdev' : '/auth')
|
||||||
})
|
// })
|
||||||
}
|
};
|
||||||
|
@ -1,84 +1,5 @@
|
|||||||
import { withAuthRedirect } from './authRedirect'
|
import { withAuthRedirect } from './authRedirect'
|
||||||
|
|
||||||
export const fetchForm = withAuthRedirect(async (link, info) => {
|
|
||||||
try {
|
|
||||||
const response = await fetch(link, {
|
|
||||||
method: 'POST',
|
|
||||||
headers: {
|
|
||||||
// 'Access-Control-Request-Headers': 'authorization',
|
|
||||||
Authorization: `Bearer ${localStorage.getItem('auth_token')}`,
|
|
||||||
Origin: `${process.env.REACT_APP_BASE_URL}`,
|
|
||||||
'Content-Type': 'application/json'
|
|
||||||
},
|
|
||||||
body: JSON.stringify(info)
|
|
||||||
})
|
|
||||||
|
|
||||||
return response
|
|
||||||
} catch (error) {
|
|
||||||
console.log('Query error', error)
|
|
||||||
}
|
|
||||||
})
|
|
||||||
|
|
||||||
export const fetchAuth = async ({
|
|
||||||
username,
|
|
||||||
password,
|
|
||||||
dispatch,
|
|
||||||
catchError
|
|
||||||
}) => {
|
|
||||||
const apiURL = process.env.REACT_APP_API_URL
|
|
||||||
try {
|
|
||||||
const response = await fetch(`${apiURL}/api/user/login`, {
|
|
||||||
method: 'POST',
|
|
||||||
mode: 'cors',
|
|
||||||
headers: {
|
|
||||||
'Access-Control-Request-Headers': 'authorization',
|
|
||||||
'Content-Type': 'application/json'
|
|
||||||
// Origin: `http://localhost`
|
|
||||||
},
|
|
||||||
body: JSON.stringify({
|
|
||||||
username,
|
|
||||||
password
|
|
||||||
})
|
|
||||||
})
|
|
||||||
|
|
||||||
if (!response.ok) {
|
|
||||||
catchError()
|
|
||||||
return response.statusText
|
|
||||||
}
|
|
||||||
|
|
||||||
response.json().then((resJSON) => {
|
|
||||||
localStorage.setItem('auth_token', resJSON.access_token)
|
|
||||||
localStorage.setItem('id', resJSON.id)
|
|
||||||
localStorage.setItem(
|
|
||||||
'access_token_expired_at',
|
|
||||||
resJSON.access_token_expired_at
|
|
||||||
)
|
|
||||||
dispatch(resJSON)
|
|
||||||
})
|
|
||||||
} catch (error) {
|
|
||||||
console.error('Error occured: ', error)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
export const fetchReportList = withAuthRedirect(async (link) => {
|
|
||||||
try {
|
|
||||||
const response = await fetch(
|
|
||||||
`https://guild.loc/api/reports/index?user_id=26&fromDate=2021-10-18`,
|
|
||||||
// link,
|
|
||||||
{
|
|
||||||
method: 'GET',
|
|
||||||
headers: {
|
|
||||||
Authorization: `Bearer ${localStorage.getItem('auth_token')}`
|
|
||||||
}
|
|
||||||
}
|
|
||||||
)
|
|
||||||
let data = await response.json()
|
|
||||||
|
|
||||||
return data
|
|
||||||
} catch (error) {
|
|
||||||
console.log('Query error', error)
|
|
||||||
}
|
|
||||||
})
|
|
||||||
|
|
||||||
export const fetchGet = withAuthRedirect(async (link) => {
|
export const fetchGet = withAuthRedirect(async (link) => {
|
||||||
try {
|
try {
|
||||||
@ -87,31 +8,11 @@ export const fetchGet = withAuthRedirect(async (link) => {
|
|||||||
headers: {
|
headers: {
|
||||||
Authorization: `Bearer ${localStorage.getItem('auth_token')}`
|
Authorization: `Bearer ${localStorage.getItem('auth_token')}`
|
||||||
}
|
}
|
||||||
})
|
});
|
||||||
let data = await response.json()
|
let data = await response.json();
|
||||||
|
|
||||||
return data
|
return data
|
||||||
} catch (error) {
|
} catch (error) {
|
||||||
console.log('Query error', error)
|
console.log('Query error', error)
|
||||||
}
|
}
|
||||||
})
|
});
|
||||||
|
|
||||||
export const fetchPost = withAuthRedirect(async (link, body) => {
|
|
||||||
console.log('i', body)
|
|
||||||
try {
|
|
||||||
const response = await fetch(link, {
|
|
||||||
method: 'POST',
|
|
||||||
headers: {
|
|
||||||
Authorization: `Bearer ${localStorage.getItem('auth_token')}`,
|
|
||||||
'Content-Type': 'application/json',
|
|
||||||
//Origin: `http://localhost:3000`
|
|
||||||
Origin: `${process.env.REACT_APP_BASE_URL}`
|
|
||||||
},
|
|
||||||
body: JSON.stringify(body)
|
|
||||||
})
|
|
||||||
|
|
||||||
return response
|
|
||||||
} catch (error) {
|
|
||||||
console.log('Query error', error)
|
|
||||||
}
|
|
||||||
})
|
|
||||||
|