Merge pull request #40 from apuc/profilePage

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

View File

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

29159
package-lock.json generated

File diff suppressed because it is too large Load Diff

View File

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

View File

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

View File

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

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

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

View File

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

View File

@ -1,31 +1,71 @@
import React, {useState} from 'react'
import {Link} from 'react-router-dom'
import {useDispatch, useSelector} from 'react-redux'
import { auth, setUserInfo } from '../../redux/outstaffingSlice'
import { loading } from '../../redux/loaderSlice'
import ellipse from '../../images/ellipse.png'
import {withSwalInstance} from 'sweetalert2-react'
import swal from 'sweetalert2'
import {Loader} from '../Loader/Loader'
import { fetchAuth } from '../../server/server'
import {auth, setUserInfo} from '../../redux/outstaffingSlice'
import {loading} from '../../redux/loaderSlice'
import {setRole} from '../../redux/roleSlice'
import {selectIsLoading} from '../../redux/loaderSlice'
import './authBox.scss'
import ellipse from '../../images/ellipse.png'
import { withSwalInstance } from 'sweetalert2-react'
import swal from 'sweetalert2'
const SweetAlert = withSwalInstance(swal)
import './authBox.scss'
import {useRequest} from "../../hooks/useRequest";
const SweetAlert = withSwalInstance(swal);
export const AuthBox = ({title, altTitle, roleChangeLink}) => {
const dispatch = useDispatch()
const dispatch = useDispatch();
const isLoading = useSelector(selectIsLoading)
const {apiRequest} = useRequest();
const [username, setUsername] = useState('')
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 (
<div className='auth-box'>
@ -69,28 +109,10 @@ export const AuthBox = ({ title, altTitle, roleChangeLink }) => {
<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))
}
})
}
: () => {}
}
onClick={(e) => {
e.preventDefault();
submitHandler()
}}
>
{isLoading ? <Loader/> : 'Войти'}
</button>
@ -104,4 +126,4 @@ export const AuthBox = ({ title, altTitle, roleChangeLink }) => {
</form>
</div>
)
}
};

View File

@ -1,57 +1,29 @@
import React, {useEffect, useState} from 'react'
import { useDispatch, useSelector } from 'react-redux'
import { selectCurrentCandidate, auth } from '../../redux/outstaffingSlice'
import { Link, useHistory, useParams } from 'react-router-dom'
import calendarMale from '../../images/medium_male.png'
import rectangle from '../../images/rectangle_secondPage.png'
import {useSelector} from 'react-redux'
import {selectCurrentCandidate} from '../../redux/outstaffingSlice'
import {Link} from 'react-router-dom'
import CalendarComponent from './CalendarComponent'
import {currentMonth} from './calendarHelper'
import {Footer} from '../Footer/Footer'
import { fetchReportList } from '../../server/server'
import { getRole } from '../../redux/roleSlice'
import rectangle from '../../images/rectangle_secondPage.png'
import './calendar.scss'
const getDateParamString = ({ paramName, value }) => {
return value ? `${paramName}=${value}` : ''
}
const Calendar = ({onSelect}) => {
const dispatch = useDispatch()
const candidateForCalendar = useSelector(selectCurrentCandidate)
const role = useSelector(getRole)
const { userId } = useParams()
const [month, setMonth] = useState('')
const [fromDate, setFromDate] = useState(null)
const [toDate, setToDate] = useState(null)
const history = useHistory()
const candidateForCalendar = useSelector(selectCurrentCandidate);
// useEffect(() => {
// fetchReportList({
// link: `${
// process.env.REACT_APP_API_URL
// }/api/reports/index?user_id=${userId}${getDateParamString({
// paramName: 'fromDate',
// value: fromDate
// })}${getDateParamString({
// paramName: 'toDate',
// value: toDate
// })}`,
// history,
// role,
// logout: () => {}
// })
// }, [])
const [month, setMonth] = useState('');
useEffect(() => {
setMonth(currentMonth)
}, [month])
}, [month]);
const { name, skillsName, photo } = candidateForCalendar
const {name, skillsName, photo} = candidateForCalendar;
const abbreviatedName = name && name.substring(0, name.lastIndexOf(' '))
const abbreviatedName = name && name.substring(0, name.lastIndexOf(' '));
return (
<section className='calendar'>
@ -87,6 +59,6 @@ const Calendar = ({ onSelect }) => {
<Footer/>
</section>
)
}
};
export default Calendar

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

@ -5,51 +5,44 @@ import {
selectItems,
selectedItems,
filteredCandidates,
auth
} from '../../redux/outstaffingSlice'
import { fetchGet } from '../../server/server'
import { useHistory } from 'react-router-dom'
import { getRole } from '../../redux/roleSlice'
import {useRequest} from "../../hooks/useRequest";
import './outstaffingBlock.scss'
const handlePositionClick = ({
const handlePositionClick = (
{
dispatch,
positionId,
isSelected,
onSelect,
history,
role
apiRequest
}) => {
if (isSelected) {
fetchGet({
link: `${process.env.REACT_APP_API_URL}/api/profile?limit=`,
params: 4,
history,
role,
logout: () => dispatch(auth(false))
apiRequest('/profile', {
params: {limit: 1000},
}).then((profileArr) => {
dispatch(filteredCandidates(profileArr))
dispatch(selectedItems([]))
dispatch(filteredCandidates(profileArr));
dispatch(selectedItems([]));
onSelect(positionId)
})
} else {
fetchGet({
link: `${process.env.REACT_APP_API_URL}/api/profile?position_id=`,
params: positionId,
history,
role,
logout: () => dispatch(auth(false))
apiRequest('/profile', {
params: {
limit: '1000',
position_id: positionId},
}).then((el) => {
dispatch(filteredCandidates(el))
dispatch(selectedItems([]))
dispatch(filteredCandidates(el));
dispatch(selectedItems([]));
onSelect(positionId)
})
}
}
};
const OutstaffingBlock = ({
const OutstaffingBlock = (
{
dataTags = [],
selected,
img,
@ -58,20 +51,21 @@ const OutstaffingBlock = ({
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) => {
if (!itemsArr.find((el) => item === el.value)) {
dispatch(selectedItems([...itemsArr, {id, value: item, label: item}]))
}
}
};
let classes
let classes;
dataTags.forEach((el) => {
if (el.name === 'skills_back') {
@ -81,7 +75,7 @@ const OutstaffingBlock = ({
} else if (el.name === 'skills_front') {
classes = 'front'
}
})
});
return (
<OutsideClickHandler
@ -104,8 +98,7 @@ const OutstaffingBlock = ({
positionId,
isSelected,
onSelect,
history,
role
apiRequest
})
}
>
@ -136,6 +129,6 @@ const OutstaffingBlock = ({
</div>
</OutsideClickHandler>
)
}
};
export default OutstaffingBlock

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

Binary file not shown.

Binary file not shown.

Binary file not shown.

Binary file not shown.

Binary file not shown.

Binary file not shown.

Binary file not shown.

Binary file not shown.

Binary file not shown.

Binary file not shown.

Binary file not shown.

Binary file not shown.

Binary file not shown.

Binary file not shown.

Binary file not shown.

Binary file not shown.

View File

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

37
src/helper.js Normal file
View File

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

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

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

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

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

BIN
src/images/arrowRight.png Normal file

Binary file not shown.

After

Width:  |  Height:  |  Size: 240 B

BIN
src/images/gitItemImg.png Normal file

Binary file not shown.

After

Width:  |  Height:  |  Size: 1.0 KiB

BIN
src/images/paymentIcon.png Normal file

Binary file not shown.

After

Width:  |  Height:  |  Size: 966 B

BIN
src/images/reports.png Normal file

Binary file not shown.

After

Width:  |  Height:  |  Size: 845 B

BIN
src/images/settingIcon.png Normal file

Binary file not shown.

After

Width:  |  Height:  |  Size: 836 B

BIN
src/images/summaryIcon.png Normal file

Binary file not shown.

After

Width:  |  Height:  |  Size: 1.4 KiB

BIN
src/images/timerIcon.png Normal file

Binary file not shown.

After

Width:  |  Height:  |  Size: 1.1 KiB

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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