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:
@ -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>
|
||||
)
|
||||
}
|
19
src/components/Achievement/Achievement.jsx
Normal file
19
src/components/Achievement/Achievement.jsx
Normal 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>
|
||||
);
|
||||
};
|
@ -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">
|
@ -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
|
90
src/components/Calendar/Calendar.jsx
Normal file
90
src/components/Calendar/Calendar.jsx
Normal 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;
|
@ -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
|
100
src/components/Calendar/CalendarComponent.jsx
Normal file
100
src/components/Calendar/CalendarComponent.jsx
Normal 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;
|
@ -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;
|
||||
|
@ -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;
|
||||
|
@ -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") {
|
@ -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;
|
||||
|
@ -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">
|
@ -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 }) => {
|
@ -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>)
|
||||
}
|
23
src/components/CardControl/CardControl.jsx
Normal file
23
src/components/CardControl/CardControl.jsx
Normal 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>
|
||||
);
|
||||
};
|
@ -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;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
@ -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
|
30
src/components/CategoriesItem/CategoriesItem.jsx
Normal file
30
src/components/CategoriesItem/CategoriesItem.jsx
Normal 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;
|
@ -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;
|
||||
}
|
||||
}
|
||||
|
@ -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">
|
@ -53,7 +53,7 @@
|
||||
}
|
||||
|
||||
.candidate {
|
||||
color: #1458DD;
|
||||
color: #1458dd;
|
||||
}
|
||||
}
|
||||
}
|
16
src/components/Common/BaseButton/BaseButton.jsx
Normal file
16
src/components/Common/BaseButton/BaseButton.jsx
Normal 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;
|
18
src/components/Common/BaseButton/basebutton.module.scss
Normal file
18
src/components/Common/BaseButton/basebutton.module.scss
Normal 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;
|
||||
}
|
||||
}
|
53
src/components/Common/Footer/Footer.jsx
Normal file
53
src/components/Common/Footer/Footer.jsx
Normal 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>
|
||||
);
|
||||
};
|
121
src/components/Common/Footer/footer.scss
Normal file
121
src/components/Common/Footer/footer.scss
Normal 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;
|
||||
}
|
||||
}
|
||||
}
|
17
src/components/Common/Loader/Loader.jsx
Normal file
17
src/components/Common/Loader/Loader.jsx
Normal 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>
|
||||
);
|
||||
};
|
@ -5,6 +5,7 @@
|
||||
justify-content: center;
|
||||
align-items: center;
|
||||
position: relative;
|
||||
|
||||
&:hover {
|
||||
path {
|
||||
fill: #6aaf5c;
|
30
src/components/Common/ModalLayout/ModalLayout.jsx
Normal file
30
src/components/Common/ModalLayout/ModalLayout.jsx
Normal 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;
|
28
src/components/Common/ModalLayout/modalLayout.scss
Normal file
28
src/components/Common/ModalLayout/modalLayout.scss
Normal 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);
|
||||
}
|
@ -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 (
|
@ -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>
|
||||
)
|
||||
}
|
@ -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;
|
||||
// }
|
||||
// }
|
||||
//}
|
@ -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
|
128
src/components/Form/Form.jsx
Normal file
128
src/components/Form/Form.jsx
Normal 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;
|
@ -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>
|
@ -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;
|
||||
}
|
@ -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>
|
||||
);
|
||||
};
|
||||
|
@ -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;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
@ -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>
|
||||
)
|
||||
};
|
16
src/components/Header/Header.jsx
Normal file
16
src/components/Header/Header.jsx
Normal 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>
|
||||
);
|
||||
};
|
@ -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;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
@ -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>
|
||||
)
|
||||
};
|
@ -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>
|
||||
)
|
||||
};
|
33
src/components/LogoutButton/LogoutButton.jsx
Normal file
33
src/components/LogoutButton/LogoutButton.jsx
Normal 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>
|
||||
);
|
||||
};
|
@ -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 {
|
||||
|
98
src/components/Modal/ModalAspirant/ModalAspirant.jsx
Normal file
98
src/components/Modal/ModalAspirant/ModalAspirant.jsx
Normal 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;
|
170
src/components/Modal/ModalAspirant/modalAspirant.scss
Normal file
170
src/components/Modal/ModalAspirant/modalAspirant.scss
Normal 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;
|
||||
}
|
||||
}
|
||||
}
|
27
src/components/Modal/ModalErrorLogin/ModalErrorLogin.jsx
Normal file
27
src/components/Modal/ModalErrorLogin/ModalErrorLogin.jsx
Normal 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;
|
31
src/components/Modal/ModalErrorLogin/modalErrorLogin.scss
Normal file
31
src/components/Modal/ModalErrorLogin/modalErrorLogin.scss
Normal 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;
|
||||
}
|
||||
}
|
79
src/components/Modal/ModalRegistration/ModalRegistration.jsx
Normal file
79
src/components/Modal/ModalRegistration/ModalRegistration.jsx
Normal 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;
|
128
src/components/Modal/ModalRegistration/modalRegistration.scss
Normal file
128
src/components/Modal/ModalRegistration/modalRegistration.scss
Normal 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;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
489
src/components/Modal/Tracker/ModalTicket/ModalTicket.jsx
Normal file
489
src/components/Modal/Tracker/ModalTicket/ModalTicket.jsx
Normal 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;
|
@ -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 {
|
@ -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;
|
@ -11,10 +11,6 @@
|
||||
}
|
||||
}
|
||||
|
||||
// .modal-tiket__content .content {
|
||||
// width: 70%;
|
||||
// }
|
||||
|
||||
.content-tabs {
|
||||
padding-bottom: 0;
|
||||
}
|
@ -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>
|
@ -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>
|
||||
)
|
||||
}
|
110
src/components/Navigation/Navigation.jsx
Normal file
110
src/components/Navigation/Navigation.jsx
Normal 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>
|
||||
);
|
||||
};
|
@ -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
|
89
src/components/Outstaffing/Outstaffing.jsx
Normal file
89
src/components/Outstaffing/Outstaffing.jsx
Normal 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 |
@ -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
|
123
src/components/OutstaffingBlock/OutstaffingBlock.jsx
Normal file
123
src/components/OutstaffingBlock/OutstaffingBlock.jsx
Normal 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;
|
@ -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;
|
||||
|
@ -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>
|
||||
)
|
||||
}
|
18
src/components/ProfileBreadcrumbs/ProfileBreadcrumbs.jsx
Normal file
18
src/components/ProfileBreadcrumbs/ProfileBreadcrumbs.jsx
Normal 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>
|
||||
);
|
||||
};
|
@ -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;
|
||||
}
|
||||
|
@ -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 = () => {
|
@ -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();
|
@ -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>
|
||||
)
|
||||
};
|
59
src/components/ProfileHeader/ProfileHeader.jsx
Normal file
59
src/components/ProfileHeader/ProfileHeader.jsx
Normal 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>
|
||||
);
|
||||
};
|
@ -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 {
|
||||
|
@ -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));
|
||||
});
|
||||
}
|
||||
|
@ -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' />
|
||||
}
|
||||
/>
|
||||
);
|
||||
};
|
||||
|
@ -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
|
326
src/components/ReportForm/ReportForm.jsx
Normal file
326
src/components/ReportForm/ReportForm.jsx
Normal 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;
|
@ -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;
|
||||
|
@ -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;
|
||||
}
|
||||
}
|
@ -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
|
81
src/components/Select/TagSelect.jsx
Normal file
81
src/components/Select/TagSelect.jsx
Normal 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;
|
81
src/components/Select/tagSelect.scss
Normal file
81
src/components/Select/tagSelect.scss
Normal 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;
|
||||
}
|
@ -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);
|
||||
|
@ -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";
|
||||
|
||||
|
@ -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;
|
@ -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>
|
||||
);
|
||||
|
@ -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 {
|
||||
|
@ -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);
|
||||
|
@ -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;
|
||||
}
|
||||
}
|
||||
|
||||
|
@ -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
|
20
src/components/StepsForCandidate/StepsForCandidate.jsx
Normal file
20
src/components/StepsForCandidate/StepsForCandidate.jsx
Normal 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;
|
@ -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;
|
||||
|
@ -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>
|
||||
)
|
||||
}
|
16
src/components/TaskItem/TaskItem.jsx
Normal file
16
src/components/TaskItem/TaskItem.jsx
Normal 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>
|
||||
);
|
||||
};
|
@ -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
Reference in New Issue
Block a user