Merge branch 'main' into tracker-connect-back

# Conflicts:
#	src/assets/images/accept.png
#	src/assets/images/mainTaskCommentImg.png
#	src/components/Modal/TrackerModal/TrackerModal.jsx
#	src/components/UI/ModalTicket/ModalTicket.jsx
#	src/components/UI/TicketFullScreen/TicketFullScreen.jsx
#	src/pages/ProjectTracker/ProjectTracker.js
#	src/redux/projectsTrackerSlice.js
This commit is contained in:
2023-06-12 22:22:51 +03:00
549 changed files with 8228 additions and 8502 deletions

View File

@ -1,19 +0,0 @@
import React from 'react'
import './achievement.scss'
export const Achievement = ({ achievement }) => {
return (
<div className='achievement'>
<div className='achievement__icon'>
<img src={achievement.img} alt='achievement' />
</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,19 @@
import React from "react";
import "./achievement.scss";
export const Achievement = ({ achievement }) => {
return (
<div className="achievement">
<div className="achievement__icon">
<img src={achievement.img} alt="achievement" />
</div>
<div className="achievement__popup">
<div className="achievement__title">{achievement.title}</div>
<div className="achievement__description">
{achievement.description}
</div>
</div>
</div>
);
};

View File

@ -1,20 +1,18 @@
import React, { useEffect, useRef, useState } from "react";
import { Link, useNavigate } from "react-router-dom";
import { useDispatch, useSelector } from "react-redux";
import { useNavigate } from "react-router-dom";
import { Loader } from "../Loader/Loader";
import { loading, selectIsLoading } from "@redux/loaderSlice";
import { auth, selectAuth, setUserInfo } from "@redux/outstaffingSlice";
import { setRole } from "@redux/roleSlice";
import { auth, selectAuth, setUserInfo } from "../../redux/outstaffingSlice";
import { loading } from "../../redux/loaderSlice";
import { setRole } from "../../redux/roleSlice";
import { selectIsLoading } from "../../redux/loaderSlice";
import { apiRequest } from "@api/request";
import ModalErrorLogin from "../../components/UI/ModalErrorLogin/ModalErrorLogin";
import { Loader } from "@components/Common/Loader/Loader";
import ModalErrorLogin from "@components/Modal/ModalErrorLogin/ModalErrorLogin";
import ModalRegistration from "@components/Modal/ModalRegistration/ModalRegistration";
import { apiRequest } from "../../api/request";
import ellipse from "../../images/ellipse.png";
import ModalRegistration from "../UI/ModalRegistration/ModalRegistration";
import ellipse from "assets/icons/ellipse.png";
import "./authBox.scss";
@ -51,7 +49,7 @@ export const AuthBox = ({ title }) => {
data: formData,
}).then((res) => {
if (!res.access_token) {
setError("Некорректные данные для входа");
setError("Введены некоректные данные для входа");
setModalError(true);
dispatch(loading(false));
} else {
@ -78,7 +76,7 @@ export const AuthBox = ({ title }) => {
Войти в <span>систему</span>
</h2>
<div className="auth-box__title">
<img src={ellipse} alt="" />
<img src={ellipse} alt="." />
<span>{title}</span>
</div>
<form ref={ref} className="auth-box__form">

View File

@ -1,78 +0,0 @@
import React, {useEffect, useState} from 'react'
import {useSelector} from 'react-redux'
import {Link, Navigate, useNavigate} from 'react-router-dom'
import CalendarComponent from './CalendarComponent'
import {currentMonth} from './calendarHelper'
import {Footer} from '../Footer/Footer'
import {LogoutButton} from "../LogoutButton/LogoutButton";
import {selectCurrentCandidate} from '../../redux/outstaffingSlice'
import rectangle from '../../images/rectangle_secondPage.png'
import './calendar.scss'
import {urlForLocal} from "../../helper";
const Calendar = () => {
if(localStorage.getItem('role_status') !== '18') {
return <Navigate to="/profile" replace/>
}
const candidateForCalendar = useSelector(selectCurrentCandidate);
const [month, setMonth] = useState('');
const navigate = useNavigate();
useEffect(() => {
setMonth(currentMonth)
}, [month]);
const {name, skillsName, photo} = candidateForCalendar;
const abbreviatedName = name && name.substring(0, name.lastIndexOf(' '));
return (
<div className='container'>
<section className='calendar'>
<div className='row'>
<div className='calendar__header'>
<h2 className='calendar__profile'>
Добрый день, <span>Александр !</span>
</h2>
<LogoutButton />
</div>
<div className='col-12 col-xl-12 d-flex justify-content-between align-items-center flex-column flex-sm-row'>
<div className='calendar__info'>
{photo && <img className='calendar__info-img' src={urlForLocal(photo)} alt='img'/>}
<h3 className='calendar__info-name'>{abbreviatedName}</h3>
</div>
<div className='calendar__title'>
<h3 className='calendar__title-text'>{skillsName} разработчик</h3>
<img className='calendar__title-img' src={rectangle} alt='img'/>
</div>
<div>
<Link to='/report'>
<button className='calendar__btn'>Заполнить отчет за день</button>
</Link>
</div>
</div>
</div>
<div className='row'>
<div className='col-12 col-xl-12'>
<CalendarComponent onSelect={() => { navigate('/report/0') }}/>
<p className='calendar__hours'>
{month} : <span> 60 часов </span>
</p>
</div>
</div>
<Footer/>
</section>
</div>
)
};
export default Calendar

View File

@ -0,0 +1,90 @@
import React, { useEffect, useState } from "react";
import { useSelector } from "react-redux";
import { Link, Navigate, useNavigate } from "react-router-dom";
import { selectCurrentCandidate } from "@redux/outstaffingSlice";
import { urlForLocal } from "@utils/helper";
import { Footer } from "@components/Common/Footer/Footer";
import { LogoutButton } from "@components/LogoutButton/LogoutButton";
import rectangle from "assets/images/rectangle_secondPage.png";
import CalendarComponent from "./CalendarComponent";
import "./calendar.scss";
import { currentMonth } from "./calendarHelper";
const Calendar = () => {
if (localStorage.getItem("role_status") !== "18") {
return <Navigate to="/profile" replace />;
}
const candidateForCalendar = useSelector(selectCurrentCandidate);
const [month, setMonth] = useState("");
const navigate = useNavigate();
useEffect(() => {
setMonth(currentMonth);
}, [month]);
const { name, skillsName, photo } = candidateForCalendar;
const abbreviatedName = name && name.substring(0, name.lastIndexOf(" "));
return (
<div className="container">
<section className="calendar">
<div className="row">
<div className="calendar__header">
<h2 className="calendar__profile">
Добрый день, <span>Александр !</span>
</h2>
<LogoutButton />
</div>
<div className="col-12 col-xl-12 d-flex justify-content-between align-items-center flex-column flex-sm-row">
<div className="calendar__info">
{photo && (
<img
className="calendar__info-img"
src={urlForLocal(photo)}
alt="img"
/>
)}
<h3 className="calendar__info-name">{abbreviatedName}</h3>
</div>
<div className="calendar__title">
<h3 className="calendar__title-text">{skillsName} разработчик</h3>
<img className="calendar__title-img" src={rectangle} alt="img" />
</div>
<div>
<Link to="/report">
<button className="calendar__btn">
Заполнить отчет за день
</button>
</Link>
</div>
</div>
</div>
<div className="row">
<div className="col-12 col-xl-12">
<CalendarComponent
onSelect={() => {
navigate("/report/0");
}}
/>
<p className="calendar__hours">
{month} : <span> 60 часов </span>
</p>
</div>
</div>
<Footer />
</section>
</div>
);
};
export default Calendar;

View File

@ -1,92 +0,0 @@
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 './calendarHelper'
import './calendarComponent.scss'
const CalendarComponent = ({ onSelect }) => {
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 dayStyles(day) {
if (beforeToday(day)) return `before`
if (isToday(day)) return `today`
if (day.day() === 6 || day.day() === 0) return `selected`
return ''
}
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={() => { setValue(day); onSelect(day) }}
key={day}
className={dayStyles(day)}
name={day.format('dddd')}
id='btn'
>
<img className={'calendar__icon'} src={calendarIcon} alt='' />
{currentMonthAndDay(day)}
</button>
))
)}
</div>
</div>
</div>
)
}
export default CalendarComponent

View File

@ -0,0 +1,100 @@
import moment from "moment";
import "moment/locale/ru";
import React, { useEffect, useState } from "react";
import calendarIcon from "assets/icons/calendar.svg";
import ellipse from "assets/icons/ellipse.png";
import rectangle from "assets/images/rectangle__calendar.png";
import "./calendarComponent.scss";
import { calendarHelper, currentMonthAndDay } from "./calendarHelper";
const CalendarComponent = ({ onSelect }) => {
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 dayStyles(day) {
if (beforeToday(day)) return `before`;
if (isToday(day)) return `today`;
if (day.day() === 6 || day.day() === 0) return `selected`;
return "";
}
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={() => {
setValue(day);
onSelect(day);
}}
key={day}
className={dayStyles(day)}
name={day.format("dddd")}
id="btn"
>
<img className={"calendar__icon"} src={calendarIcon} alt="" />
{currentMonthAndDay(day)}
</button>
))
)}
</div>
</div>
</div>
);
};
export default CalendarComponent;

View File

@ -1,6 +1,6 @@
.calendar {
margin-bottom: 40px;
font-family: 'LabGrotesque', sans-serif;
font-family: "LabGrotesque", sans-serif;
&__header {
display: flex;
@ -85,7 +85,7 @@
);
border: none;
color: #ffffff;
font-family: 'Muller';
font-family: "Muller";
font-size: 1.6em;
letter-spacing: normal;
text-align: center;

View File

@ -210,12 +210,6 @@
padding: 28px 10px 48px 10px;
&__header {
//h3 {
// position: absolute;
// top: -10%;
// left: 25%;
//}
&-box {
margin-left: 20px;
}
@ -233,101 +227,6 @@
}
}
//@media (max-width: 768px) {
// .calendar-component__form > button {
// width: 70px;
// height: 40px;
//
// img {
// display: none;
// }
// }
//}
//@media (max-width: 540.98px) {
// .calendar-component__form > button {
// width: 68px;
// height: 40px;
// }
//}
//
//@media (max-width: 520.98px) {
// .calendar-component__form > button {
// width: 66px;
// height: 40px;
// }
//}
//
//@media (max-width: 500.98px) {
// .calendar-component__form > button {
// width: 64px;
// height: 40px;
// }
//}
//
//@media (max-width: 480.98px) {
// .calendar-component__form > button {
// width: 60px;
// height: 40px;
// }
//}
//
//@media (max-width: 460.98px) {
// .calendar-component__form > button {
// width: 56px;
// height: 40px;
// }
//}
//
//@media (max-width: 440.98px) {
// .calendar-component__form > button {
// width: 52px;
// height: 40px;
// }
//}
//
//@media (max-width: 428.98px) {
// .calendar-component__form > button {
// width: 50px;
// height: 40px;
// }
//}
//
//@media (max-width: 414.98px) {
// .calendar-component__form > button {
// width: 49px;
// height: 40px;
// }
//}
//
//@media (max-width: 395.98px) {
// .calendar-component__form > button {
// width: 46px;
// height: 40px;
// }
//}
//
//@media (max-width: 350.98px) {
// .calendar-component__form > button {
// width: 44px;
// height: 40px;
// }
//}
//
//@media (max-width: 349.98px) {
// .calendar-component__form > button {
// width: 42px;
// height: 40px;
// }
//}
//
//@media (max-width: 346.98px) {
// .calendar-component__form > button {
// width: 40px;
// height: 40px;
// }
//}
.calendar__icon {
margin-right: 10px;
margin-top: -4px;

View File

@ -1,32 +1,32 @@
import React, { useEffect, useState } from "react";
import { useParams, Link, useNavigate, Navigate } from "react-router-dom";
import { useSelector, useDispatch } from "react-redux";
import SkillSection from "../SkillSection/SkillSection";
import Sidebar from "../CandidateSidebar/CandidateSidebar";
import { ProfileHeader } from "../ProfileHeader/ProfileHeader";
import { ProfileBreadcrumbs } from "../ProfileBreadcrumbs/ProfileBreadcrumbs";
import { Footer } from "../Footer/Footer";
import { useDispatch, useSelector } from "react-redux";
import { Link, Navigate, useNavigate, useParams } from "react-router-dom";
import {
currentCandidate,
selectCurrentCandidate,
} from "../../redux/outstaffingSlice";
} from "@redux/outstaffingSlice";
import { apiRequest } from "../../api/request";
import { createMarkup } from "../../helper";
import { LEVELS, SKILLS } from "@utils/constants";
import { createMarkup } from "@utils/helper";
import gitImgItem from "../../images/gitItemImg.png";
import rectangle from "../../images/rectangle_secondPage.png";
import front from "../Outstaffing/images/front_end.png";
import back from "../Outstaffing/images/back_end.png";
import design from "../Outstaffing/images/design.png";
import rightArrow from "../../images/arrowRight.png";
import { apiRequest } from "@api/request";
import { LEVELS, SKILLS } from "../../constants/constants";
import Sidebar from "@components/CandidateSidebar/CandidateSidebar";
import { Footer } from "@components/Common/Footer/Footer";
import { Navigation } from "@components/Navigation/Navigation";
import { ProfileBreadcrumbs } from "@components/ProfileBreadcrumbs/ProfileBreadcrumbs";
import { ProfileHeader } from "@components/ProfileHeader/ProfileHeader";
import SkillSection from "@components/SkillSection/SkillSection";
import rightArrow from "assets/icons/arrows/arrowRight.svg";
import gitImgItem from "assets/icons/gitItemImg.svg";
import back from "assets/images/partnerProfile/back-end.webp";
import design from "assets/images/partnerProfile/design.webp";
import front from "assets/images/partnerProfile/front-end.webp";
import rectangle from "assets/images/rectangle_secondPage.png";
import "./candidate.scss";
import { Navigation } from "../Navigation/Navigation";
const Candidate = () => {
if (localStorage.getItem("role_status") !== "18") {

View File

@ -156,41 +156,6 @@
}
}
//@media (max-width: 575.98px) {
// .candidate {
// &__title {
// h2 {
// font-size: 5em;
// line-height: normal;
// }
// }
//
// &__arrow {
// margin-bottom: 40px;
//
// &-img {
// img {
// cursor: pointer;
// }
// }
//
// &-sp {
// span {
// margin-left: 40px;
// margin-right: 120px;
// font-family: 'GT Eesti Pro Display', sans-serif;
// font-size: 1.8em;
// font-weight: 100;
// font-style: normal;
// letter-spacing: normal;
// line-height: 36px;
// text-align: left;
// }
// }
// }
// }
//}
@media (max-width: 375.98px) {
.candidate {
&__title {
@ -216,29 +181,6 @@
@media (max-width: 575.98px) {
.candidate {
//&__header {
// display: flex;
// flex-direction: column;
// margin-left: 0;
// margin-top: 40px;
//}
//&__main {
// &-description {
// h2 {
// font-size: 3.2em;
// text-align: center;
// position: absolute;
// top: -410px;
// left: 0;
// }
//
// img {
// display: none;
// }
// }
//}
&__btn {
display: block;
width: 221px;

View File

@ -1,10 +1,10 @@
import React, { useState } from "react";
import { Link } from "react-router-dom";
import { Achievement } from "../Achievement/Achievement";
import ModalAspt from "../UI/ModalAspt/ModalAspt";
import { urlForLocal } from "../../helper";
import { LEVELS, SKILLS } from "../../constants/constants";
import { LEVELS, SKILLS } from "@utils/constants";
import { urlForLocal } from "@utils/helper";
import { Achievement } from "@components/Achievement/Achievement";
import ModalAspirant from "@components/Modal/ModalAspirant/ModalAspirant";
import "./candidateSidebar.scss";
@ -39,11 +39,11 @@ const CandidateSidebar = ({ candidate, setActiveSnippet, activeSnippet }) => {
return (
<div className="candidate-sidebar">
<ModalAspt
<ModalAspirant
active={addAspt}
setActive={setAddAspt}
level={levelAspt}
></ModalAspt>
></ModalAspirant>
<div className="candidate-sidebar__info">
<div className="candidate-sidebar__position">

View File

@ -1,7 +1,8 @@
import React from "react";
import cardCalendar from "../../../images/cardCalendar.svg";
import { Link } from "react-router-dom";
import cardCalendar from "assets/icons/cardCalendar.svg";
import "./cardArticle.scss";
export const CardArticle = ({ images, title, data, id }) => {

View File

@ -1,20 +0,0 @@
import React from 'react'
import rightArrow from "../../images/arrowRight.png"
import { Link } from 'react-router-dom'
import './CardControl.scss'
export const CardControl = ({title,path,description, img}) => {
return (
<Link to={`/${path}`} className='control-card'>
<div className='control-card__about'>
<img src={img} alt='itemImg' />
<h3>{title}</h3>
</div>
<div className='control-card__info'>
<p dangerouslySetInnerHTML={{ __html: description }}></p>
<div className='control-card__infoLink'>
<img src={rightArrow} alt='arrow' />
</div>
</div>
</Link>)
}

View File

@ -0,0 +1,23 @@
import React from "react";
import { Link } from "react-router-dom";
import rightArrow from "assets/icons/arrows/arrowRight.svg";
import "./CardControl.scss";
export const CardControl = ({ title, path, description, img }) => {
return (
<Link to={`/${path}`} className="control-card">
<div className="control-card__about">
<img src={img} alt="itemImg" />
<h3>{title}</h3>
</div>
<div className="control-card__info">
<p dangerouslySetInnerHTML={{ __html: description }}></p>
<div className="control-card__infoLink">
<img src={rightArrow} alt="arrow" />
</div>
</div>
</Link>
);
};

View File

@ -1,73 +1,73 @@
.control-card{
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;
.control-card {
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);
}
&: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: 1175px) {
width: 48%;
max-width: none;
}
@media (max-width: 925px) {
width: 100%;
padding: 15px 25px;
}
@media (max-width: 925px) {
width: 100%;
padding: 15px 25px;
}
&__about {
display: flex;
column-gap: 20px;
align-items: center;
margin-bottom: 30px;
&__about {
display: flex;
column-gap: 20px;
align-items: center;
margin-bottom: 30px;
@media (max-width: 925px) {
margin-bottom: 15px;
}
@media (max-width: 925px) {
margin-bottom: 15px;
}
h3 {
color: #000000;
font-weight: 500;
font-size: 18px;
line-height: 22px;
max-width: 125px;
margin-bottom: 0;
}
}
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;
&__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;
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;
}
}
}
span {
color: #52b709;
font-weight: 700;
}
}
&Link {
width: 48px;
height: 48px;
background: #ddeec6;
border-radius: 50px;
display: flex;
justify-content: center;
align-items: center;
}
}
}

View File

@ -1,25 +0,0 @@
import React from "react";
import {Link} from "react-router-dom";
import rightArrow from "../../images/arrowRight.png"
import './categoriesItem.scss'
export const CategoriesItem = ({img, title, skills, available, link}) => {
return(
<Link to={link} className={available ? "categoriesItem" : "categoriesItem categoriesItem__disable"}>
<div className='categoriesItem__title'>
<img src={img} alt='img' />
<h5>{title}</h5>
</div>
<div className='categoriesItem__description'>
<p>{skills}</p>
<div className='more'>
<img src={rightArrow} alt="arrow" />
</div>
</div>
</Link>
)
};
export default CategoriesItem

View File

@ -0,0 +1,30 @@
import React from "react";
import { Link } from "react-router-dom";
import rightArrow from "assets/icons/arrows/arrowRight.svg";
import "./categoriesItem.scss";
export const CategoriesItem = ({ img, title, skills, available, link }) => {
return (
<Link
to={link}
className={
available ? "categoriesItem" : "categoriesItem categoriesItem__disable"
}
>
<div className="categoriesItem__title">
<img src={img} alt="img" />
<h5>{title}</h5>
</div>
<div className="categoriesItem__description">
<p>{skills}</p>
<div className="more">
<img src={rightArrow} alt="arrow" />
</div>
</div>
</Link>
);
};
export default CategoriesItem;

View File

@ -2,7 +2,7 @@
display: flex;
flex-direction: column;
padding: 33px 32px 25px 28px;
background: #FFFFFF;
background: #ffffff;
border-radius: 12px;
transition: all 0.3s ease;
position: relative;
@ -18,7 +18,6 @@
pointer-events: none;
}
&__title {
display: flex;
align-items: center;
@ -43,7 +42,7 @@
p {
max-width: 181px;
margin-bottom: 0;
color: #6F6F6F;
color: #6f6f6f;
font-weight: 400;
font-size: 12px;
line-height: 20px;
@ -55,7 +54,7 @@
justify-content: center;
width: 48px;
height: 48px;
background: #DDEEC6;
background: #ddeec6;
border-radius: 50px;
}
}

View File

@ -1,12 +1,13 @@
import React from "react";
import { NavLink } from "react-router-dom";
import { scrollToForm } from "../../helper";
import userIcon from "../../images/userIcon.png";
import { scrollToForm } from "@utils/helper";
import userIcon from "assets/icons/userIcon.svg";
import "./authHeader.scss";
export const AuthHeader = ({}) => {
export const AuthHeader = () => {
return (
<div className="auth-header">
<div className="auth-header__logo">

View File

@ -53,7 +53,7 @@
}
.candidate {
color: #1458DD;
color: #1458dd;
}
}
}

View File

@ -0,0 +1,16 @@
import React from "react";
import classes from "./basebutton.module.scss";
export const BaseButton = ({ children, styles, ...props }) => {
return (
<button
className={styles ? `${styles} ${classes.button}` : classes.button}
{...props}
>
{children}
</button>
);
};
export default BaseButton;

View File

@ -0,0 +1,18 @@
.button {
display: flex;
align-items: center;
justify-content: center;
background-color: #52b709;
border-radius: 44px;
color: white;
font-style: normal;
font-family: "LabGrotesque", sans-serif;
border: none;
transition: 0.5s;
&:hover {
transition: 0.5s;
background-color: #52b709a8;
text-decoration: none;
}
}

View File

@ -0,0 +1,53 @@
import React from "react";
import email from "assets/icons/emailLogo.svg";
import tg from "assets/icons/tgFooter.svg";
import vk from "assets/icons/vkLogo.svg";
import logo from "assets/images/logo/LogoITguild.svg";
import "./footer.scss";
export const Footer = () => {
return (
<footer>
<div className="container">
<div className="footer">
<div className="footer__top">
<img src={logo} alt="logo" className="logo" />
<p>
Подберем и документально оформим IT-специалистов, после чего
передадим исполнителей под ваше руководство. Вы получаете полное
управление над сотрудниками, имея возможность контролировать и
заменять IT штат.
</p>
<div className="footer__copyright">
© {new Date().getFullYear()} - Все права защищены
</div>
</div>
<div className="footer__bottom">
<div className="footer__social">
<div className="footer__social__icons">
<a>
<img src={vk} alt="vk" />
</a>
<a>
<img src={tg} alt="tg" />
</a>
</div>
<p>Войти в команду</p>
</div>
<div className="footer__info">
<div className="footer__mail">
<a>
<img src={email} alt="email" />
</a>
<p>office@itguild.info</p>
</div>
<a className="footer__policy">Политика конфиденциальности</a>
</div>
</div>
</div>
</div>
</footer>
);
};

View File

@ -0,0 +1,121 @@
footer {
border-top: 1px solid #ebebeb;
padding: 35px 0 50px;
}
.footer {
&__top {
display: flex;
align-items: start;
@media (max-width: 620px) {
flex-direction: column;
align-items: center;
row-gap: 15px;
}
.logo {
width: 83px;
}
p {
max-width: 620px;
margin-left: 33px;
font-weight: 400;
font-size: 12px;
line-height: 16px;
color: #5b6871;
@media (max-width: 620px) {
margin-left: 0;
text-align: center;
}
}
}
&__bottom {
display: flex;
align-items: center;
margin-top: 22px;
justify-content: space-between;
@media (max-width: 590px) {
flex-direction: column;
}
}
&__social {
display: flex;
align-items: center;
@media (max-width: 590px) {
flex-direction: column;
justify-content: center;
}
&__icons {
display: flex;
column-gap: 10px;
}
p {
cursor: pointer;
margin-left: 60px;
font-weight: 500;
font-size: 14px;
line-height: 33px;
@media (max-width: 590px) {
margin-left: 0;
}
}
}
&__info {
display: flex;
align-items: center;
}
&__mail {
display: flex;
align-items: center;
column-gap: 13px;
p {
font-weight: 400;
font-size: 12px;
line-height: 16px;
color: #5b6871;
}
}
&__policy {
font-weight: 400;
font-size: 10px;
line-height: 16px;
color: #5b6871;
margin-left: 150px;
@media (max-width: 720px) {
margin-left: 30px;
}
&:hover {
color: #5b6871;
text-decoration: none;
}
}
&__copyright {
margin-left: auto;
@media (max-width: 910px) {
min-width: 142px;
margin-left: 15px;
}
@media (max-width: 620px) {
margin-left: 0;
}
}
}

View File

@ -0,0 +1,17 @@
import React from "react";
import SVGLoader from "react-loader-spinner";
import "./loader.scss";
export const Loader = ({ width = 50, height = 50, style }) => {
return (
<div className="loader">
<SVGLoader
type="Circles"
color={style ? style : `#fff`}
height={height}
width={width}
/>
</div>
);
};

View File

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

View File

@ -0,0 +1,30 @@
import React from "react";
import "./modalLayout.scss";
export const ModalLayout = ({
active,
setActive,
children,
styles,
...props
}) => {
return (
<div
className={active ? `modal-layout active` : "modal-layout"}
onClick={() => setActive(false)}
{...props}
>
<div
className={
styles ? `modal-layout__content ${styles}` : "modal-layout__content"
}
onClick={(e) => e.stopPropagation()}
>
{children}
</div>
</div>
);
};
export default ModalLayout;

View File

@ -0,0 +1,28 @@
.modal-layout {
z-index: 9;
height: 100%;
width: 100%;
background-color: rgba(0, 0, 0, 0.11);
position: fixed;
top: 0;
left: 0;
display: flex;
align-items: center;
justify-content: center;
transform: scale(0);
&__content {
position: relative;
background: linear-gradient(180deg, #ffffff 0%, #ebebeb 100%);
border-radius: 24px;
padding: 60px 60px 30px 60px;
display: flex;
flex-direction: column;
align-items: center;
justify-content: center;
}
}
.modal-layout.active {
transform: scale(1);
}

View File

@ -2,20 +2,18 @@ import React from "react";
import { useSelector } from "react-redux";
import { Link } from "react-router-dom";
import { selectProfiles } from "@redux/outstaffingSlice";
import { LEVELS, SKILLS } from "@utils/constants";
import { urlForLocal } from "@utils/helper";
import cursorImg from "assets/icons/cursorImg.svg";
import rectangle from "assets/images/rectangle_secondPage.png";
import ErrorBoundary from "../../hoc/ErrorBoundary";
import { LEVELS, SKILLS } from "../../constants/constants";
import { selectProfiles } from "../../redux/outstaffingSlice";
import { urlForLocal } from "../../helper";
import male from "../../images/medium_male.png";
import rectangle from "../../images/rectangle_secondPage.png";
import cursorImg from "../../images/cursorImg.png";
import "./description.scss";
const Description = ({ onLoadMore, isLoadingMore }) => {
const Description = ({ onLoadMore }) => {
const candidatesListArr = useSelector(selectProfiles);
return (

View File

@ -1,49 +0,0 @@
import React from 'react'
import logo from '../../images/logoGuild.png'
import vk from '../../images/vkLogo.svg'
import tg from '../../images/tgFooter.png'
import email from '../../images/emailLogo.svg'
import './footer.scss'
export const Footer = () => {
return (
<footer>
<div className='container'>
<div className='footer'>
<div className='footer__top'>
<img src={logo} alt='logo' />
<p>Подберем и документально оформим IT-специалистов, после чего передадим исполнителей под ваше руководство.
Вы получаете полное управление над сотрудниками, имея возможность контролировать и заменять IT штат.</p>
<div className='footer__copyright'>
© {new Date().getFullYear()} - Все права защищены
</div>
</div>
<div className='footer__bottom'>
<div className='footer__social'>
<div className='footer__social__icons'>
<a>
<img src={vk} alt='vk' />
</a>
<a>
<img src={tg} alt='tg' />
</a>
</div>
<p>Войти в команду</p>
</div>
<div className='footer__info'>
<div className='footer__mail'>
<a>
<img src={email} alt='email' />
</a>
<p>office@itguild.info</p>
</div>
<a className='footer__policy'>Политика конфиденциальности</a>
</div>
</div>
</div>
</div>
</footer>
)
}

View File

@ -1,215 +0,0 @@
footer {
border-top: 1px solid #ebebeb;
padding: 35px 0 50px;
}
.footer {
&__top {
display: flex;
align-items: start;
@media (max-width: 620px) {
flex-direction: column;
align-items: center;
row-gap: 15px;
}
p {
max-width: 620px;
margin-left: 33px;
font-weight: 400;
font-size: 12px;
line-height: 16px;
color: #5B6871;
@media (max-width: 620px) {
margin-left: 0;
text-align: center;
}
}
}
&__bottom {
display: flex;
align-items: center;
margin-top: 22px;
justify-content: space-between;
@media (max-width: 590px) {
flex-direction: column;
}
}
&__social {
display: flex;
align-items: center;
@media (max-width: 590px) {
flex-direction: column;
justify-content: center;
}
&__icons {
display: flex;
column-gap: 10px;
}
p {
cursor: pointer;
margin-left: 60px;
font-weight: 500;
font-size: 14px;
line-height: 33px;
@media (max-width: 590px) {
margin-left: 0;
}
}
}
&__info {
display: flex;
align-items: center;
}
&__mail {
display: flex;
align-items: center;
column-gap: 13px;
p {
font-weight: 400;
font-size: 12px;
line-height: 16px;
color: #5B6871;
}
}
&__policy {
font-weight: 400;
font-size: 10px;
line-height: 16px;
color: #5B6871;
margin-left: 150px;
@media (max-width: 720px) {
margin-left: 30px;
}
&:hover {
color: #5B6871;
text-decoration: none;
}
}
&__copyright {
margin-left: auto;
@media (max-width: 910px) {
min-width: 142px;
margin-left: 15px;
}
@media (max-width: 620px) {
margin-left: 0;
}
}
//margin-top: -3rem;
//
//&__left {
// display: flex;
// align-items: center;
//}
//
//&__description {
// padding: 0 100px 0 34px;
//
// span {
// color: #18586e;
// font-family: 'GT Eesti Pro Display';
// font-size: 1.6em;
// font-weight: 400;
// font-style: normal;
// letter-spacing: normal;
// line-height: 16.81px;
// text-align: left;
// }
//}
//
//&__icon {
// text-align: end;
//
// img {
// margin-left: 20px;
// }
//}
//
//&__right {
// display: flex;
// flex-direction: column;
// align-items: left;
//}
//
//&__phone {
// color: #003b65;
// font-family: 'CeraPro';
// font-size: 2.1em;
// letter-spacing: normal;
// line-height: 25px;
// text-align: left;
//}
//
//&__working-hours {
// color: #003b65;
// font-family: 'CeraPro';
// font-size: 1.2em;
// font-weight: 400;
// font-style: normal;
// letter-spacing: normal;
// line-height: normal;
// margin-left: 24px;
//}
//
//&__copyright {
// padding: 1rem 1rem 1rem 5.6rem;
// font-family: 'Muller';
// font-weight: 300;
// font-size: 1.2em;
//}
}
//@media (max-width: 1199px) {
// .footer {
// &__left {
// margin-bottom: 20px;
// }
// }
//}
//
//@media (max-width: 575.98px) {
// .footer {
// &__left {
// margin-top: 80px;
// }
//
// &__description {
// padding: 0;
// margin-left: 10px;
//
// span {
// font-size: 1.2em;
// }
// }
//
// &__icon {
// img {
// margin-left: 10px;
// }
// }
//
// &__right {
// margin-bottom: 20px;
// }
// }
//}

View File

@ -1,134 +0,0 @@
import React, {useEffect, useState} from 'react'
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'
import './form.scss'
import {apiRequest} from "../../api/request";
import Swal from 'sweetalert2'
import withReactContent from 'sweetalert2-react-content'
const SweetAlert = withReactContent(Swal);
const Form = () => {
const navigate = useNavigate();
const urlParams = useParams();
const [status, setStatus] = useState(null);
const [data, setData] = useState({
email: '',
phone: '',
comment: ''
});
const [isFetching, setIsFetching] = useState(false);
const handleModal = (status) => {
SweetAlert.fire({
text: status !== 200 || 201
? 'Какие-то неполадки =('
: 'Форма отправлена',
preConfirm: () =>
status !== 200 || 201 ? () => {
setStatus(null)
} : () => {
setStatus(null);
navigate(`/candidate/${urlParams.id}`)
}
});
};
useEffect(() => {
if (status) {
handleModal(status)
}
}, [status]);
const handleChange = (e) => {
const {id, value} = e.target;
setData((prev) => ({
...prev,
[id]: value
}))
};
const handleSubmit = (e) => {
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);
apiRequest('/interview-request/create-interview-request', {
method: 'POST',
params: {
profile_id: urlParams.id,
...data
}
}).then((res) => {
setStatus(res);
setIsFetching(false)
}
)
};
return (
<div className='row'>
<div className='col-sm-12'>
<form className='form' id='test'>
<label htmlFor='email'>Емейл:</label>
<input
onChange={handleChange}
id='email'
name='Email'
type='email'
placeholder='Емейл'
value={data.email}
/>
<label htmlFor='phone'>Номер телефона:</label>
<PhoneInput
id='phone'
name='Phone'
country={'ru'}
value={data.phone}
onChange={(e) =>
handleChange({target: {value: e, id: 'phone'}})
}
/>
{/* <input
onChange={handleChange}
id="phone"
type="text"
name="Phone"
placeholder="Телефон"
value={data.phone}
/> */}
<textarea
onChange={handleChange}
id='comment'
rows='5'
cols='40'
name='Comment'
placeholder='Оставьте комментарий'
value={data.comment}
></textarea>
<button onClick={handleSubmit} className='form__btn' type='submit'>
{isFetching ? <Loader/> : 'Отправить'}
</button>
</form>
</div>
</div>
)
};
export default Form

View File

@ -0,0 +1,128 @@
import React, { useEffect, useState } from "react";
import PhoneInput from "react-phone-input-2";
import "react-phone-input-2/lib/style.css";
import { useNavigate, useParams } from "react-router-dom";
import Swal from "sweetalert2";
import withReactContent from "sweetalert2-react-content";
import { apiRequest } from "@api/request";
import { Loader } from "@components/Common/Loader/Loader";
import "./form.scss";
const SweetAlert = withReactContent(Swal);
const Form = () => {
const navigate = useNavigate();
const urlParams = useParams();
const [status, setStatus] = useState(null);
const [data, setData] = useState({
email: "",
phone: "",
comment: "",
});
const [isFetching, setIsFetching] = useState(false);
const handleModal = (status) => {
SweetAlert.fire({
text:
// eslint-disable-next-line no-constant-condition
status !== 200 || 201 ? "Какие-то неполадки =(" : "Форма отправлена",
preConfirm: () =>
// eslint-disable-next-line no-constant-condition
status !== 200 || 201
? () => {
setStatus(null);
}
: () => {
setStatus(null);
navigate(`/candidate/${urlParams.id}`);
},
});
};
useEffect(() => {
if (status) {
handleModal(status);
}
}, [status]);
const handleChange = (e) => {
const { id, value } = e.target;
setData((prev) => ({
...prev,
[id]: value,
}));
};
const handleSubmit = (e) => {
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);
apiRequest("/interview-request/create-interview-request", {
method: "POST",
params: {
profile_id: urlParams.id,
...data,
},
}).then((res) => {
setStatus(res);
setIsFetching(false);
});
};
return (
<div className="row">
<div className="col-sm-12">
<form className="form" id="test">
<label htmlFor="email">Емейл:</label>
<input
onChange={handleChange}
id="email"
name="Email"
type="email"
placeholder="Емейл"
value={data.email}
/>
<label htmlFor="phone">Номер телефона:</label>
<PhoneInput
id="phone"
name="Phone"
country={"ru"}
value={data.phone}
onChange={(e) =>
handleChange({ target: { value: e, id: "phone" } })
}
/>
<textarea
onChange={handleChange}
id="comment"
rows="5"
cols="40"
name="Comment"
placeholder="Оставьте комментарий"
value={data.comment}
></textarea>
<button onClick={handleSubmit} className="form__btn" type="submit">
{isFetching ? <Loader /> : "Отправить"}
</button>
</form>
</div>
</div>
);
};
export default Form;

View File

@ -1,19 +1,20 @@
import React from "react";
import AuthHeader from "../../AuthHeader/AuthHeader";
import SideBar from "../../SideBar/SideBar";
import { Footer } from "../../Footer/Footer";
import { Link } from "react-router-dom";
import { scrollToForm } from "../../../helper";
import { ProfileBreadcrumbs } from "../../ProfileBreadcrumbs/ProfileBreadcrumbs";
import mockWorker from "../../../images/mokPerson.png";
import arrow from "../../../images/arrow_left.png";
import { scrollToForm } from "@utils/helper";
import AuthHeader from "@components/Common/AuthHeader/AuthHeader";
import BaseButton from "@components/Common/BaseButton/BaseButton";
import { Footer } from "@components/Common/Footer/Footer";
import { ProfileBreadcrumbs } from "@components/ProfileBreadcrumbs/ProfileBreadcrumbs";
import SideBar from "@components/SideBar/SideBar";
import arrow from "assets/icons/arrows/arrow_left.png";
import mockWorker from "assets/images/mock/mokPerson.png";
import "./freeDevelopers.scss";
export const FreeDevelopers = ({}) => {
export const FreeDevelopers = () => {
return (
<section className="free-dev">
<AuthHeader />
@ -42,9 +43,9 @@ export const FreeDevelopers = ({}) => {
<div></div>
</div>
</div>
<button className="button-green" onClick={scrollToForm}>
<BaseButton onClick={scrollToForm} styles={"dev-code"}>
Код разработчика
</button>
</BaseButton>
</div>
<div className="free-dev__body">
@ -84,14 +85,13 @@ export const FreeDevelopers = ({}) => {
</div>
</div>
<div className="logIn">
<div className="login">
<h3>Для просмотра полного резюме разработчика авторизуйтесь</h3>
<Link to={"/auth"} className="button-green">
Войти
</Link>
<BaseButton styles={"dev-code"}>
<Link to={"/auth"}>Войти</Link>
</BaseButton>
</div>
</div>
<Footer />
</div>
</section>

View File

@ -118,7 +118,7 @@
}
}
.logIn {
.login {
position: absolute;
bottom: 75px;
border: 3px solid #52b709;
@ -140,10 +140,8 @@
}
a {
width: 140px;
height: 50px;
color: white;
text-decoration: none;
width: 100%;
}
@media (max-width: 920px) {
@ -156,7 +154,7 @@
text-align: center;
}
a {
button {
margin-top: 15px;
height: 25px;
height: 40px;
@ -229,17 +227,9 @@
}
}
.button-green {
align-items: center;
justify-content: center;
display: flex;
background: #52b709;
border-radius: 44px;
.dev-code {
width: 202px;
height: 50px;
font-weight: 500;
font-size: 16px;
line-height: 32px;
color: #ffffff;
border: none;
}

View File

@ -1,27 +1,30 @@
import React from "react";
import "./FrequentlyAskedQuestionsItem.scss";
import { FREQUENTLY_ASKED_QUESTION_ROUTE } from "../../constants/router-path";
import { Link } from "react-router-dom";
import questionIcon from "./../../images/faq/question.svg";
export const FrequentlyAskedQuestionsItem = ({ rubric }) => {
return (
<div className="frequently-asked-questions-item">
<div className="frequently-asked-questions-item__head">
<div className="frequently-asked-questions-item__icon-question"><img src={questionIcon} alt="" /></div>
<div className="frequently-asked-questions-item__title">
{rubric?.title}
</div>
</div>
{rubric?.questions?.map((question) => (
<Link
key={question.id}
to={FREQUENTLY_ASKED_QUESTION_ROUTE + "/" + question.id}
className="frequently-asked-questions-item__body"
>
<p>{question.title}</p>
</Link>
))}
</div>
);
};
import React from "react";
import { Link } from "react-router-dom";
import questionIcon from "assets/images/faq/question.svg";
import "./FrequentlyAskedQuestionsItem.scss";
export const FrequentlyAskedQuestionsItem = ({ rubric }) => {
return (
<div className="frequently-asked-questions-item">
<div className="frequently-asked-questions-item__head">
<div className="frequently-asked-questions-item__icon-question">
<img src={questionIcon} alt="" />
</div>
<div className="frequently-asked-questions-item__title">
{rubric?.title}
</div>
</div>
{rubric?.questions?.map((question) => (
<Link
key={question.id}
to={`/frequently-asked-question/${question.id}`}
className="frequently-asked-questions-item__body"
>
<p>{question.title}</p>
</Link>
))}
</div>
);
};

View File

@ -7,23 +7,23 @@
margin: 0 0 -5px 29px;
}
&__icon-question {
}
&__title {
font-weight: 700;
@include adaptiv-value("font-size", 28, 22, 1);
line-height: 79%;
color: #1458dd;
}
&__body {
position: relative;
z-index: 2;
display: block;
&:not(:last-child) {
margin: 0 0 13px 0;
}
p {
p {
word-break: break-word;
background: #ffffff;
border-radius: 12px;
@ -40,4 +40,4 @@
align-items: center;
}
}
}
}

View File

@ -1,15 +0,0 @@
import React from "react";
import {LogoutButton} from "../LogoutButton/LogoutButton";
import './header.scss'
export const Header = () => {
return (
<div className='container header'>
<h2>
<span>Аутстаффинг</span> it-персонала
</h2>
<LogoutButton/>
</div>
)
};

View File

@ -0,0 +1,16 @@
import React from "react";
import { LogoutButton } from "@components/LogoutButton/LogoutButton";
import "./header.scss";
export const Header = () => {
return (
<div className="container header">
<h2>
<span>Аутстаффинг</span> it-персонала
</h2>
<LogoutButton />
</div>
);
};

View File

@ -9,7 +9,7 @@
flex: 1;
text-align: center;
color: #52b709;
font-family: 'GT Eesti Pro Display', sans-serif;
font-family: "GT Eesti Pro Display", sans-serif;
font-size: 5em;
font-weight: 700;
font-style: normal;
@ -23,4 +23,4 @@
line-height: normal;
}
}
}
}

View File

@ -1,12 +0,0 @@
import SVGLoader from 'react-loader-spinner'
import './loader.scss'
import React from "react";
export const Loader = ({width = 50, height = 50, style}) => {
return (
<div className='loader'>
<SVGLoader type='Circles' color={style ? style : `#fff`} height={height} width={width}/>
</div>
)
};

View File

@ -1,33 +0,0 @@
import React, {useState} from 'react'
import {useNavigate} from 'react-router-dom'
import {useSelector} from 'react-redux'
import {useLogout} from "../../hooks/useLogout";
import {Loader} from '../Loader/Loader'
import {getRole} from '../../redux/roleSlice'
import './logoutButton.scss'
export const LogoutButton = () => {
const [isLoggingOut, setIsLoggingOut] = useState(false);
const userRole = useSelector(getRole);
const navigate = useNavigate();
const {logout} = useLogout();
return (
<button
className='logout-button'
onClick={() => {
setIsLoggingOut(true);
logout();
setIsLoggingOut(false);
navigate(userRole === 'ROLE_DEV' ? '/authdev' : '/auth')
}}
>
{isLoggingOut ? <Loader/> : 'Выйти'}
</button>
)
};

View File

@ -0,0 +1,33 @@
import React, { useState } from "react";
import { useSelector } from "react-redux";
import { useNavigate } from "react-router-dom";
import { getRole } from "@redux/roleSlice";
import { useLogout } from "@hooks/useLogout";
import { Loader } from "@components/Common/Loader/Loader";
import "./logoutButton.scss";
export const LogoutButton = () => {
const [isLoggingOut, setIsLoggingOut] = useState(false);
const userRole = useSelector(getRole);
const navigate = useNavigate();
const { logout } = useLogout();
return (
<button
className="logout-button"
onClick={() => {
setIsLoggingOut(true);
logout();
setIsLoggingOut(false);
navigate(userRole === "ROLE_DEV" ? "/authdev" : "/auth");
}}
>
{isLoggingOut ? <Loader /> : "Выйти"}
</button>
);
};

View File

@ -1,5 +1,4 @@
.logout-button {
position: relative;
z-index: 100;
display: flex;
@ -14,7 +13,7 @@
background-color: #6aaf5c;
color: #ffffff;
border: 3px solid #6aaf5c;
font-family: 'Muller', sans-serif;
font-family: "Muller", sans-serif;
text-align: center;
&:hover {

View File

@ -0,0 +1,98 @@
import React, { useState } from "react";
import { Link } from "react-router-dom";
import BaseButton from "@components/Common/BaseButton/BaseButton";
import ModalLayout from "@components/Common/ModalLayout/ModalLayout";
import arrow from "assets/icons/arrows/left-arrow.png";
import logoTg from "assets/icons/tgLogo.svg";
import interview from "assets/images/logo/interviewLogo.svg";
import avatar from "assets/images/mock/mokPerson.png";
import "./modalAspirant.scss";
export const ModalAspirant = ({ active, setActive, level }) => {
const [date, setDate] = useState("");
const [time, setTime] = useState("");
const [modalSend, setModalSend] = useState(false);
const send = () => {
if (date != "" && time != "") {
setModalSend(true);
setTimeout(() => {
setModalSend(false);
setActive(false);
}, 3200);
}
};
return (
<ModalLayout active={active} setActive={setActive} styles={"aspirant"}>
<div className="aspirant-decs">
<h1>Выбранный кандидат</h1>
<div className="aspirant-decs__avatar">
<div className="aspirant-decs__avatar_title">
<img src={avatar}></img>
<p>
{level.spec} {level.skils}, {level.level}{" "}
</p>
</div>
<div className="aspirant-decs__avatar_back">
<Link to={"/profile/catalog"}>
<div>
<img src={arrow}></img>
</div>
<p>Вернуться к списку</p>
</Link>
</div>
</div>
<div className="aspirant-decs__telega">
<h4>Есть вопросы?</h4>
<div className="aspirant-decs__telega-logo">
<img src={logoTg}></img>
<p>Напишите нам в Телеграм. Мы с удовольствием ответим!</p>
</div>
</div>
</div>
<div className="form-interview">
<p>Дата собеседования</p>
<div className="input">
<input
type="date"
value={date}
onChange={(e) => setDate(e.target.value)}
></input>
</div>
<p>Время собеседования</p>
<div className="input">
<input
type="time"
value={time}
onChange={(e) => setTime(e.target.value)}
></input>
</div>
<BaseButton onClick={send} styles="form-interview__submit">
Отправить
</BaseButton>
</div>
<span className="exit" onClick={() => setActive(false)}></span>
<ModalLayout active={modalSend} setActive={setModalSend} styles={"send"}>
<div className="send">
<img src={interview}></img>
<h2>Спасибо, собеседование назначено</h2>
<p>
Дата: <span>{date}</span>
</p>
<p>
Время собеседования: <span>{time}</span>
</p>
</div>
</ModalLayout>
</ModalLayout>
);
};
export default ModalAspirant;

View File

@ -0,0 +1,170 @@
.aspirant {
display: flex;
flex-direction: row;
background: #ffffff;
border: 1px solid #dde2e4;
border-radius: 8px;
padding: 0;
&-decs {
padding: 54px 25px 51px 61px;
border-right: 1px solid #f1f1f1;
h1 {
display: block;
font-weight: 500;
font-size: 30px;
line-height: 32px;
color: #000000;
text-align: left;
}
&__avatar {
margin-top: 76px;
&_title {
display: flex;
flex-direction: row;
align-items: center;
img {
width: 48px;
height: 48px;
margin: 0 22px 0 0;
}
p {
font-weight: 500;
font-size: 16px;
line-height: 24px;
}
}
&_back {
margin: 40px 0 0 0;
a {
display: flex;
align-items: center;
font-size: 12px;
line-height: 16px;
color: #5b6871;
text-decoration: none;
}
div {
background: #8dc63f;
opacity: 0.3;
width: 48px;
height: 48px;
border-radius: 44px;
display: flex;
align-items: center;
justify-content: center;
margin: 0 15px 0 0;
img {
margin: 0;
width: 50%;
}
}
p {
font-size: 12px;
line-height: 16px;
color: #5b6871;
}
}
}
&__telega {
text-align: left;
h4 {
color: #52b709;
font-size: 14px;
margin: 55px 0 34px 0;
}
p,
h4 {
font-weight: 900;
line-height: 24px;
}
p {
width: 50%;
font-size: 12px;
}
&-logo {
display: flex;
flex-direction: row;
img {
margin: 0 19px 0 0;
}
}
}
}
.form-interview {
text-align: left;
padding: 54px 61px 51px 72px;
p {
font-weight: 400;
font-size: 15px;
line-height: 18px;
margin-bottom: 10px;
}
&__submit {
width: 174px;
height: 46px;
font-size: 18px;
}
.input {
background: #eff2f7;
border-radius: 8px;
display: flex;
justify-content: center;
width: 294px;
height: 35px;
margin: 0 0 36px 0;
input {
background: #eff2f7;
width: 90%;
border: none;
outline: none;
font-size: 15px;
}
}
}
}
.send {
display: flex;
flex-direction: column;
align-items: center;
h2 {
text-align: center;
margin: 25px 0 31px 0;
}
p {
font-size: 14px;
line-height: 17px;
font-weight: 700;
text-align: center;
color: #000000;
margin-bottom: 10px;
span {
color: #406128;
}
}
}

View File

@ -0,0 +1,27 @@
import React from "react";
import BaseButton from "@components/Common/BaseButton/BaseButton";
import ModalLayout from "@components/Common/ModalLayout/ModalLayout";
import "./modalErrorLogin.scss";
export const ModalErrorLogin = ({ active, setActive, title }) => {
return (
<ModalLayout active={active} setActive={setActive} styles={"error-login"}>
<h2>Ошибка входа</h2>
<p>{title}</p>
<BaseButton
styles={"error-login__button"}
onClick={(e) => {
e.preventDefault();
setActive(false);
}}
>
Попробовать еще раз
</BaseButton>
<span onClick={() => setActive(false)} className="exit"></span>
</ModalLayout>
);
};
export default ModalErrorLogin;

View File

@ -0,0 +1,31 @@
.error-login {
position: relative;
padding: 54px 76px;
background: linear-gradient(180deg, #ffffff 0%, #ebebeb 100%);
border-radius: 40px;
display: flex;
flex-direction: column;
align-items: center;
h2 {
font-size: 24px;
line-height: 29px;
color: #263238;
margin-bottom: 16px;
}
p {
font-size: 12px;
line-height: 14px;
width: 176px;
text-align: center;
font-weight: 300;
margin-bottom: 30px;
}
&__button {
font-size: 14px;
width: 198px;
height: 50px;
}
}

View File

@ -0,0 +1,79 @@
import React from "react";
import BaseButton from "@components/Common/BaseButton/BaseButton";
import ModalLayout from "@components/Common/ModalLayout/ModalLayout";
import anyMoment from "assets/icons/anyMoment.svg";
import doc from "assets/icons/doc.svg";
import telegramLogo from "assets/icons/tgLogo.svg";
import "./modalRegistration.scss";
export const ModalRegistration = ({ active, setActive }) => {
return (
<ModalLayout active={active} setActive={setActive} styles={"registration"}>
<div className="registration-body__left">
<h2>
Подключайтесь к <p>itguild.</p>
</h2>
<p className="registration-body__left-desc">
Зарегистрируйтесь и назначайте собеседования любым специалистам без
задержек
</p>
<div className="input-body">
<div className="input-body__box">
<h5>Ваше имя</h5>
<input></input>
<h5>E-mail</h5>
<input></input>
</div>
<div className="input-body__box">
<h5>Название компании</h5>
<input></input>
<h5>Пароль</h5>
<input></input>
</div>
</div>
<div className="button-box">
<BaseButton
onClick={(e) => e.preventDefault()}
styles={"button-box__submit"}
>
Отправить
</BaseButton>
<h5>
У вас уже есть аккаунт? <p>Войти</p>
</h5>
</div>
</div>
<div className="registration-body__right">
<h4>Отказ от специалиста в любой момент</h4>
<div className="registration-body__right-text">
<img src={anyMoment}></img>
<p>
Поменяйте, откажитесь или возьмите еще специалиста в любой момент
работы.
</p>
</div>
<h4>100% постоплата</h4>
<div className="registration-body__right-text">
<img src={doc}></img>
<p>
Договор не подразумевает какуюлибо оплату до того, как вы
арендовали специалиста
</p>
</div>
<h4>Есть вопросы?</h4>
<div className="registration-body__right-text">
<img src={telegramLogo}></img>
<p>Напишите нам в Телеграм. Мы с удовольствием ответим!</p>
</div>
</div>
<span onClick={() => setActive(false)} className="exit"></span>
</ModalLayout>
);
};
export default ModalRegistration;

View File

@ -0,0 +1,128 @@
.registration {
background: white;
display: flex;
flex-direction: row;
padding: 0;
justify-content: space-between;
border: 1px solid #dde2e4;
border-radius: 8px;
width: 1088px;
height: 529px;
&-body {
&__left {
padding: 60px 0 30px 77px;
h2 {
font-weight: 500;
font-size: 35px;
line-height: 32px;
display: flex;
justify-content: space-between;
width: 405px;
margin: 0 auto;
}
h2 > p {
font-size: 35px;
color: #52b709;
}
&-desc {
text-align: center;
width: 500px;
font-weight: 500;
font-size: 16px;
line-height: 28px;
margin: 20px auto 0 auto;
}
.input-body {
margin-top: 44px;
display: flex;
flex-direction: row;
&__box {
margin-right: 25px;
display: flex;
flex-direction: column;
h5 {
font-weight: 400;
font-size: 15px;
line-height: 18px;
}
input {
width: 294px;
height: 35px;
background: #eff2f7;
border-radius: 8px;
border: none;
margin-bottom: 35px;
padding-left: 20px;
}
}
}
.button-box {
display: flex;
flex-direction: row;
margin-top: 10px;
&__submit {
width: 174px;
height: 46px;
font-size: 18px;
margin-right: 55px;
}
h5 {
display: flex;
align-items: flex-end;
font-size: 16px;
line-height: 28px;
p {
color: #406128;
text-decoration: underline;
margin: 0 0 0 5px;
}
}
}
}
&__right {
border-left: 1px solid #f1f1f1;
padding: 80px 32px 46px 25px;
display: flex;
flex-direction: column;
justify-content: space-between;
h4 {
font-weight: 900;
font-size: 14px;
line-height: 24px;
color: #52b709;
margin-right: 100px;
width: 180px;
}
&-text {
display: flex;
flex-direction: row;
p {
font-weight: 400;
font-size: 12px;
line-height: 22px;
width: 205px;
}
img {
margin: 0 18px 20px 0;
}
}
}
}
}

View File

@ -0,0 +1,489 @@
import React, { useEffect, useState } from "react";
import { useDispatch } from "react-redux";
import { Link } from "react-router-dom";
import { modalToggle, setProjectBoardFetch } from "@redux/projectsTrackerSlice";
import { urlForLocal } from "@utils/helper";
import { apiRequest } from "@api/request";
import { getCorrectDate } from "@components/Calendar/calendarHelper";
import BaseButton from "@components/Common/BaseButton/BaseButton";
import ModalLayout from "@components/Common/ModalLayout/ModalLayout";
import TrackerModal from "@components/Modal/TrackerModal/TrackerModal";
import archive from "assets/icons/archive.svg";
import arrow from "assets/icons/arrows/arrowStart.png";
import fullScreen from "assets/icons/arrows/inFullScreen.svg";
import category from "assets/icons/category.svg";
import close from "assets/icons/closeProjectPersons.svg";
import del from "assets/icons/delete.svg";
import edit from "assets/icons/edit.svg";
import file from "assets/icons/fileModal.svg";
import link from "assets/icons/link.svg";
import plus from "assets/icons/plus.svg";
import send from "assets/icons/send.svg";
import watch from "assets/icons/watch.svg";
import "./ModalTicket.scss";
export const ModalTiсket = ({
active,
setActive,
task,
projectId,
projectName,
projectUsers,
}) => {
const dispatch = useDispatch();
const [addSubtask, setAddSubtask] = useState(false);
const [editOpen, setEditOpen] = useState(false);
const [inputsValue, setInputsValue] = useState({
title: task.title,
description: task.description,
comment: "",
});
const [comments, setComments] = useState([]);
const [commentsEditOpen, setCommentsEditOpen] = useState({});
const [commentsEditText, setCommentsEditText] = useState({});
const [dropListOpen, setDropListOpen] = useState(false);
const [dropListMembersOpen, setDropListMembersOpen] = useState(false);
const [executor, setExecutor] = useState(task.executor);
const [members, setMembers] = useState(task.taskUsers);
const [users, setUsers] = useState([]);
function deleteTask() {
apiRequest("/task/update-task", {
method: "PUT",
data: {
task_id: task.id,
status: 0,
},
}).then(() => {
setActive(false);
dispatch(setProjectBoardFetch(projectId));
});
}
function editTask() {
apiRequest("/task/update-task", {
method: "PUT",
data: {
task_id: task.id,
title: inputsValue.title,
description: inputsValue.description,
},
}).then(() => {
dispatch(setProjectBoardFetch(projectId));
});
}
function createComment() {
apiRequest("/comment/create", {
method: "POST",
data: {
text: inputsValue.comment,
entity_type: 2,
entity_id: task.id,
},
}).then((res) => {
let newComment = res;
newComment.created_at = new Date();
setInputsValue((prevValue) => ({ ...prevValue, comment: "" }));
setComments((prevValue) => [...prevValue, newComment]);
setCommentsEditOpen((prevValue) => ({ ...prevValue, [res.id]: false }));
setCommentsEditText((prevValue) => ({
...prevValue,
[res.id]: res.text,
}));
});
}
function deleteComment(commentId) {
apiRequest("/comment/update", {
method: "PUT",
data: {
comment_id: commentId,
status: 0,
},
}).then(() => {
setComments((prevValue) =>
prevValue.filter((item) => item.id !== commentId)
);
});
}
function editComment(commentId) {
apiRequest("/comment/update", {
method: "PUT",
data: {
comment_id: commentId,
text: commentsEditText[commentId],
},
}).then(() => {});
}
function taskExecutor(person) {
apiRequest("/task/update-task", {
method: "PUT",
data: {
task_id: task.id,
executor_id: person.user_id,
},
}).then((res) => {
setDropListOpen(false);
setExecutor(res.executor);
});
}
function deleteTaskExecutor() {
apiRequest("/task/update-task", {
method: "PUT",
data: {
task_id: task.id,
executor_id: 0,
},
}).then(() => {
setExecutor(null);
});
}
function addMember(person) {
apiRequest("/task/add-user-to-task", {
method: "POST",
data: {
task_id: task.id,
user_id: person.user_id,
},
}).then((res) => {
setDropListMembersOpen(false);
setMembers((prevValue) => [...prevValue, res]);
});
}
function deleteMember(person) {
apiRequest("/task/del-user", {
method: "DELETE",
data: {
task_id: task.id,
user_id: person.user_id,
},
}).then(() => {
setMembers(members.filter((item) => item.user_id !== person.user_id));
});
}
useEffect(() => {
apiRequest(
`/comment/get-by-entity?entity_type=2&entity_id=${task.id}`
).then((res) => {
setComments(res);
res.forEach((item) => {
setCommentsEditOpen((prevValue) => ({
...prevValue,
[item.id]: false,
}));
setCommentsEditText((prevValue) => ({
...prevValue,
[item.id]: item.text,
}));
});
});
}, []);
useEffect(() => {
let ids = members.map((user) => user.user_id);
setUsers(
projectUsers.reduce((acc, cur) => {
if (!ids.includes(cur.user_id)) acc.push(cur);
return acc;
}, [])
);
}, [members]);
return (
<>
<ModalLayout
active={active}
setActive={setActive}
styles={"tracker-ticket"}
>
<div className="content">
<h3 className="title-project">
<img src={category} className="title-project__category"></img>
Проект: {projectName}
<Link
to={`/tracker/task/${task.id}`}
className="title-project__full"
>
<img src={fullScreen}></img>
</Link>
</h3>
<div className="content__task">
<span>Задача</span>
{editOpen ? (
<input
value={inputsValue.title}
onChange={(e) => {
setInputsValue((prevValue) => ({
...prevValue,
title: e.target.value,
}));
}}
/>
) : (
<h5>{inputsValue.title}</h5>
)}
<div className="content__description">
{editOpen ? (
<input
value={inputsValue.description}
onChange={(e) => {
setInputsValue((prevValue) => ({
...prevValue,
description: e.target.value,
}));
}}
/>
) : (
<p>{inputsValue.description}</p>
)}
{/*<img src={taskImg} className="image-task"></img>*/}
</div>
<div className="content__communication">
<p className="tasks">
<BaseButton
onClick={() => {
dispatch(modalToggle("addSubtask"));
setAddSubtask(true);
}}
styles={"tasks__button"}
>
<img src={plus}></img>
Добавить под задачу
</BaseButton>
</p>
<p className="file">
<BaseButton styles={"file__button"}>
<img src={file}></img>
Загрузить файл
</BaseButton>
<span>{0}</span>
Файлов
</p>
</div>
<div className="content__input">
<input
placeholder="Оставить комментарий"
value={inputsValue.comment}
onChange={(e) => {
setInputsValue((prevValue) => ({
...prevValue,
comment: e.target.value,
}));
}}
/>
<img src={send} onClick={createComment}></img>
</div>
<div className="comments__list">
{comments.map((comment) => {
return (
<div className="comments__list__item" key={comment.id}>
<div className="comments__list__item__info">
<span>{getCorrectDate(comment.created_at)}</span>
<div
className={
commentsEditOpen[comment.id]
? "edit edit__open"
: "edit"
}
>
<img
src={edit}
alt="edit"
onClick={() => {
if (commentsEditOpen[comment.id]) {
editComment(comment.id);
}
setCommentsEditOpen((prevValue) => ({
...prevValue,
[comment.id]: !prevValue[comment.id],
}));
}}
/>
</div>
<img
src={del}
alt="delete"
onClick={() => deleteComment(comment.id)}
/>
</div>
{commentsEditOpen[comment.id] ? (
<input
value={commentsEditText[comment.id]}
onChange={(e) => {
setCommentsEditText((prevValue) => ({
...prevValue,
[comment.id]: e.target.value,
}));
}}
/>
) : (
<p>{commentsEditText[comment.id]}</p>
)}
</div>
);
})}
</div>
</div>
</div>
<div className="workers">
<div className="workers_box task__info">
<span className="exit" onClick={() => setActive(false)}></span>
<span className="nameProject">{task.title}</span>
<p className="workers__creator">Создатель : {task.user?.fio}</p>
{executor ? (
<div className="executor">
<p>Исполнитель: {executor.fio}</p>
<img src={urlForLocal(executor.avatar)} alt="avatar" />
<img
src={close}
className="delete"
onClick={() => deleteTaskExecutor()}
/>
</div>
) : (
<div className="add-worker moreItems ">
<button onClick={() => setDropListOpen(true)}>+</button>
<span>Добавить исполнителя</span>
{dropListOpen && (
<div className="dropdownList">
<img
src={close}
className="dropdownList__close"
onClick={() => setDropListOpen(false)}
/>
{projectUsers.map((person) => {
return (
<div
className="dropdownList__person"
key={person.user_id}
onClick={() => taskExecutor(person)}
>
<span>{person.user.fio}</span>
<img src={urlForLocal(person.user.avatar)} />
</div>
);
})}
</div>
)}
</div>
)}
{Boolean(members.length) && (
<div className="members">
<p>Участники:</p>
<div className="members__list">
{members.map((member) => {
return (
<div className="worker" key={member.user_id}>
<p>{member.fio}</p>
<img src={urlForLocal(member.avatar)} />
<img
src={close}
className="delete"
onClick={() => deleteMember(member)}
/>
</div>
);
})}
</div>
</div>
)}
<div className="add-worker moreItems">
<button onClick={() => setDropListMembersOpen(true)}>+</button>
<span>Добавить участников</span>
{dropListMembersOpen && (
<div className="dropdownList">
<img
src={close}
className="dropdownList__close"
onClick={() => setDropListMembersOpen(false)}
/>
{users.length ? (
users.map((person) => {
return (
<div
className="dropdownList__person"
key={person.user_id}
onClick={() => addMember(person)}
>
<span>{person.user.fio}</span>
<img src={urlForLocal(person.user.avatar)} />
</div>
);
})
) : (
<p className="noUsers">Нет пользователей</p>
)}
</div>
)}
</div>
</div>
<div className="workers_box-middle">
<div className="time">
<img src={watch}></img>
<span>Длительность : </span>
<p>{"0:00:00"}</p>
</div>
<button className="start">
Начать делать <img src={arrow}></img>
</button>
</div>
<div className="workers_box-bottom">
<div
className={editOpen ? "edit" : ""}
onClick={() => {
if (editOpen) {
setEditOpen(!editOpen);
editTask();
} else {
setEditOpen(!editOpen);
}
}}
>
<img src={edit}></img>
<p>{editOpen ? "сохранить" : "редактировать"}</p>
</div>
<div>
<img src={link}></img>
<p>ссылка на проект</p>
</div>
<div onClick={deleteTask}>
<img src={archive}></img>
<p>в архив</p>
</div>
<div onClick={deleteTask}>
<img src={del}></img>
<p>удалить</p>
</div>
</div>
</div>
</ModalLayout>
<TrackerModal
active={addSubtask}
setActive={setAddSubtask}
defautlInput={task.column_id}
></TrackerModal>
</>
);
};
export default ModalTiсket;

View File

@ -1,24 +1,8 @@
.modal-tiket {
z-index: 9;
height: 100%;
width: 100%;
background-color: rgba(0, 0, 0, 0.11);
position: fixed;
top: 0;
left: 0;
display: none;
align-items: center;
justify-content: center;
}
.modal-tiket.active {
display: flex;
}
.modal-tiket__content {
.tracker-ticket {
background: #ffffff;
//border: 1px solid #dde2e4;
padding: 0;
border-radius: 8px;
align-items: initial;
display: flex;
flex-direction: row;
@ -236,6 +220,7 @@
display: flex;
flex-direction: column;
margin-top: 10px;
p {
font-weight: 400;
font-size: 14px;
@ -258,16 +243,10 @@
.tasks {
justify-content: space-evenly;
button {
&__button {
width: 180px;
height: 40px;
background: #52b709;
border-radius: 44px;
font-weight: 400;
font-size: 12px;
line-height: 32px;
border: none;
color: #ffffff;
}
}
@ -281,18 +260,13 @@
justify-content: space-between;
margin-left: 20px;
button {
display: flex;
align-items: center;
justify-content: center;
&__button {
background: white;
width: 166px;
height: 40px;
border: 0.5px solid #1458dd;
border-radius: 44px;
font-weight: 400;
font-size: 12px;
line-height: 32px;
color: #1458dd;
img {

View File

@ -0,0 +1,523 @@
import React, { useEffect, useState } from "react";
import { useDispatch, useSelector } from "react-redux";
import { Link, useNavigate, useParams } from "react-router-dom";
import {
deletePersonOnProject,
getBoarderLoader,
getProjectBoard,
modalToggle,
setProjectBoardFetch,
setToggleTab,
} from "@redux/projectsTrackerSlice";
import { urlForLocal } from "@utils/helper";
import { apiRequest } from "@api/request";
import { getCorrectDate } from "@components/Calendar/calendarHelper";
import BaseButton from "@components/Common/BaseButton/BaseButton";
import { Footer } from "@components/Common/Footer/Footer";
import { Loader } from "@components/Common/Loader/Loader";
import TrackerModal from "@components/Modal/TrackerModal/TrackerModal";
import { Navigation } from "@components/Navigation/Navigation";
import { ProfileBreadcrumbs } from "@components/ProfileBreadcrumbs/ProfileBreadcrumbs";
import { ProfileHeader } from "@components/ProfileHeader/ProfileHeader";
import archive2 from "assets/icons/archive.svg";
import archive from "assets/icons/archiveTracker.svg";
import arrow from "assets/icons/arrows/arrowCalendar.png";
import arrow2 from "assets/icons/arrows/arrowStart.png";
import selectArrow from "assets/icons/arrows/select.svg";
import close from "assets/icons/closeProjectPersons.svg";
import del from "assets/icons/delete.svg";
import edit from "assets/icons/edit.svg";
import file from "assets/icons/fileModal.svg";
import link from "assets/icons/link.svg";
import plus from "assets/icons/plus.svg";
import send from "assets/icons/send.svg";
import project from "assets/icons/trackerProject.svg";
import tasks from "assets/icons/trackerTasks.svg";
import watch from "assets/icons/watch.svg";
import "./ticketFullScreen.scss";
export const TicketFullScreen = () => {
const [modalAddWorker, setModalAddWorker] = useState(false);
const ticketId = useParams();
const dispatch = useDispatch();
const navigate = useNavigate();
const projectBoard = useSelector(getProjectBoard);
const boardLoader = useSelector(getBoarderLoader);
const [taskInfo, setTaskInfo] = useState({});
const [editOpen, setEditOpen] = useState(false);
const [inputsValue, setInputsValue] = useState({});
const [loader, setLoader] = useState(true);
const [comments, setComments] = useState([]);
const [commentsEditOpen, setCommentsEditOpen] = useState({});
const [commentsEditText, setCommentsEditText] = useState({});
const [personListOpen, setPersonListOpen] = useState(false);
useEffect(() => {
apiRequest(`/task/get-task?task_id=${ticketId.id}`).then((taskInfo) => {
setTaskInfo(taskInfo);
setInputsValue({
title: taskInfo.title,
description: taskInfo.description,
comment: "",
});
apiRequest(
`/comment/get-by-entity?entity_type=2&entity_id=${taskInfo.id}`
).then((res) => {
setComments(res);
res.forEach((item) => {
setCommentsEditOpen((prevValue) => ({
...prevValue,
[item.id]: false,
}));
setCommentsEditText((prevValue) => ({
...prevValue,
[item.id]: item.text,
}));
});
});
dispatch(setProjectBoardFetch(taskInfo.project_id));
setLoader(boardLoader);
});
}, []);
function deleteTask() {
apiRequest("/task/update-task", {
method: "PUT",
data: {
task_id: ticketId.id,
status: 0,
},
}).then(() => {
navigate(`/tracker/project/${taskInfo.project_id}`);
});
}
function editTask() {
apiRequest("/task/update-task", {
method: "PUT",
data: {
task_id: taskInfo.id,
title: inputsValue.title,
description: inputsValue.description,
},
}).then(() => {});
}
function createComment() {
apiRequest("/comment/create", {
method: "POST",
data: {
text: inputsValue.comment,
entity_type: 2,
entity_id: taskInfo.id,
},
}).then((res) => {
let newComment = res;
newComment.created_at = new Date();
setInputsValue((prevValue) => ({ ...prevValue, comment: "" }));
setComments((prevValue) => [...prevValue, newComment]);
setCommentsEditOpen((prevValue) => ({ ...prevValue, [res.id]: false }));
setCommentsEditText((prevValue) => ({
...prevValue,
[res.id]: res.text,
}));
});
}
function deleteComment(commentId) {
apiRequest("/comment/update", {
method: "PUT",
data: {
comment_id: commentId,
status: 0,
},
}).then(() => {
setComments((prevValue) =>
prevValue.filter((item) => item.id !== commentId)
);
});
}
function editComment(commentId) {
apiRequest("/comment/update", {
method: "PUT",
data: {
comment_id: commentId,
text: commentsEditText[commentId],
},
}).then(() => {});
}
function deletePerson(userId) {
apiRequest("/project/del-user", {
method: "DELETE",
data: {
project_id: projectBoard.id,
user_id: userId,
},
}).then(() => {
dispatch(deletePersonOnProject(userId));
});
}
const toggleTabs = (index) => {
dispatch(setToggleTab(index));
};
return (
<section className="ticket-full-screen">
<ProfileHeader />
<Navigation />
<div className="container">
<div className="tracker__content">
<ProfileBreadcrumbs
links={[
{ name: "Главная", link: "/profile" },
{ name: "Трекер", link: "/profile/tracker" },
]}
/>
<h2 className="tracker__title">Управление проектами с трекером</h2>
</div>
</div>
<div className="tracker__tabs">
<div className="tracker__tabs__head">
<Link
to="/profile/tracker"
className="tab active-tab"
onClick={() => toggleTabs(1)}
>
<img src={project} alt="img" />
<p>Проекты </p>
</Link>
<Link
to="/profile/tracker"
className="tab"
onClick={() => toggleTabs(2)}
>
<img src={tasks} alt="img" />
<p>Все мои задачи</p>
</Link>
<Link
to="/profile/tracker"
className="tab"
onClick={() => toggleTabs(3)}
>
<img src={archive} alt="img" />
<p>Архив</p>
</Link>
</div>
{loader ? (
<Loader />
) : (
<>
<div className="tracker__tabs__content content-tabs">
<div className="tasks__head">
<div className="tasks__head__wrapper">
<h4>Проект : {projectBoard.name}</h4>
<TrackerModal
active={modalAddWorker}
setActive={setModalAddWorker}
></TrackerModal>
<div className="tasks__head__persons">
{/*<img src={avatarTest} alt="avatar" />*/}
{/*<img src={avatarTest} alt="avatar" />*/}
<span className="countPersons">
{projectBoard.projectUsers?.length}
</span>
<span
className="addPerson"
onClick={() => {
setPersonListOpen(true);
}}
>
+
</span>
<p>добавить участника</p>
{personListOpen && (
<div className="persons__list">
<img
className="persons__list__close"
src={close}
alt="close"
onClick={() => setPersonListOpen(false)}
/>
<div className="persons__list__count">
<span>{projectBoard.projectUsers?.length}</span>
участник
</div>
<div className="persons__list__info">
В проекте - <span>{projectBoard.name}</span>
</div>
<div className="persons__list__items">
{projectBoard.projectUsers?.map((person) => {
return (
<div
className="persons__list__item"
key={person.user_id}
>
<img
className="avatar"
src={urlForLocal(person.user.avatar)}
alt="avatar"
/>
<span>{person.user.fio}</span>
<img
className="delete"
src={close}
alt="delete"
onClick={() => deletePerson(person.user_id)}
/>
</div>
);
})}
</div>
<div
className="persons__list__add"
onClick={() => {
dispatch(modalToggle("addWorker"));
setModalAddWorker(true);
setPersonListOpen(false);
}}
>
<span className="addPerson">+</span>
<p>Добавить участников</p>
</div>
</div>
)}
</div>
<div className="tasks__head__select">
<span>Учавствую</span>
<img src={selectArrow} alt="arrow" />
</div>
<div className="tasks__head__select">
<span>Мои</span>
<img src={selectArrow} alt="arrow" />
</div>
<Link to={`/profile/tracker`} className="link">
<div className="tasks__head__back">
<p>Вернуться на проекты</p>
<img src={arrow} alt="arrow" />
</div>
</Link>
</div>
</div>
</div>
<div className="tracker-ticket ticket">
<div className="content ticket-whith">
<div className="content__task">
<span>Задача</span>
{editOpen ? (
<input
value={inputsValue.title}
onChange={(e) => {
setInputsValue((prevValue) => ({
...prevValue,
title: e.target.value,
}));
}}
/>
) : (
<h5>{inputsValue.title}</h5>
)}
<div className="content__description">
{editOpen ? (
<input
value={inputsValue.description}
onChange={(e) => {
setInputsValue((prevValue) => ({
...prevValue,
description: e.target.value,
}));
}}
/>
) : (
<p>{inputsValue.description}</p>
)}
</div>
<div className="content__communication">
<p className="tasks">
<BaseButton
onClick={() => {
dispatch(modalToggle("addSubtask"));
setAddSubtask(true);
}}
styles={"tasks__button"}
>
<img src={plus}></img>
Добавить под задачу
</BaseButton>
</p>
<p className="file">
<BaseButton styles={"file__button"}>
<img src={file}></img>
Загрузить файл
</BaseButton>
<span>{0}</span>
Файлов
</p>
</div>
<div className="content__input">
<input
placeholder="Оставить комментарий"
value={inputsValue.comment}
onChange={(e) => {
setInputsValue((prevValue) => ({
...prevValue,
comment: e.target.value,
}));
}}
/>
<img src={send} onClick={createComment}></img>
</div>
<div className="comments__list">
{comments.map((comment) => {
return (
<div className="comments__list__item" key={comment.id}>
<div className="comments__list__item__info">
<span>{getCorrectDate(comment.created_at)}</span>
<div
className={
commentsEditOpen[comment.id]
? "edit edit__open"
: "edit"
}
>
<img
src={edit}
alt="edit"
onClick={() => {
if (commentsEditOpen[comment.id]) {
editComment(comment.id);
}
setCommentsEditOpen((prevValue) => ({
...prevValue,
[comment.id]: !prevValue[comment.id],
}));
}}
/>
</div>
<img
src={del}
alt="delete"
onClick={() => deleteComment(comment.id)}
/>
</div>
{commentsEditOpen[comment.id] ? (
<input
value={commentsEditText[comment.id]}
onChange={(e) => {
setCommentsEditText((prevValue) => ({
...prevValue,
[comment.id]: e.target.value,
}));
}}
/>
) : (
<p>{commentsEditText[comment.id]}</p>
)}
</div>
);
})}
</div>
</div>
</div>
<div className="workers">
<div className="workers_box">
<p className="workers__creator">
Создатель : <span>{taskInfo.user?.fio}</span>
</p>
<div>
{Boolean(taskInfo.taskUsers?.length) &&
taskInfo.taskUsers.map((worker, index) => {
return (
<div className="worker" key={index}>
<img src={worker.avatar}></img>
<p>{worker.name}</p>
</div>
);
})}
</div>
<div className="add-worker moreItems">
<button
onClick={() => {
dispatch(modalToggle("addWorker"));
setModalAddWorker(true);
}}
>
+
</button>
<span>Добавить исполнителя</span>
</div>
<div className="add-worker moreItems">
<button
onClick={() => {
dispatch(modalToggle("addWorker"));
setModalAddWorker(true);
}}
>
+
</button>
<span>Добавить участников</span>
</div>
</div>
<div className="workers_box-middle">
<div className="time">
<img src={watch}></img>
<span>Длительность : </span>
<p>{"0:00:00"}</p>
</div>
<button className="start">
Начать делать <img src={arrow2}></img>
</button>
</div>
<div className="workers_box-bottom">
<div
className={editOpen ? "edit" : ""}
onClick={() => {
if (editOpen) {
setEditOpen(!editOpen);
editTask();
} else {
setEditOpen(!editOpen);
}
}}
>
<img src={edit}></img>
<p>{editOpen ? "сохранить" : "редактировать"}</p>
</div>
<div>
<img src={link}></img>
<p>ссылка на проект</p>
</div>
<div>
<img src={archive2}></img>
<p>в архив</p>
</div>
<div onClick={deleteTask}>
<img src={del}></img>
<p>удалить</p>
</div>
</div>
</div>
</div>
</>
)}
</div>
<Footer />
</section>
);
};
export default TicketFullScreen;

View File

@ -11,10 +11,6 @@
}
}
// .modal-tiket__content .content {
// width: 70%;
// }
.content-tabs {
padding-bottom: 0;
}

View File

@ -28,7 +28,7 @@ export const TrackerModal = ({
defautlInput,
titleProject,
projectId,
priorityTask
priorityTask,
}) => {
const dispatch = useDispatch();
const projectBoard = useSelector(getProjectBoard);
@ -58,7 +58,7 @@ export const TrackerModal = ({
priority: projectBoard.columns.length ? projectBoard.columns.at(-1).priority + 1 : 1,
title: valueColumn,
},
}).then((res) => {
}).then(() => {
dispatch(setProjectBoardFetch(projectBoard.id));
});
setValueColumn("");
@ -79,9 +79,9 @@ export const TrackerModal = ({
status: 1,
user_id: localStorage.getItem("id"),
column_id: selectedTab,
priority: priorityTask
priority: priorityTask,
},
}).then((res) => {
}).then(() => {
dispatch(setProjectBoardFetch(projectBoard.id));
});
@ -97,7 +97,7 @@ export const TrackerModal = ({
project_id: projectId,
name: projectName,
},
}).then((res) => {
}).then(() => {
setActive(false);
dispatch(editProjectName({ id: projectId, name: projectName }));
});
@ -143,12 +143,12 @@ export const TrackerModal = ({
method: "PUT",
data: {
column_id: columnId,
title: columnName
}
}).then((res) => {
title: columnName,
},
}).then(() => {
setActive(false);
dispatch(editColumnName({id: columnId, title: columnName}))
})
dispatch(editColumnName({ id: columnId, title: columnName }));
});
}
function createProject() {
@ -176,34 +176,37 @@ export const TrackerModal = ({
method: "POST",
data: {
user_id: selectedWorker.user_id,
project_id: projectBoard.id
}
project_id: projectBoard.id,
},
}).then((el) => {
dispatch(addPersonToProject(el))
dispatch(addPersonToProject(el));
setActive(false);
setSelectedWorker('')
setSelectWorkersOpen(false)
})
setSelectedWorker("");
setSelectWorkersOpen(false);
});
}
useEffect(() => {
modalType === "addWorker" ? apiRequest('/project/my-employee').then((el) => {
let persons = el.managerEmployees
let ids = projectBoard.projectUsers.map((user) => user.user_id)
setWorkers(persons.reduce((acc, cur) => {
if (!ids.includes(cur.user_id)) acc.push(cur)
return acc
}, []))
}) : ''
}, [active])
modalType === "addWorker"
? apiRequest("/project/my-employee").then((el) => {
let persons = el.managerEmployees;
let ids = projectBoard.projectUsers.map((user) => user.user_id);
setWorkers(
persons.reduce((acc, cur) => {
if (!ids.includes(cur.user_id)) acc.push(cur);
return acc;
}, [])
);
})
: "";
}, [active]);
return (
<div
className={active ? "modal-add active" : "modal-add"}
onClick={() => {
setActive(false)
setSelectWorkersOpen(false)
setActive(false);
setSelectWorkersOpen(false);
}}
>
<div className="modal-add__content" onClick={(e) => e.stopPropagation()}>
@ -218,35 +221,49 @@ export const TrackerModal = ({
{/* onChange={(e) => setEmailWorker(e.target.value)}*/}
{/* />*/}
{/*</div>*/}
<div className={selectWorkersOpen ? 'select__worker open' : 'select__worker'} onClick={() => setSelectWorkersOpen(!selectWorkersOpen)}>
<p>{selectedWorker ? selectedWorker.employee.fio : 'Выберите пользователя'}</p>
<img className='arrow' src={arrowDown} alt='arrow' />
{Boolean(selectWorkersOpen) &&
<div className='select__worker__dropDown'>
{Boolean(workers.length) ?
workers.map((worker) => {
if (worker === selectedWorker) {
return
}
return <div className='worker' key={worker.id} onClick={() =>
{
setSelectedWorker(worker)
}
}>
<p>{worker.employee.fio}</p>
<img src={urlForLocal(worker.employee.avatar)} alt='avatar'/>
</div>
}) :
<div>Нет пользователей</div>
}
</div>
<div
className={
selectWorkersOpen ? "select__worker open" : "select__worker"
}
onClick={() => setSelectWorkersOpen(!selectWorkersOpen)}
>
<p>
{selectedWorker
? selectedWorker.employee.fio
: "Выберите пользователя"}
</p>
<img className="arrow" src={arrowDown} alt="arrow" />
{Boolean(selectWorkersOpen) && (
<div className="select__worker__dropDown">
{Boolean(workers.length) ? (
workers.map((worker) => {
if (worker === selectedWorker) {
return;
}
return (
<div
className="worker"
key={worker.id}
onClick={() => {
setSelectedWorker(worker);
}}
>
<p>{worker.employee.fio}</p>
<img
src={urlForLocal(worker.employee.avatar)}
alt="avatar"
/>
</div>
);
})
) : (
<div>Нет пользователей</div>
)}
</div>
)}
</div>
</div>
<button
className="button-add"
onClick={addUserToProject}
>
<button className="button-add" onClick={addUserToProject}>
Добавить
</button>
</div>

View File

@ -1,103 +0,0 @@
import React, { useEffect, useState } from 'react'
import { NavLink } from 'react-router-dom'
import { urlForLocal } from '../../helper'
import { apiRequest } from '../../api/request';
import { useDispatch, useSelector } from 'react-redux';
import { getProfileInfo, setProfileInfo } from '../../redux/outstaffingSlice';
import avatarMok from "../../pages/PartnerTreaties/Images/avatarMok.png"
export const Navigation = () => {
const dispatch = useDispatch();
const profileInfo = useSelector(getProfileInfo);
const [user] = useState(localStorage.getItem('role_status') === '18' ? 'partner' : 'developer')
const [navInfo] = useState({
developer: [
{
path: '/summary',
name: 'Резюме'
},
{
path: '/calendar',
name: 'Отчетность'
},
{
path: '/tracker',
name: 'Трекер'
},
{
path: '/payouts',
name: 'Выплаты'
},
{
path: '/settings',
name: 'Настройки'
},
],
partner: [
{
path: '/catalog',
name: 'Каталог'
},
{
path: '/requests',
name: 'Запросы'
},
{
path: '/categories',
name: 'Персонал'
},
{
path: '/tracker',
name: 'Трекер'
},
{
path: '/treaties',
name: 'Договора'
},
{
path: '/settings',
name: 'Настройки'
},
]
})
useEffect(() => {
if (localStorage.getItem('role_status') === '18') {
return
}
apiRequest(`/profile/${localStorage.getItem('cardId')}`)
.then((profileInfo) =>
dispatch(setProfileInfo(profileInfo))
);
}, [dispatch]);
return (
<div className='profileHeader__info'>
<div className='profileHeader__container'>
<nav className='profileHeader__nav'>
{
navInfo[user].map((link, index) => {
return <NavLink key={index} end to={`/profile${link.path}`}>{link.name}</NavLink>
})
}
</nav>
<div className='profileHeader__personalInfo'>
<h3 className='profileHeader__personalInfoName'>
{user === 'developer' ?
profileInfo?.fio :
''
}
</h3>
<NavLink end to={'/profile'}>
<img src={profileInfo.photo ? urlForLocal(profileInfo.photo) : avatarMok} className='profileHeader__personalInfoAvatar' alt='avatar' />
</NavLink>
</div>
</div>
</div>
)
}

View File

@ -0,0 +1,110 @@
import React, { useEffect, useState } from "react";
import { useDispatch, useSelector } from "react-redux";
import { NavLink } from "react-router-dom";
import { getProfileInfo, setProfileInfo } from "@redux/outstaffingSlice";
import { urlForLocal } from "@utils/helper";
import { apiRequest } from "@api/request";
import avatarMok from "assets/images/avatarMok.png";
export const Navigation = () => {
const dispatch = useDispatch();
const profileInfo = useSelector(getProfileInfo);
const [user] = useState(
localStorage.getItem("role_status") === "18" ? "partner" : "developer"
);
const [navInfo] = useState({
developer: [
{
path: "/summary",
name: "Резюме",
},
{
path: "/calendar",
name: "Отчетность",
},
{
path: "/tracker",
name: "Трекер",
},
{
path: "/payouts",
name: "Выплаты",
},
{
path: "/settings",
name: "Настройки",
},
],
partner: [
{
path: "/catalog",
name: "Каталог",
},
{
path: "/requests",
name: "Запросы",
},
{
path: "/categories",
name: "Персонал",
},
{
path: "/tracker",
name: "Трекер",
},
{
path: "/treaties",
name: "Договора",
},
{
path: "/settings",
name: "Настройки",
},
],
});
useEffect(() => {
if (localStorage.getItem("role_status") === "18") {
return;
}
apiRequest(`/profile/${localStorage.getItem("cardId")}`).then(
(profileInfo) => dispatch(setProfileInfo(profileInfo))
);
}, [dispatch]);
return (
<div className="profileHeader__info">
<div className="profileHeader__container">
<nav className="profileHeader__nav">
{navInfo[user].map((link, index) => {
return (
<NavLink key={index} end to={`/profile${link.path}`}>
{link.name}
</NavLink>
);
})}
</nav>
<div className="profileHeader__personalInfo">
<h3 className="profileHeader__personalInfoName">
{user === "developer" ? profileInfo?.fio : ""}
</h3>
<NavLink end to={"/profile"}>
<img
src={
profileInfo.photo ? urlForLocal(profileInfo.photo) : avatarMok
}
className="profileHeader__personalInfoAvatar"
alt="avatar"
/>
</NavLink>
</div>
</div>
</div>
);
};

View File

@ -1,87 +0,0 @@
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 front from './images/front_end.png'
import back from './images/back_end.png'
import design from './images/design.png'
import './outstaffing.scss'
const createSelectPositionHandler =
({positionId, setPositionId, dispatch}) =>
(id) => {
if (id === positionId) {
dispatch(setPositionId(null))
} else {
dispatch(setPositionId(id))
}
};
const Outstaffing = () => {
const dispatch = useDispatch();
const positionId = useSelector(getPositionId);
const tagsArr = useSelector(selectTags);
const onSelectPosition = createSelectPositionHandler({
positionId,
setPositionId,
dispatch
});
return (
<>
<section className='outstaffing'>
<div className='row'>
<div className='col-12 col-xl-4'>
<OutstaffingBlock
dataTags={
tagsArr &&
tagsArr.flat().filter((tag) => tag.name === 'skills_front')
}
img={front}
header='Frontend разработчики'
positionId='2'
isSelected={positionId === '2'}
onSelect={(id) => onSelectPosition(id)}
/>
</div>
<div className='col-12 col-xl-4'>
<OutstaffingBlock
dataTags={
tagsArr &&
tagsArr.flat().filter((tag) => tag.name === 'skills_back')
}
img={back}
header='Backend разработчики'
positionId='1'
isSelected={positionId === '1'}
onSelect={(id) => onSelectPosition(id)}
/>
</div>
<div className='col-12 col-xl-4'>
<OutstaffingBlock
dataTags={
tagsArr &&
tagsArr.flat().filter((tag) => tag.name === 'skills_design')
}
img={design}
header='Дизайн проектов'
positionId='5'
isSelected={positionId === '5'}
onSelect={(id) => onSelectPosition(id)}
/>
</div>
</div>
</section>
<TagSelect/>
</>
)
};
export default Outstaffing

View File

@ -0,0 +1,89 @@
import React from "react";
import { useDispatch, useSelector } from "react-redux";
import {
getPositionId,
selectTags,
setPositionId,
} from "@redux/outstaffingSlice";
import OutstaffingBlock from "@components/OutstaffingBlock/OutstaffingBlock";
import TagSelect from "@components/Select/TagSelect";
import back from "assets/images/partnerProfile/back-end.webp";
import design from "assets/images/partnerProfile/design.webp";
import front from "assets/images/partnerProfile/front-end.webp";
import "./outstaffing.scss";
const createSelectPositionHandler =
({ positionId, setPositionId, dispatch }) =>
(id) => {
if (id === positionId) {
dispatch(setPositionId(null));
} else {
dispatch(setPositionId(id));
}
};
const Outstaffing = () => {
const dispatch = useDispatch();
const positionId = useSelector(getPositionId);
const tagsArr = useSelector(selectTags);
const onSelectPosition = createSelectPositionHandler({
positionId,
setPositionId,
dispatch,
});
return (
<>
<section className="outstaffing">
<div className="row">
<div className="col-12 col-xl-4">
<OutstaffingBlock
dataTags={
tagsArr &&
tagsArr.flat().filter((tag) => tag.name === "skills_front")
}
img={front}
header="Frontend разработчики"
positionId="2"
isSelected={positionId === "2"}
onSelect={(id) => onSelectPosition(id)}
/>
</div>
<div className="col-12 col-xl-4">
<OutstaffingBlock
dataTags={
tagsArr &&
tagsArr.flat().filter((tag) => tag.name === "skills_back")
}
img={back}
header="Backend разработчики"
positionId="1"
isSelected={positionId === "1"}
onSelect={(id) => onSelectPosition(id)}
/>
</div>
<div className="col-12 col-xl-4">
<OutstaffingBlock
dataTags={
tagsArr &&
tagsArr.flat().filter((tag) => tag.name === "skills_design")
}
img={design}
header="Дизайн проектов"
positionId="5"
isSelected={positionId === "5"}
onSelect={(id) => onSelectPosition(id)}
/>
</div>
</div>
</section>
<TagSelect />
</>
);
};
export default Outstaffing;

Binary file not shown.

Before

Width:  |  Height:  |  Size: 15 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 19 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 14 KiB

View File

@ -1,118 +0,0 @@
import React from 'react'
import OutsideClickHandler from 'react-outside-click-handler'
import {useDispatch, useSelector} from 'react-redux'
import {
selectItems,
selectedItems,
profiles,
} from '../../redux/outstaffingSlice'
import {apiRequest} from "../../api/request";
import './outstaffingBlock.scss'
const handlePositionClick = (
{
dispatch,
positionId,
isSelected,
onSelect,
apiRequest
}) => {
if (isSelected) {
apiRequest('/profile', {
params: {
limit: 1000
},
}).then((profileArr) => {
dispatch(profiles(profileArr));
dispatch(selectedItems([]));
onSelect(positionId)
})
} else {
apiRequest('/profile', {
params: {
limit: '1000',
position_id: positionId
},
}).then((res) => {
dispatch(profiles(res));
dispatch(selectedItems([]));
onSelect(positionId)
})
}
};
const OutstaffingBlock = (
{
dataTags = [],
selected,
img,
header,
positionId,
isSelected,
onSelect
}) => {
const dispatch = useDispatch();
const itemsArr = useSelector(selectItems);
const handleBlockClick = (item, id) => {
if (!itemsArr.find((el) => item === el.value)) {
dispatch(selectedItems([...itemsArr, {id, value: item, label: item}]))
}
};
let classes;
dataTags.forEach((el) => {
if (el.name === 'skills_back') {
classes = 'back'
} else if (el.name === 'skills_design') {
classes = 'des'
} else if (el.name === 'skills_front') {
classes = 'front'
}
});
return (
<OutsideClickHandler onOutsideClick={() => isSelected && onSelect(null)}>
<div className={`outstaffing-block${isSelected ? ' outstaffing-block__selected' : ''}`}>
<div className={`outstaffing-block__img ${selected ? ' outstaffing-block__border' : ''}`}
onClick={() => handlePositionClick(
{
dispatch,
positionId,
isSelected,
onSelect,
apiRequest
})
}>
<h3>{header}</h3>
<img className={classes} src={img} alt='img'/>
</div>
<div className={`${selected ? 'outstaffing-block__mobile--block' : 'outstaffing-block__mobile--none'}`} >
<p className='outstaffing-block__text'># Популярный стек</p>
{dataTags && (
<ul className='outstaffing-block__items'>
{dataTags.map((item) => (
<li
key={item.id}
onClick={() => handleBlockClick(item.value, item.id)}
>
{item.value}
</li>
))}
</ul>
)}
</div>
</div>
</OutsideClickHandler>
)
};
export default OutstaffingBlock

View File

@ -0,0 +1,123 @@
import React from "react";
import OutsideClickHandler from "react-outside-click-handler";
import { useDispatch, useSelector } from "react-redux";
import { profiles, selectItems, selectedItems } from "@redux/outstaffingSlice";
import { apiRequest } from "@api/request";
import "./outstaffingBlock.scss";
const handlePositionClick = ({
dispatch,
positionId,
isSelected,
onSelect,
apiRequest,
}) => {
if (isSelected) {
apiRequest("/profile", {
params: {
limit: 1000,
},
}).then((profileArr) => {
dispatch(profiles(profileArr));
dispatch(selectedItems([]));
onSelect(positionId);
});
} else {
apiRequest("/profile", {
params: {
limit: "1000",
position_id: positionId,
},
}).then((res) => {
dispatch(profiles(res));
dispatch(selectedItems([]));
onSelect(positionId);
});
}
};
const OutstaffingBlock = ({
dataTags = [],
selected,
img,
header,
positionId,
isSelected,
onSelect,
}) => {
const dispatch = useDispatch();
const itemsArr = useSelector(selectItems);
const handleBlockClick = (item, id) => {
if (!itemsArr.find((el) => item === el.value)) {
dispatch(selectedItems([...itemsArr, { id, value: item, label: item }]));
}
};
let classes;
dataTags.forEach((el) => {
if (el.name === "skills_back") {
classes = "back";
} else if (el.name === "skills_design") {
classes = "des";
} else if (el.name === "skills_front") {
classes = "front";
}
});
return (
<OutsideClickHandler onOutsideClick={() => isSelected && onSelect(null)}>
<div
className={`outstaffing-block${
isSelected ? " outstaffing-block__selected" : ""
}`}
>
<div
className={`outstaffing-block__img ${
selected ? " outstaffing-block__border" : ""
}`}
onClick={() =>
handlePositionClick({
dispatch,
positionId,
isSelected,
onSelect,
apiRequest,
})
}
>
<h3>{header}</h3>
<img className={classes} src={img} alt="img" />
</div>
<div
className={`${
selected
? "outstaffing-block__mobile--block"
: "outstaffing-block__mobile--none"
}`}
>
<p className="outstaffing-block__text"># Популярный стек</p>
{dataTags && (
<ul className="outstaffing-block__items">
{dataTags.map((item) => (
<li
key={item.id}
onClick={() => handleBlockClick(item.value, item.id)}
>
{item.value}
</li>
))}
</ul>
)}
</div>
</div>
</OutsideClickHandler>
);
};
export default OutstaffingBlock;

View File

@ -1,15 +1,15 @@
body {
font-family: 'LabGrotesque', sans-serif !important;
font-family: "LabGrotesque", sans-serif !important;
}
.container {
max-width: 1160px !important;
}
.catalog {
background: #F1F1F1;
background: #f1f1f1;
height: 100%;
min-height: 100vh;
font-family: 'LabGrotesque', sans-serif;
font-family: "LabGrotesque", sans-serif;
padding-top: 23px;
&__title {
@ -37,7 +37,7 @@ body {
}
& > p {
font-family: 'GT Eesti Pro Display';
font-family: "GT Eesti Pro Display";
font-size: 1.2em;
font-weight: 300;
font-style: normal;
@ -60,7 +60,7 @@ body {
right: 13%;
top: 30%;
max-width: 130px;
font-family: 'GT Eesti Pro Display';
font-family: "GT Eesti Pro Display";
font-size: 18px;
font-weight: 400;
font-style: normal;
@ -85,7 +85,7 @@ body {
&__items {
li {
font-family: 'GT Eesti Pro Display';
font-family: "GT Eesti Pro Display";
font-size: 1.8em;
font-weight: 100;
font-style: normal;

View File

@ -1,16 +0,0 @@
import React from 'react'
import {Link} from "react-router-dom";
import './profileBreadcrumbs.scss'
export const ProfileBreadcrumbs = ({ links }) => {
return (
<div className='profileBreadcrumbs'>
{links.map((link, index) => {
return <Link key={index} to={link.link}>{link.name}</Link>
})
}
</div>
)
}

View File

@ -0,0 +1,18 @@
import React from "react";
import { Link } from "react-router-dom";
import "./profileBreadcrumbs.scss";
export const ProfileBreadcrumbs = ({ links }) => {
return (
<div className="profileBreadcrumbs">
{links.map((link, index) => {
return (
<Link key={index} to={link.link}>
{link.name}
</Link>
);
})}
</div>
);
};

View File

@ -7,7 +7,7 @@
}
a {
color: #5B6871;
color: #5b6871;
font-weight: 400;
font-size: 12px;
line-height: 16px;
@ -31,11 +31,11 @@
}
&:after {
content: '';
background-image: url("../../images/BreadcrumbsArrow.png");
content: "";
background-image: url("../../assets/icons/arrows/BreadcrumbsArrow.png");
background-repeat: no-repeat;
width: 7px;
height:10px;
height: 10px;
position: absolute;
right: -14px;
}

View File

@ -1,29 +1,28 @@
import moment from "moment";
import "moment/locale/ru";
import React, { useEffect, useState } from "react";
import { useDispatch, useSelector } from "react-redux";
import { getReports } from "../Calendar/calendarHelper";
import { Link, Navigate } from "react-router-dom";
import moment from "moment";
import { ProfileCalendarComponent } from "./ProfileCalendarComponent";
import { Loader } from "../Loader/Loader";
import { ProfileHeader } from "../ProfileHeader/ProfileHeader";
import { ProfileBreadcrumbs } from "../../components/ProfileBreadcrumbs/ProfileBreadcrumbs";
import { Footer } from "../Footer/Footer";
import { Navigation } from "../Navigation/Navigation";
import { ViewReport } from "../../pages/ViewReport/ViewReport";
import { urlForLocal } from "../../helper";
import { apiRequest } from "../../api/request";
import { getProfileInfo } from "../../redux/outstaffingSlice";
import { getProfileInfo } from "@redux/outstaffingSlice";
import {
getRequestDates,
setReportDate,
setRequestDate,
} from "../../redux/reportSlice";
} from "@redux/reportSlice";
import "moment/locale/ru";
import { urlForLocal } from "@utils/helper";
import { apiRequest } from "@api/request";
import { getReports } from "@components/Calendar/calendarHelper";
import { Footer } from "@components/Common/Footer/Footer";
import { Loader } from "@components/Common/Loader/Loader";
import { Navigation } from "@components/Navigation/Navigation";
import { ProfileBreadcrumbs } from "@components/ProfileBreadcrumbs/ProfileBreadcrumbs";
import { ProfileHeader } from "@components/ProfileHeader/ProfileHeader";
import { ProfileCalendarComponent } from "./ProfileCalendarComponent";
import "./profileCalendar.scss";
export const ProfileCalendar = () => {

View File

@ -1,26 +1,29 @@
import React, { useState, useEffect } from "react";
import arrow from "../../images/arrowCalendar.png";
import rectangle from "../../images/rectangle__calendar.png";
import calendarIcon from "../../images/calendar_icon.png";
import moment from "moment";
import "moment/locale/ru";
import React, { useEffect, useState } from "react";
import { useDispatch } from "react-redux";
import { Link } from "react-router-dom";
import {
setReportDate,
setRequestDate,
setSendRequest,
} from "@redux/reportSlice";
import "@components/Calendar/calendarComponent.scss";
import {
calendarHelper,
currentMonthAndDay,
getReports,
hourOfNum,
} from "../Calendar/calendarHelper";
import {
setReportDate,
setRequestDate,
setSendRequest,
} from "../../redux/reportSlice";
import { useDispatch } from "react-redux";
import { Link } from "react-router-dom";
import ShortReport from "../ShortReport/ShortReport";
} from "@components/Calendar/calendarHelper";
import ShortReport from "@components/ShortReport/ShortReport";
import "moment/locale/ru";
import "./../Calendar/calendarComponent.scss";
import arrow from "assets/icons/arrows/arrowCalendar.png";
import calendarIcon from "assets/icons/calendar.svg";
import rectangle from "assets/images/rectangle__calendar.png";
// eslint-disable-next-line react/display-name
export const ProfileCalendarComponent = React.memo(
({ value, setValueHandler, reports, totalHours }) => {
const dispatch = useDispatch();

View File

@ -1,61 +0,0 @@
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 {apiRequest} from "../../api/request";
import {auth, setProfileInfo} from "../../redux/outstaffingSlice";
import {getRole} from "../../redux/roleSlice";
import './profileHeader.scss'
export const ProfileHeader = () => {
const navigate = useNavigate();
const dispatch = useDispatch();
const userRole = useSelector(getRole);
const [user] = useState(localStorage.getItem('role_status') === '18' ? 'partner' : 'developer')
const [isLoggingOut, setIsLoggingOut] = useState(false);
useEffect(() => {
if (localStorage.getItem('role_status') === '18') {
return
}
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'>
<NavLink to={'/profile'} className='profileHeader__title'>itguild.
<span>
{user === 'developer' ?
'для разработчиков' :
'для партнеров'
}
</span>
</NavLink>
<button onClick={handler} className='profileHeader__logout'>
{isLoggingOut ? <Loader/> : 'Выйти'}
</button>
</div>
</div>
</header>
)
};

View File

@ -0,0 +1,59 @@
import React, { useEffect, useState } from "react";
import { useDispatch, useSelector } from "react-redux";
import { NavLink, useNavigate } from "react-router-dom";
import { auth, setProfileInfo } from "@redux/outstaffingSlice";
import { getRole } from "@redux/roleSlice";
import { apiRequest } from "@api/request";
import { Loader } from "@components/Common/Loader/Loader";
import "./profileHeader.scss";
export const ProfileHeader = () => {
const navigate = useNavigate();
const dispatch = useDispatch();
const userRole = useSelector(getRole);
const [user] = useState(
localStorage.getItem("role_status") === "18" ? "partner" : "developer"
);
const [isLoggingOut, setIsLoggingOut] = useState(false);
useEffect(() => {
if (localStorage.getItem("role_status") === "18") {
return;
}
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">
<NavLink to={"/profile"} className="profileHeader__title">
itguild.
<span>
{user === "developer" ? "для разработчиков" : "для партнеров"}
</span>
</NavLink>
<button onClick={handler} className="profileHeader__logout">
{isLoggingOut ? <Loader /> : "Выйти"}
</button>
</div>
</div>
</header>
);
};

View File

@ -2,10 +2,10 @@
width: 100%;
display: flex;
flex-direction: column;
font-family: 'LabGrotesque', sans-serif;
font-family: "LabGrotesque", sans-serif;
&__head {
background: #E1FCCF;
background: #e1fccf;
}
&__container {
@ -26,7 +26,7 @@
color: black;
span {
color: #52B709;
color: #52b709;
}
&:hover {
@ -45,7 +45,7 @@
}
&__info {
background: #FFFFFF;
background: #ffffff;
}
&__nav {

View File

@ -1,16 +1,18 @@
import React, { useEffect, useState } from "react";
import { useDispatch } from "react-redux";
import { Link } from "react-router-dom";
import { apiRequest } from "../../api/request";
import { deleteProject, modalToggle } from "../../redux/projectsTrackerSlice";
import { ModalSelect } from "../UI/ModalSelect/ModalSelect";
import TrackerModal from "../UI/TrackerModal/TrackerModal";
import { deleteProject, modalToggle } from "@redux/projectsTrackerSlice";
import link from "../../images/link.svg";
import archiveSet from "../../images/archive.svg";
import del from "../../images/delete.svg";
import edit from "../../images/edit.svg";
import { apiRequest } from "@api/request";
import { ModalSelect } from "@components/Modal/ModalSelect/ModalSelect";
import TrackerModal from "@components/Modal/TrackerModal/TrackerModal";
import archiveSet from "assets/icons/archive.svg";
import del from "assets/icons/delete.svg";
import edit from "assets/icons/edit.svg";
import link from "assets/icons/link.svg";
import "./projectTiket.scss";
@ -45,7 +47,7 @@ export const ProjectTiket = ({ project, index }) => {
project_id: project.id,
status: 10,
},
}).then((res) => {
}).then(() => {
dispatch(deleteProject(project));
});
}

View File

@ -1,23 +1,31 @@
import React from 'react';
import { useSelector } from 'react-redux'
import { Route, Redirect } from 'react-router-dom';
import { selectAuth } from '../../redux/outstaffingSlice';
import React from "react";
import { useSelector } from "react-redux";
import { Route } from "react-router-dom";
import { selectAuth } from "@redux/outstaffingSlice";
export const ProtectedRoute = ({ component: Component, ...rest }) => {
const isAuth = useSelector(selectAuth)
const existingToken = localStorage.getItem('auth_token')
const expiresAt = localStorage.getItem('access_token_expired_at')
const isAuth = useSelector(selectAuth);
const existingToken = localStorage.getItem("auth_token");
const expiresAt = localStorage.getItem("access_token_expired_at");
const isTokenAlive = !isAuth && (existingToken && expiresAt && new Date(expiresAt).getTime() > (new Date()).getTime());
// eslint-disable-next-line no-unused-vars
const isTokenAlive =
!isAuth &&
existingToken &&
expiresAt &&
new Date(expiresAt).getTime() > new Date().getTime();
return (
<Route
{...rest}
render={props =>
// ( isAuth || isTokenAlive) ? (
<Component {...props} />
// ) : <Redirect to='/auth' />
}
/>
);
};
return (
<Route
{...rest}
render={
(props) => (
// ( isAuth || isTokenAlive) ? (
<Component {...props} />
)
// ) : <Redirect to='/auth' />
}
/>
);
};

View File

@ -1,239 +0,0 @@
import React, {useState, useEffect} from 'react'
import {useSelector} from 'react-redux'
import {Link, Navigate, useNavigate} from 'react-router-dom'
import DatePicker, { registerLocale } from "react-datepicker"
import {getCorrectDate, getCreatedDate, hourOfNum} from '../Calendar/calendarHelper'
import ru from "date-fns/locale/ru"
registerLocale("ru", ru);
import {Loader} from '../Loader/Loader'
import {Footer} from "../Footer/Footer";
import {ProfileHeader} from "../ProfileHeader/ProfileHeader";
import {ProfileBreadcrumbs} from "../../components/ProfileBreadcrumbs/ProfileBreadcrumbs"
import {apiRequest} from "../../api/request";
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 arrow from "../../images/right-arrow.png";
import './reportForm.scss'
import "react-datepicker/dist/react-datepicker.css";
import { Navigation } from '../Navigation/Navigation'
const ReportForm = () => {
if(localStorage.getItem('role_status') === '18') {
return <Navigate to="/profile" replace/>
}
const navigate= useNavigate();
const reportDate = useSelector(getReportDate);
useEffect(() => {
initListeners()
}, [])
const [isFetching, setIsFetching] = useState(false);
const [reportSuccess, setReportSuccess] = useState('');
const [startDate, setStartDate] = useState(reportDate ? new Date (reportDate._d) : new Date());
const [datePickerOpen, setDatePickerOpen] = useState(false);
const [inputs, setInputs] = useState([{task: '', hours_spent: '', minutes_spent: 0}]);
const [troublesInputValue, setTroublesInputValue] = useState('');
const [scheduledInputValue, setScheduledInputValue] = useState('');
const addInput = () => {
setInputs((prev) => [...prev, {task: '', hours_spent: '', minutes_spent: 0}])
};
const initListeners = () => {
document.addEventListener('click', closeByClickingOut)
}
const closeByClickingOut = (event) => {
const path = event.path || (event.composedPath && event.composedPath())
if (event && !path.find((div) => div.classList && (div.classList.contains('report-form__block-img') || div.classList.contains('react-datepicker-popper')))) {
setDatePickerOpen(false)
}
}
const totalHours = inputs.reduce((a, b) => a + b.hours_spent, 0);
const deleteInput = (indexRemove) => {
setInputs((prev) => prev.filter((el, index) => index !== indexRemove))
};
const handler = () => {
for (let input of inputs) {
if(!input.task || !input.hours_spent) {
setReportSuccess('Заполните задачи');
setTimeout(() => setReportSuccess(''), 2000)
return
}
}
apiRequest('/reports/create', {
method: 'POST',
data: {
tasks: inputs,
difficulties: troublesInputValue,
tomorrow: scheduledInputValue,
created_at: getCreatedDate(startDate),
status: 1,
},
}).then((res) => {
setReportSuccess('Отчет отправлен');
setTimeout(() => {
setReportSuccess('')
navigate('/profile/calendar');
}, 1000)
setInputs(() => []);
setTroublesInputValue('');
setScheduledInputValue('');
setIsFetching(false);
setInputs(() => [{task: '', hours_spent: '', minutes_spent: 0}]);
})
};
return (
<section className='report-form'>
<ProfileHeader/>
<Navigation />
<div className='container'>
<ProfileBreadcrumbs links={[{name: 'Главная', link: '/profile'},
{name: 'Ваша отчетность', link: '/profile/calendar'},
{name: 'Страница добавления нового отчета', link: '/report'}]}
/>
<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>
<h3>Дата заполнения отчета:</h3>
</div>
<div className='report-form__block-img' onClick={() => setDatePickerOpen(true)}>
<img
className='report-form__calendar-icon'
src={calendarIcon}
alt=''
/>
{getCorrectDate(startDate)}
</div>
<DatePicker
className='datePicker'
open={datePickerOpen}
locale="ru"
selected={startDate}
onChange={(date) => {
setDatePickerOpen(false)
setStartDate(date)
}}
/>
<div className='report-form__task-list'>
<img src={ellipse} alt=''/>
<span>Какие задачи были выполнены?</span>
</div>
</div>
<div className='row'>
<div className='col-8'>
<div className='report-form__task-header'>
<p className='report-form__task-title--description'>
Краткое описание задачи
</p>
<p className='report-form__task-title--hours'>Количество часов</p>
</div>
{inputs.map((input, index) => {
return (
<form id={'input'} key={`input__${index}`} className='report-form__task-form'>
<div className='report-form__task-number'>
{index + 1}.
</div>
<div className='report-form__task-input report-form__task-input--description'>
<input value={inputs[index].task} className={!input.task && reportSuccess === 'Заполните задачи' ? 'checkTask' : ''} name='text' type='text'
onChange={e => setInputs(inputs.map((input, inputIndex) => {
return index === inputIndex
? {
...input,
task: e.target.value
}
: input
}))}/>
</div>
<div className='report-form__task-input report-form__task-input--hours'>
<input value={inputs[index].hours_spent} className={!input.hours_spent && reportSuccess === 'Заполните задачи' ? 'checkTask' : ''} name='number' type='number' min='1'
onChange={e => setInputs(inputs.map((input, inputIndex) => {
return index === inputIndex
? {
...input,
hours_spent: Number(e.target.value)
}
: input
}))}/>
</div>
{index > 0 &&
<div className='report-form__task-remove'>
<img onClick={() => deleteInput(index)} src={remove} alt=''/>
</div>
}
</form>
)
})}
<div className='report-form__form-add'>
<p className='addMore' onClick={addInput}>+</p>
<span>Добавить еще </span>
</div>
</div>
</div>
<div className='row'>
<div className='col-12'>
<div className='report-form__input-box'>
<div className='report-form__troubles'>
<img src={ellipse} alt=''/>
<span>Какие сложности возникли?</span>
</div>
<input type='text' value={troublesInputValue} onChange={e => setTroublesInputValue(e.target.value)}/>
<div className='report-form__scheduled'>
<img src={ellipse} alt=''/>
<span>Что планируется сделать завтра?</span>
</div>
<input type='text' value={scheduledInputValue} onChange={e => setScheduledInputValue(e.target.value)}/>
</div>
</div>
</div>
<div className='row'>
<div className='col-12'>
<div className='report-form__footer'>
<button className='report-form__footer-btn' onClick={() => handler()}>
{isFetching ? <Loader/> : 'Отправить'}
</button>
<p className='report-form__footer-text'>
Всего за день : <span>{totalHours} {hourOfNum(totalHours)}</span>
</p>
{reportSuccess &&
<p className={`report-form__footer-done ${reportSuccess === 'Заполните задачи' ? 'errorText' : ''}`}>{reportSuccess}</p>
}
</div>
</div>
</div>
</div>
</div>
<Footer/>
</section>
)
};
export default ReportForm

View File

@ -0,0 +1,326 @@
import ru from "date-fns/locale/ru";
import React, { useEffect, useState } from "react";
import DatePicker, { registerLocale } from "react-datepicker";
import "react-datepicker/dist/react-datepicker.css";
import { useSelector } from "react-redux";
import { Link, Navigate, useNavigate } from "react-router-dom";
import { getReportDate } from "@redux/reportSlice";
import { apiRequest } from "@api/request";
import { Footer } from "@components/Common/Footer/Footer";
import { Loader } from "@components/Common/Loader/Loader";
import { Navigation } from "@components/Navigation/Navigation";
import { ProfileBreadcrumbs } from "@components/ProfileBreadcrumbs/ProfileBreadcrumbs";
import { ProfileHeader } from "@components/ProfileHeader/ProfileHeader";
import arrow from "assets/icons/arrows/left-arrow.png";
import calendarIcon from "assets/icons/calendar.svg";
import ellipse from "assets/icons/ellipse.png";
import remove from "assets/icons/remove.svg";
import {
getCorrectDate,
getCreatedDate,
hourOfNum,
} from "../Calendar/calendarHelper";
import "./reportForm.scss";
registerLocale("ru", ru);
const ReportForm = () => {
if (localStorage.getItem("role_status") === "18") {
return <Navigate to="/profile" replace />;
}
const navigate = useNavigate();
const reportDate = useSelector(getReportDate);
useEffect(() => {
initListeners();
}, []);
const [isFetching, setIsFetching] = useState(false);
const [reportSuccess, setReportSuccess] = useState("");
const [startDate, setStartDate] = useState(
reportDate ? new Date(reportDate._d) : new Date()
);
const [datePickerOpen, setDatePickerOpen] = useState(false);
const [inputs, setInputs] = useState([
{ task: "", hours_spent: "", minutes_spent: 0 },
]);
const [troublesInputValue, setTroublesInputValue] = useState("");
const [scheduledInputValue, setScheduledInputValue] = useState("");
const addInput = () => {
setInputs((prev) => [
...prev,
{ task: "", hours_spent: "", minutes_spent: 0 },
]);
};
const initListeners = () => {
document.addEventListener("click", closeByClickingOut);
};
const closeByClickingOut = (event) => {
const path = event.path || (event.composedPath && event.composedPath());
if (
event &&
!path.find(
(div) =>
div.classList &&
(div.classList.contains("report-form__block-img") ||
div.classList.contains("react-datepicker-popper"))
)
) {
setDatePickerOpen(false);
}
};
const totalHours = inputs.reduce((a, b) => a + b.hours_spent, 0);
const deleteInput = (indexRemove) => {
setInputs((prev) => prev.filter((el, index) => index !== indexRemove));
};
const handler = () => {
for (let input of inputs) {
if (!input.task || !input.hours_spent) {
setReportSuccess("Заполните задачи");
setTimeout(() => setReportSuccess(""), 2000);
return;
}
}
apiRequest("/reports/create", {
method: "POST",
data: {
tasks: inputs,
difficulties: troublesInputValue,
tomorrow: scheduledInputValue,
created_at: getCreatedDate(startDate),
status: 1,
},
}).then(() => {
setReportSuccess("Отчет отправлен");
setTimeout(() => {
setReportSuccess("");
navigate("/profile/calendar");
}, 1000);
setInputs(() => []);
setTroublesInputValue("");
setScheduledInputValue("");
setIsFetching(false);
setInputs(() => [{ task: "", hours_spent: "", minutes_spent: 0 }]);
});
};
return (
<section className="report-form">
<ProfileHeader />
<Navigation />
<div className="container">
<ProfileBreadcrumbs
links={[
{ name: "Главная", link: "/profile" },
{ name: "Ваша отчетность", link: "/profile/calendar" },
{ name: "Страница добавления нового отчета", link: "/report" },
]}
/>
<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>
<h3>Дата заполнения отчета:</h3>
</div>
<div
className="report-form__block-img"
onClick={() => setDatePickerOpen(true)}
>
<img
className="report-form__calendar-icon"
src={calendarIcon}
alt=""
/>
{getCorrectDate(startDate)}
</div>
<DatePicker
className="datePicker"
open={datePickerOpen}
locale="ru"
selected={startDate}
onChange={(date) => {
setDatePickerOpen(false);
setStartDate(date);
}}
/>
<div className="report-form__task-list">
<img src={ellipse} alt="" />
<span>Какие задачи были выполнены?</span>
</div>
</div>
<div className="row">
<div className="col-8">
<div className="report-form__task-header">
<p className="report-form__task-title--description">
Краткое описание задачи
</p>
<p className="report-form__task-title--hours">
Количество часов
</p>
</div>
{inputs.map((input, index) => {
return (
<form
id={"input"}
key={`input__${index}`}
className="report-form__task-form"
>
<div className="report-form__task-number">{index + 1}.</div>
<div className="report-form__task-input report-form__task-input--description">
<input
value={inputs[index].task}
className={
!input.task && reportSuccess === "Заполните задачи"
? "checkTask"
: ""
}
name="text"
type="text"
onChange={(e) =>
setInputs(
inputs.map((input, inputIndex) => {
return index === inputIndex
? {
...input,
task: e.target.value,
}
: input;
})
)
}
/>
</div>
<div className="report-form__task-input report-form__task-input--hours">
<input
value={inputs[index].hours_spent}
className={
!input.hours_spent &&
reportSuccess === "Заполните задачи"
? "checkTask"
: ""
}
name="number"
type="number"
min="1"
onChange={(e) =>
setInputs(
inputs.map((input, inputIndex) => {
return index === inputIndex
? {
...input,
hours_spent: Number(e.target.value),
}
: input;
})
)
}
/>
</div>
{index > 0 && (
<div className="report-form__task-remove">
<img
onClick={() => deleteInput(index)}
src={remove}
alt=""
/>
</div>
)}
</form>
);
})}
<div className="report-form__form-add">
<p className="addMore" onClick={addInput}>
+
</p>
<span>Добавить еще </span>
</div>
</div>
</div>
<div className="row">
<div className="col-12">
<div className="report-form__input-box">
<div className="report-form__troubles">
<img src={ellipse} alt="" />
<span>Какие сложности возникли?</span>
</div>
<input
type="text"
value={troublesInputValue}
onChange={(e) => setTroublesInputValue(e.target.value)}
/>
<div className="report-form__scheduled">
<img src={ellipse} alt="" />
<span>Что планируется сделать завтра?</span>
</div>
<input
type="text"
value={scheduledInputValue}
onChange={(e) => setScheduledInputValue(e.target.value)}
/>
</div>
</div>
</div>
<div className="row">
<div className="col-12">
<div className="report-form__footer">
<button
className="report-form__footer-btn"
onClick={() => handler()}
>
{isFetching ? <Loader /> : "Отправить"}
</button>
<p className="report-form__footer-text">
Всего за день :{" "}
<span>
{totalHours} {hourOfNum(totalHours)}
</span>
</p>
{reportSuccess && (
<p
className={`report-form__footer-done ${
reportSuccess === "Заполните задачи" ? "errorText" : ""
}`}
>
{reportSuccess}
</p>
)}
</div>
</div>
</div>
</div>
</div>
<Footer />
</section>
);
};
export default ReportForm;

View File

@ -1,5 +1,5 @@
.report-form {
background: #F1F1F1;
background: #f1f1f1;
height: 100%;
min-height: 100vh;
font-family: "LabGrotesque", sans-serif;
@ -14,7 +14,7 @@
}
&__content {
background: #FFFFFF;
background: #ffffff;
border-radius: 12px;
margin: 25px 0 80px;
padding: 50px 40px;
@ -47,10 +47,9 @@
}
&__block-title {
h2 {
color: #52B709;
font-family: 'GT Eesti Pro Display';
color: #52b709;
font-family: "GT Eesti Pro Display";
font-size: 3.3em;
font-weight: 700;
font-style: normal;
@ -64,7 +63,7 @@
}
h3 {
font-family: 'GT Eesti Pro Display';
font-family: "GT Eesti Pro Display";
font-size: 2.1em;
font-weight: 400;
font-style: normal;
@ -90,7 +89,7 @@
background-color: #ffffff;
display: flex;
align-items: center;
font-family: 'GT Eesti Pro Display';
font-family: "GT Eesti Pro Display";
font-size: 13px;
font-weight: 700;
font-style: normal;
@ -106,7 +105,6 @@
}
&__task {
&-number {
height: 14px;
color: #282828;
@ -126,7 +124,7 @@
& > span {
color: #18586e;
font-family: 'GT Eesti Pro Display';
font-family: "GT Eesti Pro Display";
font-size: 2em;
font-weight: 500;
font-style: normal;
@ -150,7 +148,7 @@
margin-left: 50px;
p {
font-family: 'GT Eesti Pro Display';
font-family: "GT Eesti Pro Display";
font-size: 1.5em;
font-style: normal;
letter-spacing: normal;
@ -206,7 +204,6 @@
margin-left: 20px;
&--description {
.checkTask {
border-color: #fc0000;
}
@ -282,17 +279,17 @@
display: flex;
align-items: center;
justify-content: center;
color: #4CAF50;
color: #4caf50;
width: 38px;
height: 38px;
background: #E8E8E8;
background: #e8e8e8;
margin-bottom: 0;
border-radius: 50px;
font-size: 32px;
}
span {
font-family: 'GT Eesti Pro Display';
font-family: "GT Eesti Pro Display";
font-size: 1.5em;
font-style: normal;
letter-spacing: normal;
@ -334,7 +331,7 @@
span {
color: #18586e;
font-family: 'GT Eesti Pro Display';
font-family: "GT Eesti Pro Display";
font-size: 2em;
font-weight: 500;
font-style: normal;
@ -371,7 +368,7 @@
rgba(255, 255, 255, 0) 100%
);
color: #ffffff;
font-family: 'Muller';
font-family: "Muller";
font-size: 1.6em;
letter-spacing: normal;
text-align: center;
@ -379,7 +376,7 @@
}
&-text {
font-family: 'GT Eesti Pro Display';
font-family: "GT Eesti Pro Display";
font-size: 1.9em;
font-weight: 600;
font-style: normal;
@ -416,7 +413,7 @@
margin-bottom: 28px;
span {
font-family: 'GT Eesti Pro Display';
font-family: "GT Eesti Pro Display";
font-size: 2em;
font-weight: 700;
font-style: normal;

View File

@ -1,86 +0,0 @@
.search {
margin-top: 73px;
}
.search__title {
font-family: 'GT Eesti Pro Display', sans-serif;
font-size: 2.4em;
font-weight: 500;
font-style: normal;
letter-spacing: normal;
line-height: normal;
text-align: center;
margin-bottom: 40px;
}
.search__box {
display: flex;
justify-content: space-between;
}
@media (max-width: 575.98px) {
.search__box {
flex-direction: column;
align-items: center;
}
}
.search__box > button {
color: white;
width: 131px;
height: 40px;
border-radius: 10px;
border: none;
font-family: 'Muller', sans-serif;
font-size: 1.6em;
letter-spacing: 0.8px;
text-align: center;
background-color: #73c141;
}
.search__box > button:hover {
background: rgba(0, 0, 0, 0);
color: #73c141;
box-shadow: inset 0 0 0 3px #73c141;
}
@media (max-width: 575.98px) {
.search__box > button {
margin-top: 20px;
}
}
.select {
width: 85%;
}
[class$='-placeholder'] {
display: none;
}
[class$='-control'] {
border-color: #e8e8e8 !important;
box-shadow: 0 0 0 0 #e8e8e8 !important;
}
[class$='-value__label'] {
font-size: 1.4em !important;
}
[class$='-option'] {
font-size: 1.6em !important;
color: gray !important;
}
.search__submit {
font-weight: bold;
}
.search__submit:hover .loader path {
fill: #6aaf5c;
}
@media (max-width: 990px) {
.select {
margin-right: 1rem;
}
}

View File

@ -1,86 +0,0 @@
import React, {useState} from 'react'
import {useSelector, useDispatch} from 'react-redux'
import Select from 'react-select'
import {Loader} from '../Loader/Loader'
import {apiRequest} from "../../api/request";
import {
selectedItems,
selectItems,
selectTags,
profiles,
setPositionId
} from '../../redux/outstaffingSlice'
import './TagSelect.css'
const TagSelect = () => {
const [searchLoading, setSearchLoading] = useState(false);
const dispatch = useDispatch();
const itemsArr = useSelector(selectItems);
const tagsArr = useSelector(selectTags);
const handleSubmit = ({dispatch, setSearchLoading}) => {
setSearchLoading(true);
dispatch(setPositionId(null));
const filterItemsId = itemsArr.map((item) => item.id).join();
const params = filterItemsId ? {skill: filterItemsId} : '';
apiRequest('/profile', {
params: {...params, limit: 1000},
}).then((res) => {
dispatch(profiles(res));
setSearchLoading(false)
})
// dispatch(selectedItems([]));
};
return (
<>
<section className='search'>
<div className='row'>
<div className='col-12'>
<h2 className='search__title'>
Найти специалиста по навыкам
</h2>
<div className='search__box'>
<Select
value={itemsArr}
onChange={(value) => dispatch(selectedItems(value))}
isMulti
name='tags'
className='select'
classNamePrefix='select'
options={
tagsArr &&
tagsArr.flat().map((item) => {
return {
id: item.id,
value: item.value,
label: item.value
}
})
}
/>
<button
onClick={() => handleSubmit({dispatch, setSearchLoading})}
type='submit'
className='search__submit'
>
{searchLoading ? <Loader width={30} height={30}/> : 'Поиск'}
</button>
</div>
</div>
</div>
</section>
</>
)
};
export default TagSelect

View File

@ -0,0 +1,81 @@
import React, { useState } from "react";
import { useDispatch, useSelector } from "react-redux";
import Select from "react-select";
import {
profiles,
selectItems,
selectTags,
selectedItems,
setPositionId,
} from "@redux/outstaffingSlice";
import { apiRequest } from "@api/request";
import { Loader } from "@components/Common/Loader/Loader";
import "./tagSelect.scss";
const TagSelect = () => {
const [searchLoading, setSearchLoading] = useState(false);
const dispatch = useDispatch();
const itemsArr = useSelector(selectItems);
const tagsArr = useSelector(selectTags);
const handleSubmit = ({ dispatch, setSearchLoading }) => {
setSearchLoading(true);
dispatch(setPositionId(null));
const filterItemsId = itemsArr.map((item) => item.id).join();
const params = filterItemsId ? { skill: filterItemsId } : "";
apiRequest("/profile", {
params: { ...params, limit: 1000 },
}).then((res) => {
dispatch(profiles(res));
setSearchLoading(false);
});
};
return (
<>
<section className="search">
<div className="row">
<div className="col-12">
<h2 className="search__title">Найти специалиста по навыкам</h2>
<div className="search__box">
<Select
value={itemsArr}
onChange={(value) => dispatch(selectedItems(value))}
isMulti
name="tags"
className="select"
classNamePrefix="select"
options={
tagsArr &&
tagsArr.flat().map((item) => {
return {
id: item.id,
value: item.value,
label: item.value,
};
})
}
/>
<button
onClick={() => handleSubmit({ dispatch, setSearchLoading })}
type="submit"
className="search__submit"
>
{searchLoading ? <Loader width={30} height={30} /> : "Поиск"}
</button>
</div>
</div>
</div>
</section>
</>
);
};
export default TagSelect;

View File

@ -0,0 +1,81 @@
.search {
margin-top: 73px;
&__title {
font-family: "GT Eesti Pro Display", sans-serif;
font-size: 2.4em;
font-weight: 500;
font-style: normal;
letter-spacing: normal;
line-height: normal;
text-align: center;
margin-bottom: 40px;
}
&__box {
display: flex;
justify-content: space-between;
button {
color: white;
width: 131px;
height: 40px;
border-radius: 10px;
border: none;
font-family: "Muller", sans-serif;
font-size: 1.6em;
letter-spacing: 0.8px;
text-align: center;
background-color: #73c141;
&:hover {
background: rgba(0, 0, 0, 0);
color: #73c141;
box-shadow: inset 0 0 0 3px #73c141;
}
@media (max-width: 575.98px) {
margin-top: 20px;
}
}
@media (max-width: 575.98px) {
flex-direction: column;
align-items: center;
}
}
&__submit {
font-weight: bold;
}
.select {
width: 85%;
@media (max-width: 990px) {
margin-right: 1rem;
}
}
}
[class$="-placeholder"] {
display: none;
}
[class$="-control"] {
border-color: #e8e8e8 !important;
box-shadow: 0 0 0 0 #e8e8e8 !important;
}
[class$="-value__label"] {
font-size: 1.4em !important;
}
[class$="-option"] {
font-size: 1.6em !important;
color: gray !important;
}
.search__submit:hover .loader path {
fill: #6aaf5c;
}

View File

@ -1,23 +1,25 @@
import React, { useEffect, useState } from "react";
import React, { useState } from "react";
import { useDispatch, useSelector } from "react-redux";
import { Link } from "react-router-dom";
import { apiRequest } from "../../api/request";
import {
getCorrectDate,
getCreatedDate,
hourOfNum,
} from "../../components/Calendar/calendarHelper";
import {
getReportDate,
getSendRequest,
setSendRequest,
} from "../../redux/reportSlice";
import { useDispatch, useSelector } from "react-redux";
import { Link } from "react-router-dom";
import { Loader } from "../Loader/Loader";
} from "@redux/reportSlice";
import { apiRequest } from "@api/request";
import {
getCorrectDate,
getCreatedDate,
hourOfNum,
} from "@components/Calendar/calendarHelper";
import { Loader } from "@components/Common/Loader/Loader";
import "./shortReport.scss";
export const ShortReport = ({}) => {
export const ShortReport = () => {
const reportDate = useSelector(getReportDate);
const sendReport = useSelector(getSendRequest);

View File

@ -1,8 +1,8 @@
import React, { useState } from "react";
import { Link } from "react-router-dom";
import arrow from "../../images/sideBarArrow.svg";
import LogoITguild from "../../images/LogoITguild.svg";
import arrow from "assets/icons/sideBarArrow.svg";
import LogoITguild from "assets/images/logo/LogoITguild.svg";
import "./sidebar.scss";

View File

@ -1,9 +1,10 @@
import React from 'react'
import './skillSection.scss'
import React from "react";
import "./skillSection.scss";
const SkillSection = ({ skillsArr }) => {
return (
<div className='skill-section'>
<div className="skill-section">
<h3>Навыки:</h3>
<ul>
{skillsArr &&
@ -12,7 +13,7 @@ const SkillSection = ({ skillsArr }) => {
))}
</ul>
</div>
)
}
);
};
export default SkillSection
export default SkillSection;

View File

@ -1,12 +1,14 @@
import React, { useState } from "react";
import { Link } from "react-router-dom";
import Slider from "react-slick";
import "slick-carousel/slick/slick-theme.css";
import "slick-carousel/slick/slick.css";
import mockWorker from "../../images/mokPerson.png";
import BaseButton from "@components/Common/BaseButton/BaseButton";
import mockWorker from "assets/images/mock/mokPerson.png";
import "./sliderWorkers.scss";
import "slick-carousel/slick/slick.css";
import "slick-carousel/slick/slick-theme.css";
import { Link } from "react-router-dom";
export const SliderWorkers = ({ title, titleInfo, subTitle }) => {
const [workers] = useState([
@ -65,9 +67,9 @@ export const SliderWorkers = ({ title, titleInfo, subTitle }) => {
<img src={worker.avatar}></img>
<div className="worker-description">
<p>{worker.skils}</p>
<Link to={`/worker/${index}`} className="worker__resume">
Подробное резюме
</Link>
<BaseButton styles="worker__resume">
<Link to={`/worker/${index}`}>Подробное резюме</Link>
</BaseButton>
</div>
</div>
);

View File

@ -59,24 +59,16 @@
}
&__resume {
display: flex;
align-items: center;
justify-content: center;
margin-top: 5px;
width: 177px;
height: 40px;
background: #52b709;
border-radius: 44px;
font-size: 14px;
line-height: 32px;
color: white;
border: none;
transition: 0.5s;
&:hover {
transition: 0.5s;
background-color: #52b709a8;
text-decoration: none;
a {
color: white;
width: 100%;
}
}
}
@ -91,7 +83,7 @@
border-radius: 44px;
&:before {
content: url("../../images/arrowViewReport.svg");
content: url("../../assets/icons/arrows/arrowViewReport.svg");
}
&:focus {
@ -113,7 +105,7 @@
border-radius: 44px;
&:before {
content: url("../../images/ArrovLeftSlider.png");
content: url("../../assets/icons/arrows/ArrovLeftSlider.png");
}
&:focus {

View File

@ -1,44 +1,86 @@
import React, { useEffect, useState } from 'react'
import './StarRating.scss'
const StarRating = ({ countStars=1, countActiveStars=1, color='#52B709', size=61 }) => {
const [shadedStars, setShadedStars] = useState([])
const [noShadedStars, setNoShadedStars] = useState([])
const percent = (Math.abs(countActiveStars)) >= countStars ? 100 : (countActiveStars * 100 / countStars)
useEffect(() => {
for (let index = 0; index < countStars; index++) {
setShadedStars(prev => [...prev, '★'])
setNoShadedStars(prev => [...prev, '☆'])
}
}, [])
const ratingStyle = {
"--size": size+'px'
}
const activeStyle = {
"--width": percent + '%',
"--color": color,
"--content": shadedStars.join('')
}
const bodyStyle = {
"--content": noShadedStars.join(''),
"--color": color
}
return (
<div className='rating' style={ratingStyle}>
<div className="rating__body" style={bodyStyle} data-content={noShadedStars.join('')}>
<div className="rating__active" style={activeStyle} data-content={shadedStars.join('')}></div>
<div className="rating__items">
<input type='radio' className="rating__item" value={1} name='star'></input>
<input type='radio' className="rating__item" value={2} name='star'></input>
<input type='radio' className="rating__item" value={3} name='star'></input>
<input type='radio' className="rating__item" value={4} name='star'></input>
<input type='radio' className="rating__item" value={5} name='star'></input>
</div>
</div>
</div>
)
}
export default React.memo(StarRating);
import React, { useEffect, useState } from "react";
import "./StarRating.scss";
const StarRating = ({
countStars = 1,
countActiveStars = 1,
color = "#52B709",
size = 61,
}) => {
const [shadedStars, setShadedStars] = useState([]);
const [noShadedStars, setNoShadedStars] = useState([]);
const percent =
Math.abs(countActiveStars) >= countStars
? 100
: (countActiveStars * 100) / countStars;
useEffect(() => {
for (let index = 0; index < countStars; index++) {
setShadedStars((prev) => [...prev, "★"]);
setNoShadedStars((prev) => [...prev, "☆"]);
}
}, []);
const ratingStyle = {
"--size": size + "px",
};
const activeStyle = {
"--width": percent + "%",
"--color": color,
"--content": shadedStars.join(""),
};
const bodyStyle = {
"--content": noShadedStars.join(""),
"--color": color,
};
return (
<div className="rating" style={ratingStyle}>
<div
className="rating__body"
style={bodyStyle}
data-content={noShadedStars.join("")}
>
<div
className="rating__active"
style={activeStyle}
data-content={shadedStars.join("")}
></div>
<div className="rating__items">
<input
type="radio"
className="rating__item"
value={1}
name="star"
></input>
<input
type="radio"
className="rating__item"
value={2}
name="star"
></input>
<input
type="radio"
className="rating__item"
value={3}
name="star"
></input>
<input
type="radio"
className="rating__item"
value={4}
name="star"
></input>
<input
type="radio"
className="rating__item"
value={5}
name="star"
></input>
</div>
</div>
</div>
);
};
export default React.memo(StarRating);

View File

@ -1,45 +1,44 @@
.rating{
display: flex;
align-items: center;
font-size: var(--size);
line-height: 0.75;
&__body{
position: relative;
&::before{
content: attr(data-content);
display: block;
color: var(--color);
}
}
&__active{
position: absolute;
height: 100%;
width: var(--width);
top: 0;
left: 0;
overflow: hidden;
&::before{
content: attr(data-content);
position: absolute;
left: 0;
top: 0;
width: 100%;
height: 100%;
color: var(--color);
}
}
&__items{
display: flex;
position: absolute;
width: 100%;
height: 100%;
left: 0;
top: 0;
}
&__item{
flex: 0 0 20%;
height: 100%;
opacity: 0;
}
.rating {
display: flex;
align-items: center;
font-size: var(--size);
line-height: 0.75;
&__body {
position: relative;
&::before {
content: attr(data-content);
display: block;
color: var(--color);
}
}
&__active {
position: absolute;
height: 100%;
width: var(--width);
top: 0;
left: 0;
overflow: hidden;
&::before {
content: attr(data-content);
position: absolute;
left: 0;
top: 0;
width: 100%;
height: 100%;
color: var(--color);
}
}
&__items {
display: flex;
position: absolute;
width: 100%;
height: 100%;
left: 0;
top: 0;
}
&__item {
flex: 0 0 20%;
height: 100%;
opacity: 0;
}
}

View File

@ -1,20 +0,0 @@
import React from 'react';
import './stepForCandidate.scss'
export const StepsForCandidate = ({step}) => {
return(
<div className='step'>
<div className='step__start'>
<span>2</span>
<p>шага для твоего входа в команду </p>
</div>
<div className='step__info'>
<p>{step}</p>
<span>из 2</span>
</div>
</div>
)
};
export default StepsForCandidate

View File

@ -0,0 +1,20 @@
import React from "react";
import "./stepForCandidate.scss";
export const StepsForCandidate = ({ step }) => {
return (
<div className="step">
<div className="step__start">
<span>2</span>
<p>шага для твоего входа в команду </p>
</div>
<div className="step__info">
<p>{step}</p>
<span>из 2</span>
</div>
</div>
);
};
export default StepsForCandidate;

View File

@ -4,7 +4,7 @@
align-items: center;
justify-content: space-between;
top: -100px;
padding:0 55px 0 85px;
padding: 0 55px 0 85px;
width: 100%;
@media (max-width: 965px) {
@ -20,7 +20,7 @@
font-weight: 900;
font-size: 258px;
line-height: 32px;
color: #52B709;
color: #52b709;
}
p {
@ -41,7 +41,7 @@
display: flex;
align-items: center;
p {
background: #DDEEC6;
background: #ddeec6;
border-radius: 44px;
padding: 8px 26px;
font-weight: 400;

View File

@ -1,16 +0,0 @@
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,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

@ -6,7 +6,7 @@
&__index {
margin-top: 0.2rem;
color: #282828;
font-family: 'GT Eesti Pro Display';
font-family: "GT Eesti Pro Display";
font-size: 20px;
font-weight: 700;
line-height: 48.74px;
@ -19,7 +19,7 @@
max-width: 525px;
margin-left: 1.6rem;
color: #000000;
font-family: 'GT Eesti Pro Display';
font-family: "GT Eesti Pro Display";
font-size: 15px;
font-weight: 400;
letter-spacing: normal;
@ -49,7 +49,7 @@
);
color: #ffffff;
font-family: 'Muller Extra Bold';
font-family: "Muller Extra Bold";
font-size: 16px;
font-weight: 400;
text-align: left;
@ -61,7 +61,7 @@
width: 69px;
height: 25px;
color: #000000;
font-family: 'GT Eesti Pro Display - Thin';
font-family: "GT Eesti Pro Display - Thin";
font-size: 13px;
font-weight: 400;
letter-spacing: normal;

Some files were not shown because too many files have changed in this diff Show More