Merge pull request #36 from apuc/achievements

Achievements
This commit is contained in:
2021-10-18 12:26:12 +03:00
committed by GitHub
16 changed files with 474 additions and 62 deletions
+31 -21
View File
@@ -1,26 +1,28 @@
import React, { Suspense, lazy } from 'react'
import { BrowserRouter as Router, Route, Switch } from 'react-router-dom'
import { useSelector } from 'react-redux'
import { selectAuth } from './redux/outstaffingSlice';
import { selectAuth } from './redux/outstaffingSlice'
import 'bootstrap/dist/css/bootstrap.min.css'
import './fonts/stylesheet.css'
import { ProtectedRoute } from './components/ProtectedRoute/ProtectedRoute';
import { ProtectedRoute } from './components/ProtectedRoute/ProtectedRoute'
import { YMInitializer } from 'react-yandex-metrika';
import { YMInitializer } from 'react-yandex-metrika'
import AuthPageForDevelopers from './pages/AuthPageForDevelopers';
import AuthPageForPartners from './pages/AuthPageForPartners';
import HomePage from './pages/HomePage';
import CandidatePage from './pages/CandidatePage';
import CalendarPage from'./pages/CalendarPage';
import ReportPage from './pages/ReportFormPage.js';
import FormPage from './pages/FormPage.js';
import AuthPageForDevelopers from './pages/AuthPageForDevelopers'
import AuthPageForPartners from './pages/AuthPageForPartners'
import HomePage from './pages/HomePage'
import CandidatePage from './pages/CandidatePage'
import CalendarPage from './pages/CalendarPage'
import ReportPage from './pages/ReportFormPage.js'
import FormPage from './pages/FormPage.js'
import SingleReportPage from './pages/SingleReportPage'
const App = (props) => {
const isAuth = useSelector(selectAuth)
return (<>
<h1>IT Аутстаффинг в России</h1>
<Router>
return (
<>
<h1>IT Аутстаффинг в России</h1>
<Router>
<Switch>
<Route path='/authdev' exact>
<AuthPageForDevelopers />
@@ -29,15 +31,24 @@ const App = (props) => {
<AuthPageForPartners />
</Route>
<ProtectedRoute exact path='/' component={HomePage} />
<ProtectedRoute exact path='/candidate/:id' component={CandidatePage} />
<ProtectedRoute
exact
path='/candidate/:id'
component={CandidatePage}
/>
<ProtectedRoute path='/calendar' component={CalendarPage} />
<ProtectedRoute exact path='/candidate/:id/form' component={FormPage} />
<ProtectedRoute path='/report' component={ReportPage} />
<ProtectedRoute component={()=><div>Page not found</div>} />
<ProtectedRoute
exact
path='/candidate/:id/form'
component={FormPage}
/>
<ProtectedRoute exact path='/report' component={ReportPage} />
<ProtectedRoute path='/report/:id' component={SingleReportPage} />
<ProtectedRoute component={() => <div>Page not found</div>} />
</Switch>
</Router>
{/* <YMInitializer
</Router>
{/* <YMInitializer
accounts={[84188125]}
options={{
clickmap:true,
@@ -51,5 +62,4 @@ const App = (props) => {
)
}
export default App
+19
View File
@@ -0,0 +1,19 @@
import React from 'react'
import './achievement.scss'
export const Achievement = ({ achievement }) => {
return (
<div className='achievement'>
<div className='achievement__icon'>
<img src={achievement.img} />
</div>
<div className='achievement__popup'>
<div className='achievement__title'>{achievement.title}</div>
<div className='achievement__description'>
{achievement.description}
</div>
</div>
</div>
)
}
@@ -0,0 +1,51 @@
.achievement {
position: relative;
width: 50px;
height: 50px;
margin: 0 1rem;
&__popup {
position: absolute;
top: 50px;
left: 0;
width: 140px;
min-height: 70px;
padding: 1rem;
display: flex;
flex-direction: column;
justify-content: center;
align-items: center;
display: none;
text-align: left;
border-bottom: 3px solid #52b709;
border-left: 3px solid #52b709;
}
&__title {
color: #18586e;
font-size: 20px;
margin: 0;
padding: 0;
}
&__description {
color: #18586e;
font-size: 14px;
margin: 0;
padding: 0;
}
&__icon {
width: 50px;
height: 50px;
}
img {
width: 100%;
height: 100%;
}
&:hover .achievement__popup {
display: block;
}
}
+1 -1
View File
@@ -35,7 +35,7 @@ const Description = ({ onLoadMore, isLoadingMore }) => {
{
candidatesListArr && candidatesListArr.length > 0 ? candidatesListArr.map((el) => (
<div className="row" key={el.id}>
<div className="col-2">
<div className="col-2 col-xs-12">
<img className={style.description__img} src={el.photo} alt="" />
</div>
<div className="col-12 col-xl-6">
@@ -30,8 +30,11 @@
@media (max-width: 575.98px) {
.description__img {
position: absolute;
top: 80px;
left: 0;
top: 100px;
left: calc(50% - 60px);
}
.description__wrapper {
padding: 45px 40px 0 40px;
}
}
+8 -6
View File
@@ -3,6 +3,7 @@ import style from './Form.module.css';
import { fetchForm } from '../../server/server';
import { auth } from '../../redux/outstaffingSlice';
import { useHistory, useParams, Redirect } from 'react-router-dom';
import { Loader } from '../Loader/Loader';
import PhoneInput from 'react-phone-input-2'
import 'react-phone-input-2/lib/style.css'
import './form.css';
@@ -25,6 +26,7 @@ const Form = () => {
phone: '',
comment: '',
});
const [isFetching, setIsFetching] = useState(false);
const handleChange = (e) => {
const { id, value } = e.target;
@@ -38,6 +40,7 @@ const Form = () => {
const handleSubmit = (e) => {
e.preventDefault();
setIsFetching(true)
const formData = new FormData();
formData.append('profile_id', urlParams.id);
formData.append('email', data.email);
@@ -48,14 +51,13 @@ const Form = () => {
profile_id: urlParams.id,
...data,
}, history, role, logout: dispatch(auth(false)) }).then( (res)=> res.json()
.then( resJSON => setStatus(resJSON))
.then( resJSON => {
setStatus(resJSON);
setIsFetching(false);
})
)
};
console.log('s',status)
return (
<>
{status && <SweetAlert
@@ -104,7 +106,7 @@ const Form = () => {
></textarea>
<button onClick={handleSubmit} className={style.form__btn} type="submit">
Отправить
{ isFetching ? <Loader /> : 'Отправить' }
</button>
</form>
</div>
+40 -24
View File
@@ -1,37 +1,53 @@
import React from 'react';
import { Link } from 'react-router-dom';
import maleBig from '../../images/medium_male_big.png';
import style from './Sidebar.module.css';
import React from 'react'
import { Link } from 'react-router-dom'
import { Achievement } from '../Achievement/Achievement'
import maleBig from '../../images/medium_male_big.png'
import './sidebar.scss'
const getYearsString = (years) => {
let yearsString;
if (years%10 === 1) {
yearsString = 'год';
let yearsString
if (years % 10 === 1) {
yearsString = 'год'
} else if (years === 11 || years === 12 || years === 13 || years === 14) {
yearsString = 'лет';
} else if (years%10 === 2 || years%10 === 3 || years%10 === 4) {
yearsString = 'года';
yearsString = 'лет'
} else if (years % 10 === 2 || years % 10 === 3 || years % 10 === 4) {
yearsString = 'года'
} else {
yearsString = 'лет';
yearsString = 'лет'
}
return `${years} ${yearsString}`;
return `${years} ${yearsString}`
}
const Sidebar = ({ candidate }) => {
console.log('c', candidate)
return (
<div className={style.candidateSidebar}>
<div className={style.candidateSidebar__info}>
<img src={candidate.photo} alt="" />
{ candidate && candidate.years_of_exp && <>
<p className={style.candidateSidebar__info__e}>Опыт работы</p>
<p className={style.candidateSidebar__info__y}>{getYearsString(candidate.years_of_exp)}</p>
</> }
<Link to={`/candidate/${candidate.id}/form`}>
<button className={style.candidateSidebar__info__btn}>Выбрать к собеседованию</button>
<div className='candidateSidebar'>
<div className='candidateSidebar__info'>
<img src={candidate.photo} alt='' />
{candidate && candidate.years_of_exp && (
<>
<p className='candidateSidebar__experience-title'>Опыт работы</p>
<p className='candidateSidebar__experience'>
{getYearsString(candidate.years_of_exp)}
</p>
</>
)}
<Link to={`/candidate/${candidate.id}/form`}>
<button className='candidateSidebar__select'>
Выбрать к собеседованию
</button>
</Link>
<div className='candidateSidebar__achievements'>
{candidate &&
candidate.achievements &&
candidate.achievements.map((item) => {
return <Achievement achievement={item.achievement} />
})}
</div>
</div>
</div>
);
};
)
}
export default Sidebar;
export default Sidebar
@@ -6,6 +6,14 @@
border-bottom: none !important;
position: sticky;
top: 80px;
&__achievements {
display: flex;
flex-wrap: wrap;
justify-content: flex-start;
padding: 0 1rem;
margin-bottom: 80px;
}
}
.candidateSidebar__info {
@@ -25,7 +33,7 @@
border-radius: 100px;
}
.candidateSidebar__info__e {
.candidateSidebar__experience-title {
font-family: 'GT Eesti Pro Display';
font-size: 1.8em;
font-weight: normal;
@@ -35,7 +43,7 @@
margin-top: 20px;
}
.candidateSidebar__info__y {
.candidateSidebar__experience {
font-family: 'GT Eesti Pro Display';
font-size: 3em;
font-weight: 700;
@@ -44,7 +52,7 @@
line-height: normal;
}
.candidateSidebar__info__btn {
.candidateSidebar__select {
width: 280px;
height: 60px;
border-radius: 100px;
@@ -58,10 +66,10 @@
line-height: normal;
text-align: center;
margin-top: 20px;
margin-bottom: 120px;
margin-bottom: 40px;
}
.candidateSidebar__info__btn:hover {
.candidateSidebar__select:hover {
background: rgba(0, 0, 0, 0);
color: #73c141;
box-shadow: inset 0 0 0 3px #73c141;
@@ -142,7 +150,8 @@
flex-direction: column;
}
.candidateSidebar__info__e, .candidateSidebar__info__y {
.candidateSidebar__info__e,
.candidateSidebar__info__y {
width: 180px;
}
}
}
+16
View File
@@ -0,0 +1,16 @@
import React from 'react'
import './taskItem.scss'
export const TaskItem = ({ index, text, hours }) => {
return (
<div className='task-item'>
<div className='task-item__index'>{index}.</div>
<div className='task-item__text'>{text}</div>
<div className='task-item__hours'>
<div className='task-item__hours-value'>{hours}</div>
<div className='task-item__hours-text'>Количество часов</div>
</div>
</div>
)
}
+72
View File
@@ -0,0 +1,72 @@
.task-item {
display: flex;
justify-content: flex-start;
align-items: center;
&__index {
margin-top: 0.2rem;
color: #282828;
font-family: 'GT Eesti Pro Display';
font-size: 20px;
font-weight: 700;
line-height: 48.74px;
text-align: left;
letter-spacing: 0.34px;
}
&__text {
min-width: 525px;
max-width: 525px;
margin-left: 1.6rem;
color: #000000;
font-family: 'GT Eesti Pro Display';
font-size: 15px;
font-weight: 400;
letter-spacing: normal;
line-height: normal;
text-align: left;
}
&__hours {
margin-left: 3.3rem;
display: flex;
&-value {
width: 34px;
height: 34px;
display: flex;
justify-content: center;
align-items: center;
box-shadow: 6px 5px 20px rgba(82, 151, 34, 0.21);
border-radius: 50%;
background-color: #ffffff;
background-image: linear-gradient(to top, #6aaf5c 0%, #52b709 100%),
linear-gradient(
36deg,
rgba(255, 255, 255, 0) 0%,
rgba(255, 255, 255, 0.16) 47%,
rgba(255, 255, 255, 0.17) 50%,
rgba(255, 255, 255, 0) 100%
);
color: #ffffff;
font-family: 'Muller Extra Bold';
font-size: 16px;
font-weight: 400;
text-align: left;
letter-spacing: 0.8px;
}
&-text {
margin-left: 1rem;
width: 69px;
height: 25px;
color: #000000;
font-family: 'GT Eesti Pro Display - Thin';
font-size: 13px;
font-weight: 400;
letter-spacing: normal;
line-height: normal;
text-align: left;
}
}
}
+1
View File
@@ -0,0 +1 @@
<svg xmlns="http://www.w3.org/2000/svg" xmlns:xlink="http://www.w3.org/1999/xlink" width="5" height="9" viewBox="0 0 5 9"><g><g><image width="5" height="9" xlink:href="data:image/png;base64,iVBORw0KGgoAAAANSUhEUgAAAAUAAAAJCAYAAAD6reaeAAAAZ0lEQVQImV2OvRFAUBCE10ju56WvMUZIATSgDxogph96EGjAnOSZw4a7O98uoLRDucdHwiOEDEFaN80yKM8QMijVHlRVDuEVQhcCFx7EGKB8QPl8N5fULBNTpsRsnvUhrXfOUtr+P2/kAxfNQ+wYfgAAAABJRU5ErkJggg=="/></g></g></svg>

After

Width:  |  Height:  |  Size: 423 B

+1
View File
@@ -0,0 +1 @@
<svg xmlns="http://www.w3.org/2000/svg" xmlns:xlink="http://www.w3.org/1999/xlink" width="5" height="9" viewBox="0 0 5 9"><g><g><image width="5" height="9" xlink:href="data:image/png;base64,iVBORw0KGgoAAAANSUhEUgAAAAUAAAAJCAYAAAD6reaeAAAAaklEQVQImVWOvRFAQBSE10jez6XXGCOkABrQBw0Q0w89CDRgnuDO3Nlw55tvF8ijPELpTIWTHkIG4TkS1ELIoLzCrAAcVxB6ILyjacrPc0P5gvcuc3EdyS2RwdkFpyzBmdaHuD79bsafxwt/CxfNS2+70AAAAABJRU5ErkJggg=="/></g></g></svg>

After

Width:  |  Height:  |  Size: 427 B

+8
View File
@@ -24,4 +24,12 @@ h1 {
.container {
position: relative !important;
}
@media (max-width: 568px) {
.col-xs-12 {
width: 100% !important;
max-width: 100%;
flex: initial;
}
}
+1 -1
View File
@@ -1,4 +1,4 @@
import React from 'react';
import React, { useState } from 'react';
import { useDispatch, useSelector } from 'react-redux';
import { useHistory, useParams, Link } from 'react-router-dom';
import { currentCandidate, selectCurrentCandidate, auth } from '../redux/outstaffingSlice';
+107
View File
@@ -0,0 +1,107 @@
import React from 'react'
import { WithLogout } from '../hoc/withLogout'
import arrowLeft from '../images/right-arrow.png'
import SVG from 'react-inlinesvg'
import prevDateArrowIcon from '../images/prevDateArrow.svg'
import nextDateArrowIcon from '../images/nextDateArrow.svg'
import './singleReportPage.scss'
import { TaskItem } from '../components/TaskItem/TaskItem'
const tasks = [
{
index: 1,
text: 'Задача «67 – Навигационная система – Главное меню – Обновить иконки» заблокирована из-за отсутствия новых иконок',
hours: 3
},
{
index: 2,
text: 'Задача «83 – Навигационная система – Поиск по почтовому индексу – Добавить экран поиска по почтовому индексу» не может быть завершена, т.к. работа над задачей «82 – Навигационная система – Разработать модуль поиска по почтовому индексу» ещё не начата',
hours: 3
}
]
const SingleReportPage = () => {
return (
<WithLogout>
<div className='single-report-page'>
<div className='single-report-page__back'>
<div className='single-report-page__back-arrow'>
<img src={arrowLeft} />
</div>
<div className='single-report-page__back-text'>
Вернуться к списку
</div>
</div>
<div className='single-report-page__title'>
<div className='single-report-page__title-text'>Отчет за день</div>
<div className='single-report-page__title-date'>
<div className='single-report-page__title-date--prev'>
<button>
<SVG src={prevDateArrowIcon} />
</button>
</div>
<div className='single-report-page__title-date--actual'>
<img src='' />
<p></p>
</div>
<div className='single-report-page__title-date--next single-report-page__title-date--enabled'>
<button>
<SVG src={nextDateArrowIcon} />
</button>
</div>
</div>
</div>
<div className='single-report-page__tasks'>
<div className='single-report-page__tasks-title'>
<div className='single-report-page__marker'></div>
<h3>Какие задачи были выполнены?</h3>
</div>
{tasks.map((task) => {
return (
<div className='single-report-page__tasks-item'>
<TaskItem {...task} />
</div>
)
})}
</div>
<div className='single-report-page__troubles'>
<div className='single-report-page__troubles-title'>
<div className='single-report-page__marker'></div>
<h3>Какие сложности возникли?</h3>
</div>
<div className='single-report-page__troubles-item'>
91 Навигационная система Поиск адреса Разобраться, почему
находятся несколько пересечений Невского пр. и Казанской ул.
</div>
</div>
<div className='single-report-page__scheduled'>
<div className='single-report-page__scheduled-title'>
<div className='single-report-page__marker'></div>
<h3>Что планируется сделать завтра?</h3>
</div>
<div className='single-report-page__scheduled-item'>
91 Навигационная система Поиск адреса Разобраться, почему
находятся несколько пересечений Невского пр. и Казанской ул.
</div>
</div>
<div className='single-report-page__footer'>
<div className='single-report-page__footer-rectangle'></div>
<div className='single-report-page__hours'>
<div className='single-report-page__hours-value'></div>
<div className='single-report-page__hours-text'></div>
</div>
</div>
</div>
</WithLogout>
)
}
export default SingleReportPage
+97
View File
@@ -0,0 +1,97 @@
.single-report-page {
padding-top: 4.6rem;
&__back {
display: flex;
justify-content: flex-start;
align-items: center;
&-text {
margin-left: 3.1rem;
color: #000000;
font-family: 'GT Eesti Pro Display - Thin';
font-size: 18px;
font-weight: 400;
letter-spacing: normal;
line-height: 36px;
text-align: left;
}
}
&__title {
display: flex;
justify-content: flex-start;
align-items: center;
margin-top: 3rem;
&-text {
color: #282828;
font-family: 'GT Eesti Pro Display';
font-size: 33px;
font-weight: 700;
line-height: 48.74px;
text-align: left;
letter-spacing: 0.56px;
}
&-date {
margin-top: 0.2rem;
margin-left: 3rem;
display: flex;
justify-content: flex-start;
align-items: center;
}
button {
border: none;
outline: none;
width: 31px;
height: 31px;
background-color: #f6f6f6;
border-radius: 50%;
display: flex;
justify-content: center;
align-items: center;
}
}
&__marker {
width: 6px;
height: 6px;
background-color: #18586e;
border-radius: 50%;
margin-right: 0.8rem;
}
&__tasks,
&__troubles,
&__scheduled {
margin-top: 3.7rem;
&-title {
display: flex;
justify-content: flex-start;
align-items: center;
h3 {
color: #18586e;
font-family: 'GT Eesti Pro Display';
font-size: 20px;
font-weight: 500;
letter-spacing: normal;
line-height: normal;
text-align: left;
}
}
&-item {
margin-top: 2.4rem;
width: 580px;
color: #000000;
font-family: 'GT Eesti Pro Display';
font-size: 15px;
font-weight: 400;
letter-spacing: normal;
line-height: normal;
text-align: left;
}
}
}