Merge pull request #36 from apuc/achievements

Achievements
This commit is contained in:
kavalar 2021-10-18 12:26:12 +03:00 committed by GitHub
commit 76c46067ef
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23
16 changed files with 474 additions and 62 deletions

View File

@ -1,24 +1,26 @@
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 (<>
return (
<>
<h1>IT Аутстаффинг в России</h1>
<Router>
<Switch>
@ -29,10 +31,19 @@ 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
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>
@ -51,5 +62,4 @@ const App = (props) => {
)
}
export default App

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>
)
}

View File

@ -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;
}
}

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">

View File

@ -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;
}
}

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>

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;
let yearsString
if (years % 10 === 1) {
yearsString = 'год';
yearsString = 'год'
} else if (years === 11 || years === 12 || years === 13 || years === 14) {
yearsString = 'лет';
yearsString = 'лет'
} else if (years % 10 === 2 || years % 10 === 3 || years % 10 === 4) {
yearsString = 'года';
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>
</> }
<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={style.candidateSidebar__info__btn}>Выбрать к собеседованию</button>
<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

View File

@ -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;
}
}

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>
)
}

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;
}
}
}

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

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

View File

@ -25,3 +25,11 @@ h1 {
.container {
position: relative !important;
}
@media (max-width: 568px) {
.col-xs-12 {
width: 100% !important;
max-width: 100%;
flex: initial;
}
}

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';

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

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;
}
}
}