Fixed conflicts
4
.prettierrc
Normal file
@ -0,0 +1,4 @@
|
||||
{
|
||||
"tabWidth": 2,
|
||||
"useTabs": false
|
||||
}
|
41
src/App.js
@ -15,6 +15,7 @@ import ReportForm from "./components/ReportForm/ReportForm";
|
||||
import FreeDevelopers from "./components/UI/FreeDevelopers/FreeDevelopers";
|
||||
import { TicketFullScreen } from "./components/UI/TicketFullScreen/TicketFullScreen";
|
||||
import { ProfileCalendar } from "./components/ProfileCalendar/ProfileCalendar";
|
||||
import Article from "./pages/Article/Article";
|
||||
import FormPage from "./pages/FormPage/FormPage.js";
|
||||
import SingleReportPage from "./pages/SingleReportPage/SingleReportPage";
|
||||
import { QuizPage } from "./pages/quiz/QuizPage";
|
||||
@ -35,13 +36,14 @@ import { AuthForCandidate } from "./pages/AuthForCandidate/AuthForCandidate";
|
||||
import { RegistrationForCandidate } from "./pages/RegistrationForCandidate/RegistrationForCandidate";
|
||||
import { ProfileCandidate } from "./pages/ProfileCandidate/ProfileCandidate";
|
||||
import { PassingTests } from "./pages/quiz/PassingTests";
|
||||
import "./assets/global.scss";
|
||||
import "./fonts/stylesheet.css";
|
||||
import "bootstrap/dist/css/bootstrap.min.css";
|
||||
import { FREQUENTLY_ASKED_QUESTIONS_ROUTE, FREQUENTLY_ASKED_QUESTION_ROUTE } from "./constants/router-path";
|
||||
import Blog from "./pages/Blog/Blog";
|
||||
import { ProjectTracker } from "./pages/ProjectTracker/ProjectTracker";
|
||||
import { FrequentlyAskedQuestions } from "./pages/FrequentlyAskedQuestions/FrequentlyAskedQuestions";
|
||||
import { FrequentlyAskedQuestion } from "./pages/FrequentlyAskedQuestion/FrequentlyAskedQuestion";
|
||||
|
||||
import "./assets/global.scss";
|
||||
import "./fonts/stylesheet.css";
|
||||
import "bootstrap/dist/css/bootstrap.min.css";
|
||||
|
||||
const App = () => {
|
||||
return (
|
||||
@ -49,16 +51,19 @@ const App = () => {
|
||||
<Router>
|
||||
<Routes>
|
||||
<Route exact path="/authdev" element={<AuthForDevelopers />} />
|
||||
<Route exact path="/auth" element={<AuthForPartners />} />
|
||||
<Route exact path={FREQUENTLY_ASKED_QUESTIONS_ROUTE} element={<FrequentlyAskedQuestions />}/>
|
||||
<Route exact path={FREQUENTLY_ASKED_QUESTION_ROUTE+'/:id'} element={<FrequentlyAskedQuestion />} />
|
||||
|
||||
<Route exact path="/auth" element={<AuthForPartners />} />
|
||||
|
||||
<Route exact path="/worker/:id" element={<FreeDevelopers />} />
|
||||
<Route
|
||||
exact
|
||||
path="/tracker/:id"
|
||||
path="/tracker/task/:id"
|
||||
element={<TicketFullScreen />}
|
||||
></Route>
|
||||
<Route
|
||||
exact
|
||||
path="/tracker/project/:id"
|
||||
element={<ProjectTracker />}
|
||||
/>
|
||||
<Route exact path="/auth-candidate" element={<AuthForCandidate />} />
|
||||
<Route
|
||||
exact
|
||||
@ -66,6 +71,19 @@ const App = () => {
|
||||
element={<RegistrationForCandidate />}
|
||||
/>
|
||||
|
||||
<Route exact path="/blog" element={<Blog />}></Route>
|
||||
<Route exact path="/blog/article/:id" element={<Article />}></Route>
|
||||
<Route
|
||||
exact
|
||||
path="/frequently-asked-questions"
|
||||
element={<FrequentlyAskedQuestions />}
|
||||
/>
|
||||
<Route
|
||||
exact
|
||||
path="/frequently-asked-question/:id"
|
||||
element={<FrequentlyAskedQuestion />}
|
||||
/>
|
||||
|
||||
<Route exact path="/candidate/:id" element={<Candidate />} />
|
||||
<Route exact path="/candidate/:id/form" element={<FormPage />} />
|
||||
<Route path="/:userId/calendar" element={<Calendar />} />
|
||||
@ -83,8 +101,9 @@ const App = () => {
|
||||
<Route index element={<Profile />} />
|
||||
<Route exact path="catalog" element={<Home />} />
|
||||
<Route exact path="calendar" element={<ProfileCalendar />} />
|
||||
<Route exact path="calendar/view/" element={<ProfileCalendar />} />
|
||||
<Route exact path="summary" element={<Summary />} />
|
||||
<Route exact path="view" element={<ViewReport />} />
|
||||
<Route exact path="view/:id" element={<ViewReport />} />
|
||||
<Route exact path="tracker" element={<Tracker />} />
|
||||
<Route exact path="payouts" element={<Payouts />} />
|
||||
<Route exact path="settings" element={<PartnerSettings />} />
|
||||
@ -104,7 +123,7 @@ const App = () => {
|
||||
<Route index element={<ProfileCandidate />} />
|
||||
</Route>
|
||||
|
||||
{/* <Route path="*" element={<Navigate to="/auth" replace />} /> */}
|
||||
<Route path="*" element={<Navigate to="/auth" replace />} />
|
||||
</Routes>
|
||||
</Router>
|
||||
</>
|
||||
|
@ -7,7 +7,7 @@
|
||||
padding-right: 54px;
|
||||
padding-top: 48px;
|
||||
padding-bottom: 40px;
|
||||
font-family: 'LabGrotesque', sans-serif;
|
||||
font-family: "LabGrotesque", sans-serif;
|
||||
|
||||
&__header {
|
||||
display: flex;
|
||||
@ -56,7 +56,6 @@
|
||||
}
|
||||
|
||||
h3 {
|
||||
|
||||
font-size: 2.5em;
|
||||
font-weight: 400;
|
||||
font-style: normal;
|
||||
@ -133,6 +132,8 @@
|
||||
text-align: center;
|
||||
}
|
||||
}
|
||||
|
||||
margin-bottom: 60px;
|
||||
}
|
||||
|
||||
&__form {
|
||||
@ -142,8 +143,8 @@
|
||||
|
||||
button {
|
||||
margin: 0 auto;
|
||||
//width: 125px;
|
||||
//height: 42px;
|
||||
width: 125px;
|
||||
height: 42px;
|
||||
padding: 0 5px;
|
||||
box-shadow: 0 0 59px rgba(44, 44, 44, 0.05);
|
||||
border-radius: 5px;
|
||||
@ -158,39 +159,34 @@
|
||||
text-align: center;
|
||||
|
||||
a {
|
||||
display: flex;
|
||||
align-items: center;
|
||||
justify-content: center;
|
||||
width: 115px;
|
||||
height: 42px;
|
||||
text-decoration: none;
|
||||
color: #000000;
|
||||
color: black;
|
||||
}
|
||||
|
||||
img {
|
||||
width: 16px;
|
||||
height: 16px;
|
||||
margin: 0 10px 0 0;
|
||||
}
|
||||
|
||||
@media (max-width: 1200px) {
|
||||
width: 90px;
|
||||
height: 40px;
|
||||
}
|
||||
img {
|
||||
width: 16px;
|
||||
height: 16px;
|
||||
margin: 0 10px 0 0;
|
||||
|
||||
@media (max-width: 968px) {
|
||||
width: 62px;
|
||||
height: 40px;
|
||||
font-size: 10px;
|
||||
margin-right: 2px;
|
||||
|
||||
img {
|
||||
margin-right: 2px;
|
||||
}
|
||||
}
|
||||
|
||||
@media (max-width: 610px) {
|
||||
img {
|
||||
@media (max-width: 610px) {
|
||||
display: none;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@media (max-width: 1200px) {
|
||||
width: 90px;
|
||||
height: 40px;
|
||||
}
|
||||
|
||||
@media (max-width: 968px) {
|
||||
width: 62px;
|
||||
height: 40px;
|
||||
font-size: 10px;
|
||||
|
||||
@media (max-width: 610px) {
|
||||
width: auto;
|
||||
height: auto;
|
||||
}
|
||||
|
@ -1,19 +1,23 @@
|
||||
import moment from 'moment';
|
||||
import 'moment/locale/ru';
|
||||
import moment from "moment";
|
||||
import "moment/locale/ru";
|
||||
|
||||
export function calendarHelper(value) {
|
||||
const startDay = value.clone().startOf('month').startOf('week').startOf('day');
|
||||
const endDay = value.clone().endOf('month').endOf('week');
|
||||
const startDay = value
|
||||
.clone()
|
||||
.startOf("month")
|
||||
.startOf("week")
|
||||
.startOf("day");
|
||||
const endDay = value.clone().endOf("month").endOf("week");
|
||||
|
||||
const day = startDay.clone().subtract(1, 'day');
|
||||
const day = startDay.clone().subtract(1, "day");
|
||||
|
||||
const calendar = [];
|
||||
|
||||
while (day.isBefore(endDay, 'day')) {
|
||||
while (day.isBefore(endDay, "day")) {
|
||||
calendar.push(
|
||||
Array(1)
|
||||
.fill(0)
|
||||
.map(() => day.add(1, 'day').clone())
|
||||
.map(() => day.add(1, "day").clone())
|
||||
);
|
||||
}
|
||||
|
||||
@ -21,48 +25,84 @@ export function calendarHelper(value) {
|
||||
}
|
||||
|
||||
export function getReports(value) {
|
||||
const startDay = value.clone().startOf('month').startOf('week').startOf('day');
|
||||
const reportsStart = `${new Date(startDay).getFullYear()}-${new Date(startDay).getMonth() + 1}-${new Date(startDay).getDate()}`
|
||||
const endDay = value.clone().endOf('month').endOf('week');
|
||||
const reportsEnd = `${new Date(endDay).getFullYear()}-${new Date(endDay).getMonth() + 1}-${new Date(endDay).getDate()}`
|
||||
const getReports = `fromDate=${reportsStart}&toDate=${reportsEnd}`
|
||||
const startDay = value
|
||||
.clone()
|
||||
.startOf("month")
|
||||
.startOf("week")
|
||||
.startOf("day");
|
||||
const reportsStart = `${new Date(startDay).getFullYear()}-${
|
||||
new Date(startDay).getMonth() + 1
|
||||
}-${new Date(startDay).getDate()}`;
|
||||
const endDay = value.clone().endOf("month").endOf("week");
|
||||
const reportsEnd = `${new Date(endDay).getFullYear()}-${
|
||||
new Date(endDay).getMonth() + 1
|
||||
}-${new Date(endDay).getDate()}`;
|
||||
const getReports = `fromDate=${reportsStart}&toDate=${reportsEnd}`;
|
||||
|
||||
return getReports;
|
||||
}
|
||||
|
||||
export function getCreatedDate(day) {
|
||||
if (day) {
|
||||
return `${new Date(day).getFullYear()}-${new Date(day).getMonth() + 1}-${new Date(day).getDate()}`
|
||||
return `${new Date(day).getFullYear()}-${correctDay(
|
||||
new Date(day).getMonth() + 1
|
||||
)}-${correctDay(new Date(day).getDate())}`;
|
||||
} else {
|
||||
const date = new Date();
|
||||
const dd = String(date.getDate()).padStart(2, '0');
|
||||
const mm = String(date.getMonth() + 1).padStart(2, '0');
|
||||
const dd = String(date.getDate()).padStart(2, "0");
|
||||
const mm = String(date.getMonth() + 1).padStart(2, "0");
|
||||
const yyyy = date.getFullYear();
|
||||
return `${yyyy}-${mm}-${dd}`
|
||||
return `${yyyy}-${mm}-${dd}`;
|
||||
}
|
||||
}
|
||||
|
||||
export function correctDay(day) {
|
||||
if (day < 10) {
|
||||
return `0${day}`;
|
||||
}
|
||||
return day;
|
||||
}
|
||||
|
||||
export function currentMonth() {
|
||||
const currentMonth = moment().format('MMMM');
|
||||
const currentMonth = moment().format("MMMM");
|
||||
|
||||
return currentMonth.charAt(0).toUpperCase() + currentMonth.slice(1);
|
||||
}
|
||||
|
||||
export function currentMonthAndDay(day) {
|
||||
return day.format('D MMMM');
|
||||
return day.format("D MMMM");
|
||||
}
|
||||
|
||||
export function getCorrectDate(day) {
|
||||
const months = ['января', 'февраля', 'марта', 'апреля', 'мая', 'июня', 'июля', 'августа', 'сентября', 'октября', 'ноября', 'декабря']
|
||||
return `${new Date(day).getDate()} ${months[new Date(day).getMonth()]} ${new Date(day).getFullYear()} года`
|
||||
};
|
||||
const months = [
|
||||
"января",
|
||||
"февраля",
|
||||
"марта",
|
||||
"апреля",
|
||||
"мая",
|
||||
"июня",
|
||||
"июля",
|
||||
"августа",
|
||||
"сентября",
|
||||
"октября",
|
||||
"ноября",
|
||||
"декабря",
|
||||
];
|
||||
return `${new Date(day).getDate()} ${
|
||||
months[new Date(day).getMonth()]
|
||||
} ${new Date(day).getFullYear()} года`;
|
||||
}
|
||||
|
||||
export function currentMonthAndDayReportPage() {
|
||||
return moment().format('D MMMM');
|
||||
return moment().format("D MMMM");
|
||||
}
|
||||
|
||||
export function hourOfNum(number) {
|
||||
const hours = [' час', ' часа', ' часов'];
|
||||
const hours = [" час", " часа", " часов"];
|
||||
const cases = [2, 0, 1, 1, 1, 2];
|
||||
return hours[(number % 100 > 4 && number % 100 < 20) ? 2 : cases[(number % 10 < 5) ? number % 10 : 5]];
|
||||
return hours[
|
||||
number % 100 > 4 && number % 100 < 20
|
||||
? 2
|
||||
: cases[number % 10 < 5 ? number % 10 : 5]
|
||||
];
|
||||
}
|
||||
|
@ -3,10 +3,10 @@ import './loader.scss'
|
||||
import React from "react";
|
||||
|
||||
|
||||
export const Loader = ({width = 50, height = 50}) => {
|
||||
export const Loader = ({width = 50, height = 50, style}) => {
|
||||
return (
|
||||
<div className='loader'>
|
||||
<SVGLoader type='Circles' color='#fff' height={height} width={width}/>
|
||||
<SVGLoader type='Circles' color={style ? style : `#fff`} height={height} width={width}/>
|
||||
</div>
|
||||
)
|
||||
};
|
||||
|
@ -1,99 +1,127 @@
|
||||
import React, { useEffect, useState } from 'react'
|
||||
import {useDispatch, useSelector} from 'react-redux'
|
||||
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 { 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 { 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 { urlForLocal } from "../../helper";
|
||||
|
||||
import {apiRequest} from "../../api/request";
|
||||
import { getProfileInfo } from '../../redux/outstaffingSlice'
|
||||
import {getRequestDates, setReportDate, setRequestDate} from "../../redux/reportSlice";
|
||||
|
||||
import 'moment/locale/ru'
|
||||
import './profileCalendar.scss'
|
||||
import { Navigation } from '../Navigation/Navigation';
|
||||
import { apiRequest } from "../../api/request";
|
||||
import { getProfileInfo } from "../../redux/outstaffingSlice";
|
||||
import {
|
||||
getRequestDates,
|
||||
setReportDate,
|
||||
setRequestDate,
|
||||
} from "../../redux/reportSlice";
|
||||
|
||||
import "moment/locale/ru";
|
||||
import "./profileCalendar.scss";
|
||||
|
||||
export const ProfileCalendar = () => {
|
||||
if(localStorage.getItem('role_status') === '18') {
|
||||
return <Navigate to="/profile" replace/>
|
||||
if (localStorage.getItem("role_status") === "18") {
|
||||
return <Navigate to="/profile" replace />;
|
||||
}
|
||||
const dispatch = useDispatch();
|
||||
const profileInfo = useSelector(getProfileInfo);
|
||||
const requestDates = useSelector(getRequestDates);
|
||||
const [value, setValue] = useState(moment());
|
||||
const [reports, setReports] = useState([]);
|
||||
const [totalHours, setTotalHours] = useState(0);
|
||||
const [loader, setLoader] = useState(true);
|
||||
|
||||
function setValueHandler(value) {
|
||||
setValue(value);
|
||||
}
|
||||
|
||||
useEffect(() => {
|
||||
dispatch(setRequestDate(getReports(moment())));
|
||||
}, []);
|
||||
|
||||
useEffect(() => {
|
||||
setLoader(true);
|
||||
if (!requestDates) {
|
||||
return;
|
||||
}
|
||||
const dispatch = useDispatch();
|
||||
const profileInfo = useSelector(getProfileInfo)
|
||||
const requestDates = useSelector(getRequestDates)
|
||||
const [value, setValue] = useState(moment())
|
||||
const [reports, setReports] = useState([]);
|
||||
const [totalHours, setTotalHours] = useState(0);
|
||||
const [loader, setLoader] = useState(true)
|
||||
apiRequest(
|
||||
`/reports/reports-by-date?${requestDates}&user_card_id=${localStorage.getItem(
|
||||
"cardId"
|
||||
)}`
|
||||
).then((reports) => {
|
||||
let spendTime = 0;
|
||||
for (const report of reports) {
|
||||
report.task.map((task) => {
|
||||
if (task.hours_spent) {
|
||||
spendTime += Number(task.hours_spent);
|
||||
}
|
||||
});
|
||||
}
|
||||
setTotalHours(spendTime);
|
||||
setReports(reports);
|
||||
setLoader(false);
|
||||
});
|
||||
}, [requestDates]);
|
||||
|
||||
function setValueHandler (value) {
|
||||
setValue(value)
|
||||
}
|
||||
|
||||
useEffect(() => {
|
||||
dispatch(setRequestDate(getReports(moment())))
|
||||
},[]);
|
||||
|
||||
useEffect( () => {
|
||||
setLoader(true)
|
||||
if (!requestDates) {
|
||||
return
|
||||
}
|
||||
apiRequest(`/reports/reports-by-date?${requestDates}&user_card_id=${localStorage.getItem('cardId')}`)
|
||||
.then((reports) => {
|
||||
let spendTime = 0;
|
||||
for (const report of reports) {
|
||||
report.task.map((task) => {
|
||||
if(task.hours_spent) {
|
||||
spendTime += Number(task.hours_spent)
|
||||
}
|
||||
})
|
||||
}
|
||||
setTotalHours(spendTime);
|
||||
setReports(reports)
|
||||
setLoader(false)
|
||||
})
|
||||
}, [requestDates]);
|
||||
|
||||
return (
|
||||
<div className='profile__calendar'>
|
||||
<ProfileHeader/>
|
||||
<Navigation />
|
||||
<div className='container'>
|
||||
<ProfileBreadcrumbs links={[{name: 'Главная', link: '/profile'},{name: 'Ваша отчетность', link: '/profile/calendar'}]} />
|
||||
<h2 className='summary__title'>Ваши отчеты</h2>
|
||||
<div className='summary__info'>
|
||||
<div className='summary__person'>
|
||||
<img src={urlForLocal(profileInfo.photo)} className='summary__avatar' alt='avatar'/>
|
||||
<p className='summary__name'>{profileInfo.fio}, {profileInfo.specification} разработчик</p>
|
||||
</div>
|
||||
<Link to='/report'>
|
||||
<button className="calendar__btn" onClick={() => {
|
||||
dispatch(setReportDate(''))
|
||||
}}>Заполнить отчет за день</button>
|
||||
</Link>
|
||||
</div>
|
||||
{loader ?
|
||||
<div className='loader__wrapper'>
|
||||
<Loader height={80} width={80} />
|
||||
</div>
|
||||
:
|
||||
<div className='row calendar__wrapper'>
|
||||
<div className='col-12 col-xl-12'>
|
||||
<ProfileCalendarComponent setValueHandler={setValueHandler} value={value} reports={reports} totalHours={totalHours} />
|
||||
</div>
|
||||
</div>
|
||||
}
|
||||
</div>
|
||||
<Footer />
|
||||
return (
|
||||
<div className="profile__calendar">
|
||||
<ProfileHeader />
|
||||
<Navigation />
|
||||
<div className="container">
|
||||
<ProfileBreadcrumbs
|
||||
links={[
|
||||
{ name: "Главная", link: "/profile" },
|
||||
{ name: "Ваша отчетность", link: "/profile/calendar" },
|
||||
]}
|
||||
/>
|
||||
<h2 className="summary__title">Ваши отчеты</h2>
|
||||
<div className="summary__info">
|
||||
<div className="summary__person">
|
||||
<img
|
||||
src={urlForLocal(profileInfo.photo)}
|
||||
className="summary__avatar"
|
||||
alt="avatar"
|
||||
/>
|
||||
<p className="summary__name">
|
||||
{profileInfo.fio}, {profileInfo.specification} разработчик
|
||||
</p>
|
||||
</div>
|
||||
<Link to="/report">
|
||||
<button
|
||||
className="calendar__btn"
|
||||
onClick={() => {
|
||||
dispatch(setReportDate(""));
|
||||
}}
|
||||
>
|
||||
Заполнить отчет за день
|
||||
</button>
|
||||
</Link>
|
||||
</div>
|
||||
)
|
||||
{loader ? (
|
||||
<div className="loader__wrapper">
|
||||
<Loader height={80} width={80} />
|
||||
</div>
|
||||
) : (
|
||||
<div className="row calendar__wrapper">
|
||||
<div className="col-12 col-xl-12">
|
||||
<ProfileCalendarComponent
|
||||
setValueHandler={setValueHandler}
|
||||
value={value}
|
||||
reports={reports}
|
||||
totalHours={totalHours}
|
||||
/>
|
||||
</div>
|
||||
</div>
|
||||
)}
|
||||
</div>
|
||||
<Footer />
|
||||
</div>
|
||||
);
|
||||
};
|
||||
|
@ -1,141 +1,175 @@
|
||||
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 {calendarHelper, currentMonthAndDay, getReports, hourOfNum} from '../Calendar/calendarHelper'
|
||||
import {setReportDate, setRequestDate} from '../../redux/reportSlice';
|
||||
import {useDispatch} from "react-redux";
|
||||
import {Link} from "react-router-dom";
|
||||
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 {
|
||||
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";
|
||||
|
||||
import 'moment/locale/ru'
|
||||
import './../Calendar/calendarComponent.scss'
|
||||
import "moment/locale/ru";
|
||||
import "./../Calendar/calendarComponent.scss";
|
||||
|
||||
export const ProfileCalendarComponent = React.memo(({value, setValueHandler, reports, totalHours}) => {
|
||||
export const ProfileCalendarComponent = React.memo(
|
||||
({ value, setValueHandler, reports, totalHours }) => {
|
||||
const dispatch = useDispatch();
|
||||
const [currentDay] = useState(moment())
|
||||
const [calendar, setCalendar] = useState([])
|
||||
const [month, setMonth] = useState('');
|
||||
|
||||
const [currentDay] = useState(moment());
|
||||
const [calendar, setCalendar] = useState([]);
|
||||
const [month, setMonth] = useState("");
|
||||
const [shortReport, setShortReport] = useState(false);
|
||||
|
||||
useEffect(() => {
|
||||
setCalendar(calendarHelper(value))
|
||||
}, [value])
|
||||
setCalendar(calendarHelper(value));
|
||||
}, [value]);
|
||||
|
||||
useEffect(() => {
|
||||
setMonth(value.format('MMMM'))
|
||||
setMonth(value.format("MMMM"));
|
||||
}, [month]);
|
||||
|
||||
function isToday(day) {
|
||||
return day.isSame(new Date(), 'day')
|
||||
return day.isSame(new Date(), "day");
|
||||
}
|
||||
|
||||
function correctDay(day) {
|
||||
if (day < 10) {
|
||||
return `0${day}`
|
||||
} return day
|
||||
if (day < 10) {
|
||||
return `0${day}`;
|
||||
}
|
||||
return day;
|
||||
}
|
||||
|
||||
function dayStyles(day) {
|
||||
if (currentDay < day) return `block`
|
||||
for (const date of reports) {
|
||||
if (`${new Date(day).getFullYear()}-${correctDay(new Date(day).getMonth() + 1)}-${correctDay(new Date(day).getDate())}` === date.created_at) {
|
||||
return `before`
|
||||
}
|
||||
if (currentDay < day) return `block`;
|
||||
for (const date of reports) {
|
||||
if (
|
||||
`${new Date(day).getFullYear()}-${correctDay(
|
||||
new Date(day).getMonth() + 1
|
||||
)}-${correctDay(new Date(day).getDate())}` === date.created_at
|
||||
) {
|
||||
return `before`;
|
||||
}
|
||||
if (day.day() === 6 || day.day() === 0) return `selected`
|
||||
if (isToday(day)) return `today`
|
||||
return 'pass'
|
||||
}
|
||||
if (day.day() === 6 || day.day() === 0) return `selected`;
|
||||
if (isToday(day)) return `today`;
|
||||
return "pass";
|
||||
}
|
||||
|
||||
function correctRoute(day) {
|
||||
for (const date of reports) {
|
||||
if (`${new Date(day).getFullYear()}-${correctDay(new Date(day).getMonth() + 1)}-${correctDay(new Date(day).getDate())}` === date.created_at) {
|
||||
return `../view`
|
||||
}
|
||||
for (const date of reports) {
|
||||
if (
|
||||
`${new Date(day).getFullYear()}-${correctDay(
|
||||
new Date(day).getMonth() + 1
|
||||
)}-${correctDay(new Date(day).getDate())}` === date.created_at
|
||||
) {
|
||||
return;
|
||||
}
|
||||
return '../../report'
|
||||
}
|
||||
return "../../report";
|
||||
}
|
||||
|
||||
function prevMonth() {
|
||||
return value.clone().subtract(1, 'month')
|
||||
return value.clone().subtract(1, "month");
|
||||
}
|
||||
|
||||
function nextMonth() {
|
||||
return value.clone().add(1, 'month');
|
||||
return value.clone().add(1, "month");
|
||||
}
|
||||
|
||||
return (
|
||||
<div className='calendar-component'>
|
||||
<div className='calendar-component__header'>
|
||||
<div className='calendar-component__header-info'>
|
||||
<h3>Мои отчеты:</h3>
|
||||
<p className='calendar__hours'>
|
||||
{month} <span>{totalHours} {hourOfNum(totalHours)} </span>
|
||||
</p>
|
||||
</div>
|
||||
<div className='calendar-component__header-switcher'>
|
||||
<div className='calendar-component__header-box' onClick={() => {
|
||||
setValueHandler(prevMonth())
|
||||
dispatch(setRequestDate(getReports(prevMonth())))
|
||||
}}>
|
||||
<img src={arrow} alt='' />
|
||||
<span>
|
||||
{prevMonth().format('MMMM')}
|
||||
</span>
|
||||
</div>
|
||||
<div className='calendar-component__header-box'>
|
||||
<span>{value.format('YYYY')}</span>
|
||||
</div>
|
||||
<div className='calendar-component__header-box' onClick={() => {
|
||||
setValueHandler(nextMonth())
|
||||
dispatch(setRequestDate(getReports(nextMonth())))
|
||||
|
||||
}}>
|
||||
<span>
|
||||
{nextMonth().format('MMMM')}
|
||||
</span>
|
||||
<img src={arrow} alt='' />
|
||||
</div>
|
||||
</div>
|
||||
<div className="calendar-component">
|
||||
<div className="calendar-component__header">
|
||||
<div className="calendar-component__header-info">
|
||||
<h3>Мои отчеты:</h3>
|
||||
<p className="calendar__hours">
|
||||
{month}
|
||||
<span>
|
||||
{totalHours} {hourOfNum(totalHours)}{" "}
|
||||
</span>
|
||||
</p>
|
||||
</div>
|
||||
<div className="calendar-component__header-switcher">
|
||||
<div
|
||||
className="calendar-component__header-box"
|
||||
onClick={() => {
|
||||
setValueHandler(prevMonth());
|
||||
dispatch(setRequestDate(getReports(prevMonth())));
|
||||
}}
|
||||
>
|
||||
<img src={arrow} alt="" />
|
||||
<span>{prevMonth().format("MMMM")}</span>
|
||||
</div>
|
||||
|
||||
<div className='calendar-component__rectangle'>
|
||||
<img src={rectangle} alt='' />
|
||||
<div className="calendar-component__header-box">
|
||||
<span>{value.format("YYYY")}</span>
|
||||
</div>
|
||||
|
||||
<div className='calendar-component__body'>
|
||||
<div>
|
||||
<p>Пн</p>
|
||||
<p>Вт</p>
|
||||
<p>Ср</p>
|
||||
<p>Чт</p>
|
||||
<p>Пт</p>
|
||||
<p>Сб</p>
|
||||
<p>Вс</p>
|
||||
</div>
|
||||
|
||||
<div className='calendar-component__form'>
|
||||
{calendar.map((week) =>
|
||||
week.map((day) => (
|
||||
<button
|
||||
onClick={() => {
|
||||
dispatch(setReportDate(day))
|
||||
}}
|
||||
key={day}
|
||||
className={dayStyles(day)}
|
||||
name={day.format('dddd')}
|
||||
id='btn'
|
||||
>
|
||||
<Link to={correctRoute(day)}>
|
||||
<img className={'calendar__icon'} src={calendarIcon} alt='' />
|
||||
{currentMonthAndDay(day)}
|
||||
</Link>
|
||||
</button>
|
||||
))
|
||||
)}
|
||||
</div>
|
||||
<div
|
||||
className="calendar-component__header-box"
|
||||
onClick={() => {
|
||||
setValueHandler(nextMonth());
|
||||
dispatch(setRequestDate(getReports(nextMonth())));
|
||||
}}
|
||||
>
|
||||
<span>{nextMonth().format("MMMM")}</span>
|
||||
<img src={arrow} alt="" />
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
)
|
||||
})
|
||||
|
||||
<div className="calendar-component__rectangle">
|
||||
<img src={rectangle} alt="" />
|
||||
</div>
|
||||
|
||||
<div className="calendar-component__body">
|
||||
<div>
|
||||
<p>Пн</p>
|
||||
<p>Вт</p>
|
||||
<p>Ср</p>
|
||||
<p>Чт</p>
|
||||
<p>Пт</p>
|
||||
<p>Сб</p>
|
||||
<p>Вс</p>
|
||||
</div>
|
||||
|
||||
<div className="calendar-component__form">
|
||||
{calendar.map((week) =>
|
||||
week.map((day) => (
|
||||
<button
|
||||
onClick={() => {
|
||||
dispatch(setReportDate(day));
|
||||
setShortReport(true);
|
||||
dispatch(setSendRequest(true));
|
||||
}}
|
||||
key={day}
|
||||
className={dayStyles(day)}
|
||||
name={day.format("dddd")}
|
||||
id="btn"
|
||||
>
|
||||
<Link to={correctRoute(day)}>
|
||||
<img
|
||||
className={"calendar__icon"}
|
||||
src={calendarIcon}
|
||||
alt=""
|
||||
/>
|
||||
{currentMonthAndDay(day)}
|
||||
</Link>
|
||||
</button>
|
||||
))
|
||||
)}
|
||||
</div>
|
||||
</div>
|
||||
|
||||
{shortReport && <ShortReport />}
|
||||
</div>
|
||||
);
|
||||
}
|
||||
);
|
||||
|
@ -1,5 +1,5 @@
|
||||
.profile__calendar {
|
||||
background: #F1F1F1;
|
||||
background: #f1f1f1;
|
||||
height: 100%;
|
||||
min-height: 100vh;
|
||||
font-family: "LabGrotesque", sans-serif;
|
||||
@ -32,7 +32,6 @@
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
.calendar__wrapper {
|
||||
min-height: 719px;
|
||||
|
||||
|
@ -1,69 +0,0 @@
|
||||
import React, { useEffect, useState } from "react";
|
||||
import ModalSettings from "../UI/ModalSettings/ModalSettings";
|
||||
|
||||
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 "./projectTiket.scss";
|
||||
|
||||
export const ProjectTiket = ({ project, index, setOpenProject }) => {
|
||||
const [modalSettings, setModalSettings] = useState(false);
|
||||
|
||||
useEffect(() => {
|
||||
initListeners();
|
||||
}, []);
|
||||
|
||||
function initListeners() {
|
||||
document.addEventListener("click", closeByClickingOut);
|
||||
}
|
||||
|
||||
function closeByClickingOut(event) {
|
||||
const path = event.path || (event.composedPath && event.composedPath());
|
||||
|
||||
if (
|
||||
event &&
|
||||
!path.find((item) => item.classList && item.classList.contains("project"))
|
||||
) {
|
||||
setModalSettings(false);
|
||||
}
|
||||
}
|
||||
|
||||
return (
|
||||
<div className="project" key={index}>
|
||||
<h3 onClick={() => setOpenProject(true)}>{project.name}</h3>
|
||||
<div className="project__info">
|
||||
<p>Открытые задачи</p>
|
||||
<span className="count">{project.count}</span>
|
||||
<span className="add">+</span>
|
||||
<span className="menu-settings" onClick={() => setModalSettings(true)}>
|
||||
...
|
||||
</span>
|
||||
</div>
|
||||
|
||||
<ModalSettings active={modalSettings}>
|
||||
<div className="project__settings-menu">
|
||||
<div>
|
||||
<img src={edit}></img>
|
||||
<p>редактировать</p>
|
||||
</div>
|
||||
<div>
|
||||
<img src={link}></img>
|
||||
<p>ссылка на проект</p>
|
||||
</div>
|
||||
<div>
|
||||
<img src={archiveSet}></img>
|
||||
<p>в архив</p>
|
||||
</div>
|
||||
<div>
|
||||
<img src={del}></img>
|
||||
<p>удалить</p>
|
||||
</div>
|
||||
</div>
|
||||
</ModalSettings>
|
||||
</div>
|
||||
);
|
||||
};
|
||||
|
||||
export default ProjectTiket;
|
107
src/components/ProjectTiket/ProjectTiket.jsx
Normal file
@ -0,0 +1,107 @@
|
||||
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 link from "../../images/link.svg";
|
||||
import archiveSet from "../../images/archive.svg";
|
||||
import del from "../../images/delete.svg";
|
||||
import edit from "../../images/edit.svg";
|
||||
|
||||
import "./projectTiket.scss";
|
||||
|
||||
export const ProjectTiket = ({ project, index }) => {
|
||||
const [modalSelect, setModalSelect] = useState(false);
|
||||
const [modalAdd, setModalAdd] = useState(false);
|
||||
const dispatch = useDispatch();
|
||||
|
||||
useEffect(() => {
|
||||
initListeners();
|
||||
}, []);
|
||||
|
||||
function initListeners() {
|
||||
document.addEventListener("click", closeByClickingOut);
|
||||
}
|
||||
|
||||
function closeByClickingOut(event) {
|
||||
const path = event.path || (event.composedPath && event.composedPath());
|
||||
|
||||
if (
|
||||
event &&
|
||||
!path.find((item) => item.classList && item.classList.contains("project"))
|
||||
) {
|
||||
setModalSelect(false);
|
||||
}
|
||||
}
|
||||
|
||||
function removeProject() {
|
||||
apiRequest("/project/update", {
|
||||
method: "PUT",
|
||||
data: {
|
||||
project_id: project.id,
|
||||
status: 10,
|
||||
},
|
||||
}).then((res) => {
|
||||
dispatch(deleteProject(project));
|
||||
});
|
||||
}
|
||||
|
||||
return (
|
||||
<div className="project" key={index}>
|
||||
<Link to={`/tracker/project/${project.id}`}>{project.name}</Link>
|
||||
<div className="project__info">
|
||||
<p>Открытые задачи</p>
|
||||
<span className="count">
|
||||
{project.columns.reduce(
|
||||
(accumulator, currentValue) =>
|
||||
accumulator + currentValue.tasks.length,
|
||||
0
|
||||
)}
|
||||
</span>
|
||||
<span className="menu-settings" onClick={() => setModalSelect(true)}>
|
||||
...
|
||||
</span>
|
||||
</div>
|
||||
|
||||
<TrackerModal
|
||||
active={modalAdd}
|
||||
setActive={setModalAdd}
|
||||
defautlInput={project.name}
|
||||
projectId={project.id}
|
||||
></TrackerModal>
|
||||
|
||||
<ModalSelect active={modalSelect}>
|
||||
<div className="project__settings-menu">
|
||||
<div
|
||||
onClick={() => {
|
||||
dispatch(modalToggle("editProject"));
|
||||
setModalAdd(true);
|
||||
setModalSelect(false);
|
||||
}}
|
||||
>
|
||||
<img src={edit}></img>
|
||||
<p>редактировать</p>
|
||||
</div>
|
||||
<div>
|
||||
<img src={link}></img>
|
||||
<p>ссылка на проект</p>
|
||||
</div>
|
||||
<div>
|
||||
<img src={archiveSet}></img>
|
||||
<p>в архив</p>
|
||||
</div>
|
||||
<div onClick={removeProject}>
|
||||
<img src={del}></img>
|
||||
<p>удалить</p>
|
||||
</div>
|
||||
</div>
|
||||
</ModalSelect>
|
||||
</div>
|
||||
);
|
||||
};
|
||||
|
||||
export default ProjectTiket;
|
@ -19,7 +19,7 @@
|
||||
padding: 8px 13px 8px;
|
||||
}
|
||||
|
||||
h3 {
|
||||
a {
|
||||
font-weight: 700;
|
||||
font-size: 18px;
|
||||
line-height: 32px;
|
||||
@ -27,7 +27,12 @@
|
||||
margin-bottom: 10px;
|
||||
overflow: hidden;
|
||||
white-space: nowrap;
|
||||
display: flex;
|
||||
text-overflow: ellipsis;
|
||||
|
||||
&:hover {
|
||||
color: black;
|
||||
}
|
||||
}
|
||||
|
||||
&__info {
|
||||
@ -78,7 +83,6 @@
|
||||
color: #6f6f6f;
|
||||
right: 0;
|
||||
top: -35%;
|
||||
z-index: 999;
|
||||
|
||||
@media (max-width: 430px) {
|
||||
display: none;
|
||||
|
176
src/components/ShortReport/ShortReport.jsx
Normal file
@ -0,0 +1,176 @@
|
||||
import React, { useEffect, useState } from "react";
|
||||
|
||||
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";
|
||||
|
||||
import "./shortReport.scss";
|
||||
|
||||
export const ShortReport = ({}) => {
|
||||
const reportDate = useSelector(getReportDate);
|
||||
|
||||
const sendReport = useSelector(getSendRequest);
|
||||
const dispatch = useDispatch();
|
||||
|
||||
const [taskText, setTaskText] = useState([]);
|
||||
const [difficulties, setDifficulties] = useState([]);
|
||||
const [tomorrowTask, setTomorrowTask] = useState([]);
|
||||
const [totalHours, setTotalHours] = useState(0);
|
||||
const [loader, setLoader] = useState(false);
|
||||
const [dateCreate, setDateCreate] = useState("");
|
||||
|
||||
function getReportFromDate(day) {
|
||||
setLoader(true);
|
||||
setTaskText([]);
|
||||
setDifficulties([]);
|
||||
setTomorrowTask([]);
|
||||
setTotalHours(0);
|
||||
apiRequest(
|
||||
`reports/find-by-date?user_card_id=${localStorage.getItem(
|
||||
"cardId"
|
||||
)}&date=${day}`
|
||||
).then((res) => {
|
||||
let spendTime = 0;
|
||||
for (const item of res) {
|
||||
setDateCreate(item.created_at);
|
||||
if (item.difficulties) {
|
||||
setDifficulties((prevArray) => [...prevArray, item.difficulties]);
|
||||
}
|
||||
if (item.tomorrow) {
|
||||
setTomorrowTask((prevArray) => [...prevArray, item.tomorrow]);
|
||||
}
|
||||
item.task.map((task) => {
|
||||
const taskInfo = {
|
||||
hours: task.hours_spent,
|
||||
task: task.task,
|
||||
id: task.id,
|
||||
};
|
||||
if (task.hours_spent) {
|
||||
spendTime += Number(task.hours_spent);
|
||||
}
|
||||
setTaskText((prevArray) => [...prevArray, taskInfo]);
|
||||
});
|
||||
}
|
||||
|
||||
setTotalHours(spendTime);
|
||||
setLoader(false);
|
||||
});
|
||||
}
|
||||
|
||||
if (sendReport) {
|
||||
dispatch(setSendRequest(false));
|
||||
getReportFromDate(getCreatedDate(reportDate));
|
||||
}
|
||||
|
||||
return (
|
||||
<div>
|
||||
<div className="viewReport__info short-report">
|
||||
<div className="viewReport__title-box">
|
||||
<h2 className="viewReport__title">
|
||||
Ваши отчеты - <span>просмотр отчета за день</span>
|
||||
</h2>
|
||||
<Link to={`../view/${dateCreate}`}>
|
||||
Посмотреть подробнее об отчете
|
||||
</Link>
|
||||
</div>
|
||||
|
||||
<div className="viewReport__bar">
|
||||
<h3 className="viewReport__bar__date">
|
||||
{getCorrectDate(reportDate)}
|
||||
</h3>
|
||||
<p className="viewReport__bar__hours">
|
||||
Вами потрачено на работу :{" "}
|
||||
<span>
|
||||
{totalHours} {hourOfNum(totalHours)}
|
||||
</span>
|
||||
</p>
|
||||
</div>
|
||||
</div>
|
||||
{loader && <Loader style="green" />}
|
||||
{Boolean(taskText.length) && (
|
||||
<div className="viewReport__content">
|
||||
<div className="table__container">
|
||||
<table className="viewReport__done">
|
||||
<thead>
|
||||
<tr>
|
||||
<th>
|
||||
<p>Какие задачи были выполнены?</p>
|
||||
</th>
|
||||
<th>
|
||||
<p>Время</p>
|
||||
</th>
|
||||
</tr>
|
||||
</thead>
|
||||
<tbody>
|
||||
{taskText.length &&
|
||||
taskText.map((task, index) => {
|
||||
return (
|
||||
<tr key={task.id}>
|
||||
<td>
|
||||
<p>
|
||||
{index + 1}. {task.task}
|
||||
</p>
|
||||
</td>
|
||||
<td>
|
||||
<div className="viewReport__done__hours__item">
|
||||
<span>{task.hours}</span>
|
||||
<p className="hours">
|
||||
{hourOfNum(task.hours)} на задачу
|
||||
</p>
|
||||
</div>
|
||||
</td>
|
||||
</tr>
|
||||
);
|
||||
})}
|
||||
<tr>
|
||||
<td></td>
|
||||
<td>
|
||||
<span>
|
||||
Всего: {totalHours} {hourOfNum(totalHours)}
|
||||
</span>
|
||||
</td>
|
||||
</tr>
|
||||
</tbody>
|
||||
</table>
|
||||
</div>
|
||||
{Boolean(difficulties.length) && (
|
||||
<div className="viewReport__item">
|
||||
<h3>Какие сложности возникли?</h3>
|
||||
{difficulties.map((item, index) => {
|
||||
return <p key={index}>{item}</p>;
|
||||
})}
|
||||
</div>
|
||||
)}
|
||||
{Boolean(tomorrowTask.length) && (
|
||||
<div className="viewReport__item">
|
||||
<h3>Что планируется сделать завтра?</h3>
|
||||
{tomorrowTask.map((item, index) => {
|
||||
return <p key={index}>{item}</p>;
|
||||
})}
|
||||
</div>
|
||||
)}
|
||||
</div>
|
||||
)}
|
||||
{!Boolean(taskText.length) && !loader && (
|
||||
<div className="viewReport__noTask">
|
||||
<p>
|
||||
В этот день вы <span>не заполняли</span> отчет
|
||||
</p>
|
||||
</div>
|
||||
)}
|
||||
</div>
|
||||
);
|
||||
};
|
||||
|
||||
export default ShortReport;
|
15
src/components/ShortReport/shortReport.scss
Normal file
@ -0,0 +1,15 @@
|
||||
.short-report {
|
||||
margin-bottom: 30px;
|
||||
}
|
||||
|
||||
.viewReport {
|
||||
&__title-box {
|
||||
display: flex;
|
||||
align-items: center;
|
||||
justify-content: space-between;
|
||||
|
||||
a {
|
||||
font-size: 16px;
|
||||
}
|
||||
}
|
||||
}
|
@ -1,11 +1,10 @@
|
||||
import React, { useState } from "react";
|
||||
import { Link } from "react-router-dom";
|
||||
|
||||
import arrow from "../../images/sideBarArrow.svg";
|
||||
import LogoITguild from "../../images/LogoITguild.svg";
|
||||
|
||||
import "./sidebar.scss";
|
||||
import { Link } from "react-router-dom";
|
||||
import { FREQUENTLY_ASKED_QUESTIONS_ROUTE } from "../../constants/router-path";
|
||||
|
||||
export const SideBar = () => {
|
||||
const [active, setActive] = useState(false);
|
||||
@ -48,10 +47,10 @@ export const SideBar = () => {
|
||||
</div>
|
||||
<ul className="auth-body__navigation">
|
||||
<li>
|
||||
<a href="#">Вход для партнеров</a>
|
||||
<Link to={"/auth"}>Вход для партнеров</Link>
|
||||
</li>
|
||||
<li>
|
||||
<a href="#">Кабинет разработчика</a>
|
||||
<Link to={"/auth"}>Кабинет разработчика</Link>
|
||||
</li>
|
||||
<li>
|
||||
<a href="#">Школа</a>
|
||||
@ -63,10 +62,10 @@ export const SideBar = () => {
|
||||
<a href="#">Контакты</a>
|
||||
</li>
|
||||
<li>
|
||||
<a href="#">Блог</a>
|
||||
<Link to={"/blog"}>Блог</Link>
|
||||
</li>
|
||||
<li>
|
||||
<Link to={FREQUENTLY_ASKED_QUESTIONS_ROUTE}>FAQ</Link>
|
||||
<Link to={"/frequently-asked-questions"}>FAQ</Link>
|
||||
</li>
|
||||
</ul>
|
||||
<p className="auth-body__politic">Политика конфиденциальности</p>
|
@ -20,11 +20,13 @@
|
||||
}
|
||||
|
||||
.auth-title {
|
||||
position: relative;
|
||||
display: flex;
|
||||
flex-direction: column;
|
||||
justify-content: space-between;
|
||||
align-items: center;
|
||||
height: 80%;
|
||||
z-index: 9999;
|
||||
|
||||
.text {
|
||||
display: flex;
|
||||
@ -108,8 +110,10 @@
|
||||
|
||||
.auth-body {
|
||||
padding: 40px;
|
||||
display: none;
|
||||
z-index: -1;
|
||||
visibility: hidden;
|
||||
transition: 0.2s ease-in-out;
|
||||
opacity: 0;
|
||||
z-index: 99;
|
||||
position: absolute;
|
||||
top: 0;
|
||||
left: 0;
|
||||
@ -173,6 +177,9 @@
|
||||
}
|
||||
|
||||
.auth-body.active {
|
||||
visibility: visible;
|
||||
transition: 0.1s ease-in-out;
|
||||
opacity: 1;
|
||||
display: flex;
|
||||
width: 424px;
|
||||
left: 140px;
|
||||
@ -184,6 +191,6 @@
|
||||
@media (max-width: 1375px) {
|
||||
left: 0;
|
||||
width: 100%;
|
||||
height: 535px;
|
||||
height: 605px;
|
||||
}
|
||||
}
|
||||
|
22
src/components/UI/CardArticle/CardArticle.jsx
Normal file
@ -0,0 +1,22 @@
|
||||
import React from "react";
|
||||
import cardCalendar from "../../../images/cardCalendar.svg";
|
||||
import { Link } from "react-router-dom";
|
||||
|
||||
import "./cardArticle.scss";
|
||||
|
||||
export const CardArticle = ({ images, title, data, id }) => {
|
||||
return (
|
||||
<div className="card-article">
|
||||
<Link to={`/blog/article/${id}`}>
|
||||
<img src={images} />
|
||||
<h5>{title}</h5>
|
||||
<div className="card-article__data">
|
||||
<img src={cardCalendar} />
|
||||
<p>{data}</p>
|
||||
</div>
|
||||
</Link>
|
||||
</div>
|
||||
);
|
||||
};
|
||||
|
||||
export default CardArticle;
|
36
src/components/UI/CardArticle/cardArticle.scss
Normal file
@ -0,0 +1,36 @@
|
||||
.card-article {
|
||||
background: #ffffff;
|
||||
border-radius: 12px;
|
||||
width: 344px;
|
||||
margin-bottom: 24px;
|
||||
transition: 0.4s;
|
||||
a {
|
||||
color: #000000;
|
||||
}
|
||||
|
||||
&:hover {
|
||||
cursor: pointer;
|
||||
transform: scale(1.05);
|
||||
transition: 0.4s;
|
||||
a {
|
||||
color: #000000;
|
||||
}
|
||||
}
|
||||
|
||||
h5 {
|
||||
font-style: normal;
|
||||
font-weight: 500;
|
||||
font-size: 17px;
|
||||
line-height: 20px;
|
||||
padding: 12px 16px 8px 16px;
|
||||
}
|
||||
|
||||
&__data {
|
||||
display: flex;
|
||||
padding: 0 0 14px 16px;
|
||||
|
||||
img {
|
||||
margin-right: 9px;
|
||||
}
|
||||
}
|
||||
}
|
@ -175,7 +175,6 @@
|
||||
.auth-body.active {
|
||||
left: 0;
|
||||
width: 100%;
|
||||
height: 535px;
|
||||
}
|
||||
|
||||
.auth-body {
|
||||
|
@ -1,13 +1,13 @@
|
||||
import React, { useState } from "react";
|
||||
|
||||
import { Link } from "react-router-dom";
|
||||
import ModalLayout from "../ModalLayout/ModalLayout";
|
||||
|
||||
import avatar from "../../../images/mokPerson.png";
|
||||
import logoTg from "../../../images/TgLogo.svg";
|
||||
import arrow from "../../../images/right-arrow.png";
|
||||
import interview from "../../../images/interviewLogo.svg";
|
||||
|
||||
import { Link } from "react-router-dom";
|
||||
import ModalAdd from "../ModalAdd/ModalAdd";
|
||||
|
||||
import "./modalAspt.scss";
|
||||
|
||||
export const ModalAspt = ({ active, setActive, level }) => {
|
||||
@ -82,7 +82,8 @@ export const ModalAspt = ({ active, setActive, level }) => {
|
||||
</div>
|
||||
<span className="exit" onClick={() => setActive(false)}></span>
|
||||
</div>
|
||||
<ModalAdd active={modalSend} setActive={setModalSend}>
|
||||
|
||||
<ModalLayout active={modalSend} setActive={setModalSend}>
|
||||
<div className="send">
|
||||
<img src={interview}></img>
|
||||
<h2>Спасибо, собеседование назначено</h2>
|
||||
@ -93,7 +94,7 @@ export const ModalAspt = ({ active, setActive, level }) => {
|
||||
Время собеседования: <span>{time}</span>
|
||||
</p>
|
||||
</div>
|
||||
</ModalAdd>
|
||||
</ModalLayout>
|
||||
</div>
|
||||
);
|
||||
};
|
@ -1,53 +0,0 @@
|
||||
import React, { useState } from "react";
|
||||
import { useDispatch } from "react-redux";
|
||||
import { setProject } from "../../../redux/projectsTrackerSlice";
|
||||
|
||||
import "./ModalCreate.scss";
|
||||
|
||||
export const ModalCreate = ({ active, setActive, title }) => {
|
||||
const [inputValue, setInputValue] = useState("");
|
||||
const dispatch = useDispatch();
|
||||
|
||||
function createName() {
|
||||
if (inputValue === "") {
|
||||
return;
|
||||
} else {
|
||||
let newItem = {
|
||||
name: inputValue,
|
||||
count: 0,
|
||||
};
|
||||
dispatch(setProject(newItem));
|
||||
setActive(false);
|
||||
setInputValue("");
|
||||
}
|
||||
}
|
||||
|
||||
return (
|
||||
<div
|
||||
className={active ? "modal-project active" : "modal-project"}
|
||||
onClick={() => setActive(false)}
|
||||
>
|
||||
<div
|
||||
className="modal-project__content"
|
||||
onClick={(e) => e.stopPropagation()}
|
||||
>
|
||||
<div className="title-project">
|
||||
<h4>{title}</h4>
|
||||
<div className="input-container">
|
||||
<input
|
||||
className="name-project"
|
||||
value={inputValue}
|
||||
onChange={(e) => setInputValue(e.target.value)}
|
||||
></input>
|
||||
</div>
|
||||
<button className="create-project" onClick={createName}>
|
||||
Создать
|
||||
</button>
|
||||
</div>
|
||||
<span className="exit" onClick={() => setActive(false)}></span>
|
||||
</div>
|
||||
</div>
|
||||
);
|
||||
};
|
||||
|
||||
export default ModalCreate;
|
@ -1,77 +0,0 @@
|
||||
.modal-project {
|
||||
z-index: 9999;
|
||||
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;
|
||||
width: 424px;
|
||||
height: 248px;
|
||||
background: linear-gradient(180deg, #ffffff 0%, #ebebeb 100%);
|
||||
border-radius: 40px;
|
||||
|
||||
padding: 15px;
|
||||
display: flex;
|
||||
flex-direction: column;
|
||||
align-items: center;
|
||||
justify-content: center;
|
||||
|
||||
.title-project {
|
||||
display: flex;
|
||||
align-items: flex-start;
|
||||
flex-direction: column;
|
||||
|
||||
.input-container {
|
||||
width: 287px;
|
||||
height: 35px;
|
||||
background: #ffffff;
|
||||
border-radius: 8px;
|
||||
}
|
||||
|
||||
h4 {
|
||||
font-weight: 500;
|
||||
font-size: 22px;
|
||||
line-height: 26px;
|
||||
color: #263238;
|
||||
margin-bottom: 22px;
|
||||
}
|
||||
}
|
||||
|
||||
.name-project {
|
||||
margin-left: 10px;
|
||||
border: none;
|
||||
outline: none;
|
||||
height: 100%;
|
||||
width: 90%;
|
||||
font-size: 14px;
|
||||
}
|
||||
|
||||
.create-project {
|
||||
margin: 30px 0 0 0;
|
||||
width: 130px;
|
||||
height: 37px;
|
||||
background: #52b709;
|
||||
border-radius: 44px;
|
||||
border: none;
|
||||
font-weight: 400;
|
||||
font-size: 15px;
|
||||
line-height: 32px;
|
||||
color: #ffffff;
|
||||
display: flex;
|
||||
align-items: center;
|
||||
justify-content: center;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
.modal-project.active {
|
||||
transform: scale(1);
|
||||
}
|
@ -1,8 +1,6 @@
|
||||
import React from "react";
|
||||
|
||||
import "./modalAdd.scss";
|
||||
|
||||
export const ModalAdd = ({ children, active, setActive }) => {
|
||||
export const ModalLayout = ({ active, setActive, children }) => {
|
||||
return (
|
||||
<div
|
||||
className={active ? "modal-add active" : "modal-add"}
|
||||
@ -10,10 +8,9 @@ export const ModalAdd = ({ children, active, setActive }) => {
|
||||
>
|
||||
<div className="modal-add__content" onClick={(e) => e.stopPropagation()}>
|
||||
{children}
|
||||
<span className="exit" onClick={() => setActive(false)}></span>
|
||||
</div>
|
||||
</div>
|
||||
);
|
||||
};
|
||||
|
||||
export default ModalAdd;
|
||||
export default ModalLayout;
|
14
src/components/UI/ModalSelect/ModalSelect.jsx
Normal file
@ -0,0 +1,14 @@
|
||||
import React from "react";
|
||||
|
||||
import "./modalSelect.scss";
|
||||
|
||||
export const ModalSelect = ({ active, children }) => {
|
||||
return (
|
||||
<div className={active ? "project__settings active" : "project__settings "}>
|
||||
<span className="project__settings-ellipsis">...</span>
|
||||
{children}
|
||||
</div>
|
||||
);
|
||||
};
|
||||
|
||||
export default ModalSelect;
|
@ -21,6 +21,14 @@
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
&-ellipsis {
|
||||
position: absolute;
|
||||
top: -2px;
|
||||
left: 10px;
|
||||
font-size: 21px;
|
||||
color: #6f6f6f;
|
||||
}
|
||||
}
|
||||
|
||||
&__settings.active {
|
@ -1,13 +0,0 @@
|
||||
import React from "react";
|
||||
|
||||
import "./modalSettings.scss";
|
||||
|
||||
export const ModalSettings = ({ active, children }) => {
|
||||
return (
|
||||
<div className={active ? "project__settings active" : "project__settings "}>
|
||||
{children}
|
||||
</div>
|
||||
);
|
||||
};
|
||||
|
||||
export default ModalSettings;
|
@ -1,11 +1,17 @@
|
||||
import React, { useState } from "react";
|
||||
import React, {useEffect, useState} from "react";
|
||||
import { Link } from "react-router-dom";
|
||||
import TrackerModal from "../TrackerModal/TrackerModal";
|
||||
import { apiRequest } from "../../../api/request";
|
||||
import { useDispatch } from "react-redux";
|
||||
import {
|
||||
modalToggle,
|
||||
setProjectBoardFetch,
|
||||
} from "../../../redux/projectsTrackerSlice";
|
||||
|
||||
import avatarMock1 from "../../../images/avatarMoсk1.png";
|
||||
import avatarMock2 from "../../../images/avatarMoсk2.png";
|
||||
import category from "../../../images/category.png";
|
||||
import watch from "../../../images/watch.png";
|
||||
import file from "../../../images/fileModal.svg";
|
||||
import task from "../../../images/tasksMock.png";
|
||||
import taskImg from "../../../images/tasksMock.png";
|
||||
import arrow from "../../../images/arrowStart.png";
|
||||
import link from "../../../images/link.svg";
|
||||
import archive from "../../../images/archive.svg";
|
||||
@ -15,29 +21,45 @@ import send from "../../../images/send.svg";
|
||||
import plus from "../../../images/plus.svg";
|
||||
import fullScreen from "../../../images/inFullScreen.svg";
|
||||
|
||||
import ModalAdd from "../ModalAdd/ModalAdd";
|
||||
import "./ModalTicket.scss";
|
||||
import { Link } from "react-router-dom";
|
||||
|
||||
export const ModalTiсket = ({ active, setActive, index }) => {
|
||||
const [tiket] = useState({
|
||||
name: "Разработка трекера",
|
||||
code: "PR - 2245",
|
||||
creator: "Василий Тарасов",
|
||||
descriptions:
|
||||
"На многих страницах сайта отсутствуют или некорректно заполнены метатеги Description. Это может негативно повлиять на представление сайта в результатах поиска. Необходимо исправить все страницы где есть ошибки или отсутствует Title и Description.",
|
||||
});
|
||||
const [workers] = useState([
|
||||
{
|
||||
name: "Дмитрий Рогов",
|
||||
avatar: avatarMock2,
|
||||
},
|
||||
{
|
||||
name: "Марина Серова",
|
||||
avatar: avatarMock1,
|
||||
},
|
||||
]);
|
||||
export const ModalTiсket = ({
|
||||
active,
|
||||
setActive,
|
||||
task,
|
||||
projectId,
|
||||
projectName,
|
||||
}) => {
|
||||
const dispatch = useDispatch();
|
||||
const [addSubtask, setAddSubtask] = useState(false);
|
||||
const [editOpen, setEditOpen] = useState(false);
|
||||
const [inputsValue, setInputsValue] = useState({title: task.title, description: task.description})
|
||||
|
||||
function deleteTask() {
|
||||
apiRequest("/task/update-task", {
|
||||
method: "PUT",
|
||||
data: {
|
||||
task_id: task.id,
|
||||
status: 0,
|
||||
},
|
||||
}).then((res) => {
|
||||
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((res) => {
|
||||
dispatch(setProjectBoardFetch(projectId));
|
||||
});
|
||||
}
|
||||
|
||||
return (
|
||||
<div
|
||||
@ -51,23 +73,34 @@ export const ModalTiсket = ({ active, setActive, index }) => {
|
||||
<div className="content">
|
||||
<h3 className="title-project">
|
||||
<img src={category} className="title-project__category"></img>
|
||||
Проект: {tiket.name}
|
||||
<Link to={`/tracker/${index}`} className="title-project__full">
|
||||
Проект: {projectName}
|
||||
<Link
|
||||
to={`/tracker/task/${task.id}`}
|
||||
className="title-project__full"
|
||||
>
|
||||
<img src={fullScreen}></img>
|
||||
</Link>
|
||||
</h3>
|
||||
|
||||
<div className="content__task">
|
||||
<span>Задача</span>
|
||||
<h5>{tiket.code}</h5>
|
||||
{editOpen ? <input value={inputsValue.title} onChange={(e) => {
|
||||
setInputsValue((prevValue) => ({...prevValue, title: e.target.value}))
|
||||
}} /> :<h5>{inputsValue.title}</h5>}
|
||||
<div className="content__description">
|
||||
<p>{tiket.descriptions}</p>
|
||||
<img src={task} className="image-task"></img>
|
||||
<p>{tiket.descriptions}</p>
|
||||
{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">
|
||||
<button onClick={() => setAddSubtask(true)}>
|
||||
<button
|
||||
onClick={() => {
|
||||
dispatch(modalToggle("addSubtask"));
|
||||
setAddSubtask(true);
|
||||
}}
|
||||
>
|
||||
<img src={plus}></img>
|
||||
Добавить под задачу
|
||||
</button>
|
||||
@ -90,17 +123,18 @@ export const ModalTiсket = ({ active, setActive, index }) => {
|
||||
<div className="workers">
|
||||
<div className="workers_box">
|
||||
<span className="exit" onClick={() => setActive(false)}></span>
|
||||
<span>{tiket.code}</span>
|
||||
<p className="workers__creator">Создатель : {tiket.creator}</p>
|
||||
<span>{task.title}</span>
|
||||
<p className="workers__creator">Создатель : {task.user?.fio}</p>
|
||||
<div>
|
||||
{workers.map((worker, index) => {
|
||||
return (
|
||||
<div className="worker" key={index}>
|
||||
<img src={worker.avatar}></img>
|
||||
<p>{worker.name}</p>
|
||||
</div>
|
||||
);
|
||||
})}
|
||||
{Boolean(task.taskUsers?.length) &&
|
||||
task.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">
|
||||
@ -122,39 +156,38 @@ export const ModalTiсket = ({ active, setActive, index }) => {
|
||||
</div>
|
||||
|
||||
<div className="workers_box-bottom">
|
||||
<div>
|
||||
<div className={editOpen ? 'edit' : ''} onClick={() => {
|
||||
if(editOpen) {
|
||||
setEditOpen(!editOpen)
|
||||
editTask()
|
||||
} else {
|
||||
setEditOpen(!editOpen)
|
||||
}
|
||||
}}>
|
||||
<img src={edit}></img>
|
||||
<p>редактировать</p>
|
||||
<p>{editOpen ? 'сохранить' : 'редактировать'}</p>
|
||||
</div>
|
||||
<div>
|
||||
<img src={link}></img>
|
||||
<p>ссылка на проект</p>
|
||||
</div>
|
||||
<div>
|
||||
<div onClick={deleteTask}>
|
||||
<img src={archive}></img>
|
||||
<p>в архив</p>
|
||||
</div>
|
||||
<div>
|
||||
<div onClick={deleteTask}>
|
||||
<img src={del}></img>
|
||||
<p>удалить</p>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
<ModalAdd active={addSubtask} setActive={setAddSubtask}>
|
||||
<div className="title-project subtask">
|
||||
<h4>
|
||||
Вы добавляете подзадачу <p>в колонку задачи {"Готово"}</p>
|
||||
</h4>
|
||||
<p className="title-project__decs">Введите текст</p>
|
||||
<div>
|
||||
<textarea className="title-project__textarea"></textarea>
|
||||
</div>
|
||||
</div>
|
||||
<button className="button-add" onClick={(e) => e.preventDefault()}>
|
||||
Добавить
|
||||
</button>
|
||||
</ModalAdd>
|
||||
|
||||
<TrackerModal
|
||||
active={addSubtask}
|
||||
setActive={setAddSubtask}
|
||||
defautlInput={task.column_id}
|
||||
></TrackerModal>
|
||||
</div>
|
||||
);
|
||||
};
|
@ -6,19 +6,18 @@
|
||||
position: fixed;
|
||||
top: 0;
|
||||
left: 0;
|
||||
display: flex;
|
||||
display: none;
|
||||
align-items: center;
|
||||
justify-content: center;
|
||||
transform: scale(0);
|
||||
}
|
||||
|
||||
.modal-tiket.active {
|
||||
transform: scale(1);
|
||||
display: flex;
|
||||
}
|
||||
|
||||
.modal-tiket__content {
|
||||
background: #ffffff;
|
||||
border: 1px solid #dde2e4;
|
||||
//border: 1px solid #dde2e4;
|
||||
border-radius: 8px;
|
||||
display: flex;
|
||||
flex-direction: row;
|
||||
@ -50,6 +49,16 @@
|
||||
&__task {
|
||||
margin-top: -5px;
|
||||
padding: 18px;
|
||||
display: flex;
|
||||
flex-direction: column;
|
||||
|
||||
input {
|
||||
font-style: normal;
|
||||
font-size: 16px;
|
||||
line-height: 24px;
|
||||
max-width: 340px;
|
||||
outline: none;
|
||||
}
|
||||
|
||||
button {
|
||||
img {
|
||||
@ -64,10 +73,14 @@
|
||||
font-size: 16px;
|
||||
line-height: 24px;
|
||||
color: #1a1919;
|
||||
margin-bottom: 0;
|
||||
}
|
||||
}
|
||||
|
||||
&__description {
|
||||
display: flex;
|
||||
flex-direction: column;
|
||||
margin-top: 10px;
|
||||
p {
|
||||
font-weight: 400;
|
||||
font-size: 14px;
|
||||
@ -77,7 +90,8 @@
|
||||
}
|
||||
|
||||
.image-task {
|
||||
margin: 0 0 20px 0;
|
||||
margin: 10px 0 20px 0;
|
||||
max-width: 330px;
|
||||
}
|
||||
}
|
||||
|
||||
@ -266,6 +280,19 @@
|
||||
line-height: 32px;
|
||||
font-weight: 500;
|
||||
color: #2d4a17;
|
||||
display: flex;
|
||||
max-width: 200px;
|
||||
overflow: hidden;
|
||||
white-space: nowrap;
|
||||
text-overflow: ellipsis;
|
||||
|
||||
span {
|
||||
margin-left: 5px;
|
||||
font-size: 14px;
|
||||
line-height: 32px;
|
||||
font-weight: 500;
|
||||
display: flex;
|
||||
}
|
||||
}
|
||||
|
||||
.worker {
|
||||
@ -291,19 +318,30 @@
|
||||
}
|
||||
|
||||
&-bottom {
|
||||
padding: 40px 0 75px 56px;
|
||||
padding: 40px 110px 75px 56px;
|
||||
font-weight: 400;
|
||||
font-size: 14px;
|
||||
line-height: 38px;
|
||||
|
||||
div {
|
||||
display: flex;
|
||||
cursor: pointer;
|
||||
align-items: center;
|
||||
padding-left: 10px;
|
||||
|
||||
p {
|
||||
margin: 0 0 0 12px;
|
||||
}
|
||||
}
|
||||
|
||||
.edit {
|
||||
background: #52b709;
|
||||
border-radius: 50px;
|
||||
|
||||
p {
|
||||
font-weight: 700;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
@ -1,13 +1,17 @@
|
||||
import React, { useState } from "react";
|
||||
import React, { useEffect, useState } from "react";
|
||||
|
||||
import { ProfileHeader } from "../../ProfileHeader/ProfileHeader";
|
||||
import { ProfileBreadcrumbs } from "../../ProfileBreadcrumbs/ProfileBreadcrumbs";
|
||||
import { Footer } from "../../Footer/Footer";
|
||||
import { Link } from "react-router-dom";
|
||||
import ModalAdd from "../ModalAdd/ModalAdd";
|
||||
import { Link, useParams, useNavigate } from "react-router-dom";
|
||||
import TrackerModal from "../TrackerModal/TrackerModal";
|
||||
import { Navigation } from "../../Navigation/Navigation";
|
||||
import {Loader} from "../../Loader/Loader";
|
||||
|
||||
import { useDispatch } from "react-redux";
|
||||
import {modalToggle, setToggleTab} from "../../../redux/projectsTrackerSlice";
|
||||
import { apiRequest } from "../../../api/request";
|
||||
|
||||
import avatarMock1 from "../../../images/avatarMoсk1.png";
|
||||
import avatarMock2 from "../../../images/avatarMoсk2.png";
|
||||
import project from "../../../images/trackerProject.svg";
|
||||
import watch from "../../../images/watch.png";
|
||||
import file from "../../../images/fileModal.svg";
|
||||
@ -28,36 +32,61 @@ import edit from "../../../images/edit.svg";
|
||||
import "./ticketFullScreen.scss";
|
||||
|
||||
export const TicketFullScreen = ({}) => {
|
||||
const [toggleTab, setToggleTab] = useState(1);
|
||||
const [addSubtask, setAddSubtask] = useState(false);
|
||||
const [modalAddWorker, setModalAddWorker] = useState(false);
|
||||
const [valueTiket, setValueTiket] = useState("");
|
||||
const ticketId = useParams();
|
||||
const dispatch = useDispatch();
|
||||
const navigate = useNavigate();
|
||||
const [projectInfo, setProjectInfo] = useState({});
|
||||
const [taskInfo, setTaskInfo] = useState({});
|
||||
const [editOpen, setEditOpen] = useState(false);
|
||||
const [inputsValue, setInputsValue] = useState({})
|
||||
const [loader, setLoader] = useState(true)
|
||||
|
||||
const [tiket] = useState({
|
||||
name: "Разработка трекера",
|
||||
code: "PR - 2245",
|
||||
creator: "Василий Тарасов",
|
||||
descriptions:
|
||||
"На многих страницах сайта отсутствуют или некорректно заполнены метатеги Description. Это может негативно повлиять на представление сайта в результатах поиска. Необходимо исправить все страницы где есть ошибки или отсутствует Title и Description.",
|
||||
});
|
||||
const [workers] = useState([
|
||||
{
|
||||
name: "Дмитрий Рогов",
|
||||
avatar: avatarMock2,
|
||||
},
|
||||
{
|
||||
name: "Марина Серова",
|
||||
avatar: avatarMock1,
|
||||
},
|
||||
]);
|
||||
useEffect(() => {
|
||||
apiRequest(`/task/get-task?task_id=${ticketId.id}`).then((taskInfo) => {
|
||||
setTaskInfo(taskInfo);
|
||||
setInputsValue({title: taskInfo.title, description: taskInfo.description})
|
||||
apiRequest(`/project/get-project?project_id=${taskInfo.project_id}`).then(
|
||||
(project) => {
|
||||
setProjectInfo(project);
|
||||
setLoader(false)
|
||||
}
|
||||
);
|
||||
});
|
||||
}, []);
|
||||
|
||||
function deleteTask() {
|
||||
apiRequest("/task/update-task", {
|
||||
method: "PUT",
|
||||
data: {
|
||||
task_id: ticketId.id,
|
||||
status: 0,
|
||||
},
|
||||
}).then((res) => {
|
||||
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((res) => {
|
||||
});
|
||||
}
|
||||
|
||||
const toggleTabs = (index) => {
|
||||
setToggleTab(index);
|
||||
dispatch(setToggleTab(index));
|
||||
};
|
||||
|
||||
return (
|
||||
<section className="ticket-full-screen">
|
||||
<ProfileHeader />
|
||||
<Navigation />
|
||||
<div className="container">
|
||||
<div className="tracker__content">
|
||||
<ProfileBreadcrumbs
|
||||
@ -71,62 +100,54 @@ export const TicketFullScreen = ({}) => {
|
||||
</div>
|
||||
<div className="tracker__tabs">
|
||||
<div className="tracker__tabs__head">
|
||||
<div
|
||||
className={toggleTab === 1 ? "tab active-tab" : "tab"}
|
||||
<Link
|
||||
to="/profile/tracker"
|
||||
className="tab active-tab"
|
||||
onClick={() => toggleTabs(1)}
|
||||
>
|
||||
<img src={project} alt="img" />
|
||||
<p>Проекты </p>
|
||||
</div>
|
||||
<div
|
||||
className={toggleTab === 2 ? "tab active-tab" : "tab"}
|
||||
</Link>
|
||||
<Link
|
||||
to="/profile/tracker"
|
||||
className="tab"
|
||||
onClick={() => toggleTabs(2)}
|
||||
>
|
||||
<img src={tasks} alt="img" />
|
||||
<Link to={`/profile/tracker`} className="link">
|
||||
<p>Все мои задачи</p>
|
||||
</Link>
|
||||
</div>
|
||||
<div
|
||||
className={toggleTab === 3 ? "tab active-tab" : "tab"}
|
||||
<p>Все мои задачи</p>
|
||||
</Link>
|
||||
<Link
|
||||
to="/profile/tracker"
|
||||
className="tab"
|
||||
onClick={() => toggleTabs(3)}
|
||||
>
|
||||
<img src={archive} alt="img" />
|
||||
<Link to={`/profile/tracker`} className="link">
|
||||
<p>Архив</p>
|
||||
</Link>
|
||||
</div>
|
||||
<p>Архив</p>
|
||||
</Link>
|
||||
</div>
|
||||
{loader ? <Loader /> :
|
||||
<>
|
||||
<div className="tracker__tabs__content content-tabs">
|
||||
<div className="tasks__head">
|
||||
<div className="tasks__head__wrapper">
|
||||
<h4>Проект : Разработка трекера</h4>
|
||||
<h4>Проект : {projectInfo.name}</h4>
|
||||
|
||||
<ModalAdd active={modalAddWorker} setActive={setModalAddWorker}>
|
||||
<div className="title-project">
|
||||
<h4>Добавьте участника</h4>
|
||||
<p className="title-project__decs">Введите имя или e-mail</p>
|
||||
<div className="input-container">
|
||||
<input
|
||||
className="name-project"
|
||||
value={valueTiket}
|
||||
onChange={(e) => setValueTiket(e.target.value)}
|
||||
></input>
|
||||
</div>
|
||||
</div>
|
||||
<button
|
||||
className="button-add"
|
||||
onClick={(e) => e.preventDefault()}
|
||||
>
|
||||
Добавить
|
||||
</button>
|
||||
</ModalAdd>
|
||||
<TrackerModal
|
||||
active={modalAddWorker}
|
||||
setActive={setModalAddWorker}
|
||||
></TrackerModal>
|
||||
|
||||
<div className="tasks__head__persons">
|
||||
<img src={avatarTest} alt="avatar" />
|
||||
<img src={avatarTest} alt="avatar" />
|
||||
<span className="countPersons">+9</span>
|
||||
<span className="addPerson" onClick={setModalAddWorker}>
|
||||
<span
|
||||
className="addPerson"
|
||||
onClick={() => {
|
||||
dispatch(modalToggle("addWorker"));
|
||||
setModalAddWorker(true);
|
||||
}}
|
||||
>
|
||||
+
|
||||
</span>
|
||||
<p>добавить участника в проект</p>
|
||||
@ -152,15 +173,23 @@ export const TicketFullScreen = ({}) => {
|
||||
<div className="content ticket-whith">
|
||||
<div className="content__task">
|
||||
<span>Задача</span>
|
||||
<h5>{tiket.code}</h5>
|
||||
{editOpen ? <input value={inputsValue.title} onChange={(e) => {
|
||||
setInputsValue((prevValue) => ({...prevValue, title: e.target.value}))
|
||||
}} /> :<h5>{inputsValue.title}</h5>}
|
||||
<div className="content__description">
|
||||
<p>{tiket.descriptions}</p>
|
||||
{editOpen ? <input value={inputsValue.description} onChange={(e) => {
|
||||
setInputsValue((prevValue) => ({...prevValue, description: e.target.value}))
|
||||
}}/> :<p>{inputsValue.description}</p>}
|
||||
<img src={task} className="image-task"></img>
|
||||
<p>{tiket.descriptions}</p>
|
||||
</div>
|
||||
<div className="content__communication">
|
||||
<p className="tasks">
|
||||
<button onClick={() => setAddSubtask(true)}>
|
||||
<button
|
||||
onClick={() => {
|
||||
dispatch(modalToggle("addSubtask"));
|
||||
setModalAddWorker(true);
|
||||
}}
|
||||
>
|
||||
<img src={plus}></img>
|
||||
Добавить под задачу
|
||||
</button>
|
||||
@ -182,20 +211,30 @@ export const TicketFullScreen = ({}) => {
|
||||
</div>
|
||||
<div className="workers">
|
||||
<div className="workers_box">
|
||||
<p className="workers__creator">Создатель : {tiket.creator}</p>
|
||||
<p className="workers__creator">
|
||||
Создатель : <span>{taskInfo.user?.fio}</span>
|
||||
</p>
|
||||
<div>
|
||||
{workers.map((worker, index) => {
|
||||
return (
|
||||
<div className="worker" key={index}>
|
||||
<img src={worker.avatar}></img>
|
||||
<p>{worker.name}</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>+</button>
|
||||
<button
|
||||
onClick={() => {
|
||||
dispatch(modalToggle("addWorker"));
|
||||
setModalAddWorker(true);
|
||||
}}
|
||||
>
|
||||
+
|
||||
</button>
|
||||
<span>Добавить участников</span>
|
||||
</div>
|
||||
</div>
|
||||
@ -213,9 +252,16 @@ export const TicketFullScreen = ({}) => {
|
||||
</div>
|
||||
|
||||
<div className="workers_box-bottom">
|
||||
<div>
|
||||
<div className={editOpen ? 'edit' : ''} onClick={() => {
|
||||
if(editOpen) {
|
||||
setEditOpen(!editOpen)
|
||||
editTask()
|
||||
} else {
|
||||
setEditOpen(!editOpen)
|
||||
}
|
||||
}}>
|
||||
<img src={edit}></img>
|
||||
<p>редактировать</p>
|
||||
<p>{editOpen ? 'сохранить' : 'редактировать'}</p>
|
||||
</div>
|
||||
<div>
|
||||
<img src={link}></img>
|
||||
@ -225,28 +271,15 @@ export const TicketFullScreen = ({}) => {
|
||||
<img src={archive2}></img>
|
||||
<p>в архив</p>
|
||||
</div>
|
||||
<div>
|
||||
<div onClick={deleteTask}>
|
||||
<img src={del}></img>
|
||||
<p>удалить</p>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<ModalAdd active={addSubtask} setActive={setAddSubtask}>
|
||||
<div className="title-project subtask">
|
||||
<h4>
|
||||
Вы добавляете подзадачу <p>в колонку задачи {"Готово"}</p>
|
||||
</h4>
|
||||
<p className="title-project__decs">Введите текст</p>
|
||||
<div>
|
||||
<textarea className="title-project__textarea"></textarea>
|
||||
</div>
|
||||
</div>
|
||||
<button className="button-add" onClick={(e) => e.preventDefault()}>
|
||||
Добавить
|
||||
</button>
|
||||
</ModalAdd>
|
||||
</>
|
||||
}
|
||||
</div>
|
||||
<Footer />
|
||||
</section>
|
314
src/components/UI/TrackerModal/TrackerModal.jsx
Normal file
@ -0,0 +1,314 @@
|
||||
import React, {useEffect, useState} from "react";
|
||||
|
||||
import { useDispatch, useSelector } from "react-redux";
|
||||
import { apiRequest } from "../../../api/request";
|
||||
import { urlForLocal } from '../../../helper'
|
||||
import {
|
||||
setColumnName,
|
||||
getProjectBoard,
|
||||
getValueModalType,
|
||||
setProject,
|
||||
setProjectBoardFetch,
|
||||
editProjectName,
|
||||
editColumnName,
|
||||
getColumnName,
|
||||
getColumnId
|
||||
} from "../../../redux/projectsTrackerSlice";
|
||||
|
||||
import arrowDown from "../../../images/selectArrow.png"
|
||||
|
||||
import "./trackerModal.scss";
|
||||
|
||||
export const TrackerModal = ({
|
||||
active,
|
||||
setActive,
|
||||
selectedTab,
|
||||
defautlInput,
|
||||
titleProject,
|
||||
projectId,
|
||||
priorityTask
|
||||
}) => {
|
||||
const dispatch = useDispatch();
|
||||
const projectBoard = useSelector(getProjectBoard);
|
||||
const columnName = useSelector(getColumnName);
|
||||
const columnId = useSelector(getColumnId)
|
||||
|
||||
const modalType = useSelector(getValueModalType);
|
||||
const [projectName, setProjectName] = useState(defautlInput);
|
||||
const [valueColumn, setValueColumn] = useState("");
|
||||
const [nameProject, setNameProject] = useState("");
|
||||
const [valueTiket, setValueTiket] = useState("");
|
||||
const [descriptionTicket, setDescriptionTicket] = useState("");
|
||||
const [workers, setWorkers] = useState([])
|
||||
const [selectWorkersOpen, setSelectWorkersOpen] = useState(false)
|
||||
const [selectedWorker, setSelectedWorker] = useState(null)
|
||||
|
||||
function createTab() {
|
||||
if (!valueColumn) {
|
||||
return;
|
||||
}
|
||||
|
||||
apiRequest("/project-column/create-column", {
|
||||
method: "POST",
|
||||
data: {
|
||||
project_id: projectBoard.id,
|
||||
title: valueColumn,
|
||||
},
|
||||
}).then((res) => {
|
||||
dispatch(setProjectBoardFetch(projectBoard.id));
|
||||
});
|
||||
setValueColumn("");
|
||||
setActive(false);
|
||||
}
|
||||
|
||||
function createTiket() {
|
||||
if (!valueTiket || !descriptionTicket) {
|
||||
return;
|
||||
}
|
||||
|
||||
apiRequest("/task/create-task", {
|
||||
method: "POST",
|
||||
data: {
|
||||
project_id: projectBoard.id,
|
||||
title: valueTiket,
|
||||
description: descriptionTicket,
|
||||
status: 1,
|
||||
user_id: localStorage.getItem("id"),
|
||||
column_id: selectedTab,
|
||||
priority: priorityTask
|
||||
},
|
||||
}).then((res) => {
|
||||
dispatch(setProjectBoardFetch(projectBoard.id));
|
||||
});
|
||||
|
||||
setActive(false);
|
||||
setValueTiket("");
|
||||
setDescriptionTicket("");
|
||||
}
|
||||
|
||||
function editProject() {
|
||||
apiRequest("/project/update", {
|
||||
method: "PUT",
|
||||
data: {
|
||||
project_id: projectId,
|
||||
name: projectName,
|
||||
},
|
||||
}).then((res) => {
|
||||
setActive(false);
|
||||
dispatch(editProjectName({ id: projectId, name: projectName }));
|
||||
});
|
||||
}
|
||||
|
||||
function changeColumnName() {
|
||||
apiRequest("/project-column/update-column", {
|
||||
method: "PUT",
|
||||
data: {
|
||||
column_id: columnId,
|
||||
title: columnName
|
||||
}
|
||||
}).then((res) => {
|
||||
setActive(false);
|
||||
dispatch(editColumnName({id: columnId, title: columnName}))
|
||||
})
|
||||
}
|
||||
|
||||
function createProject() {
|
||||
if (nameProject === "") {
|
||||
return;
|
||||
} else {
|
||||
apiRequest("/project/create", {
|
||||
method: "POST",
|
||||
data: {
|
||||
user_id: localStorage.getItem("id"),
|
||||
name: nameProject,
|
||||
status: 19,
|
||||
},
|
||||
}).then((res) => {
|
||||
const result = { ...res, columns: [] };
|
||||
dispatch(setProject(result));
|
||||
setActive(false);
|
||||
setNameProject("");
|
||||
});
|
||||
}
|
||||
}
|
||||
|
||||
function addUserToProject() {
|
||||
apiRequest("/project/add-user", {
|
||||
method: "POST",
|
||||
data: {
|
||||
user_id: selectedWorker.id,
|
||||
project_id: projectBoard.id
|
||||
}
|
||||
}).then((el) => {
|
||||
setActive(false);
|
||||
selectedWorker(null)
|
||||
})
|
||||
}
|
||||
|
||||
useEffect(() => {
|
||||
modalType === "addWorker" ? apiRequest('/project/my-employee').then((el) => setWorkers(el.managerEmployees)) : ''
|
||||
}, [modalType])
|
||||
|
||||
return (
|
||||
<div
|
||||
className={active ? "modal-add active" : "modal-add"}
|
||||
onClick={() => setActive(false)}
|
||||
>
|
||||
<div className="modal-add__content" onClick={(e) => e.stopPropagation()}>
|
||||
{modalType === "addWorker" && (
|
||||
<div>
|
||||
<div className="title-project">
|
||||
<h4>Добавьте участника</h4>
|
||||
{/*<div className="input-container">*/}
|
||||
{/* <input*/}
|
||||
{/* className="name-project"*/}
|
||||
{/* value={emailWorker}*/}
|
||||
{/* 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'>
|
||||
{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>
|
||||
<button
|
||||
className="button-add"
|
||||
onClick={addUserToProject}
|
||||
>
|
||||
Добавить
|
||||
</button>
|
||||
</div>
|
||||
)}
|
||||
{modalType === "createTiketProject" && (
|
||||
<div>
|
||||
<div className="title-project">
|
||||
<h4>Введите название и описание задачи</h4>
|
||||
<div className="input-container">
|
||||
<input
|
||||
className="name-project"
|
||||
value={valueTiket}
|
||||
onChange={(e) => setValueTiket(e.target.value)}
|
||||
placeholder="Название задачи"
|
||||
/>
|
||||
</div>
|
||||
<div className="input-container">
|
||||
<input
|
||||
className="name-project"
|
||||
value={descriptionTicket}
|
||||
onChange={(e) => setDescriptionTicket(e.target.value)}
|
||||
placeholder="Описание задачи"
|
||||
/>
|
||||
</div>
|
||||
</div>
|
||||
<button className="button-add" onClick={createTiket}>
|
||||
Создать
|
||||
</button>
|
||||
</div>
|
||||
)}
|
||||
{modalType === "editProject" && (
|
||||
<div>
|
||||
<div className="title-project">
|
||||
<h4>Введите новое название</h4>
|
||||
<div className="input-container">
|
||||
<input
|
||||
className="name-project"
|
||||
value={projectName}
|
||||
onChange={(e) => setProjectName(e.target.value)}
|
||||
/>
|
||||
</div>
|
||||
</div>
|
||||
<button className="button-add" onClick={editProject}>
|
||||
Сохранить
|
||||
</button>
|
||||
</div>
|
||||
)}
|
||||
{modalType === "createProject" && (
|
||||
<div>
|
||||
<div className="title-project">
|
||||
<h4>{titleProject}</h4>
|
||||
<div className="input-container">
|
||||
<input
|
||||
className="name-project"
|
||||
value={nameProject}
|
||||
onChange={(e) => setNameProject(e.target.value)}
|
||||
/>
|
||||
</div>
|
||||
<button className="button-add" onClick={createProject}>
|
||||
Создать
|
||||
</button>
|
||||
</div>
|
||||
</div>
|
||||
)}
|
||||
{modalType === "addSubtask" && (
|
||||
<div>
|
||||
<div className="title-project subtask">
|
||||
<h4>
|
||||
Вы добавляете подзадачу{" "}
|
||||
<p>в колонку(id) задачи "{defautlInput}"</p>
|
||||
</h4>
|
||||
<p className="title-project__decs">Введите текст</p>
|
||||
<div>
|
||||
<textarea className="title-project__textarea"></textarea>
|
||||
</div>
|
||||
</div>
|
||||
<button className="button-add" onClick={(e) => e.preventDefault()}>
|
||||
Добавить
|
||||
</button>
|
||||
</div>
|
||||
)}
|
||||
{modalType === "createColumn" && (
|
||||
<div>
|
||||
<div className="title-project">
|
||||
<h4>Введите название колонки</h4>
|
||||
<div className="input-container">
|
||||
<input
|
||||
className="name-project"
|
||||
value={valueColumn}
|
||||
onChange={(e) => setValueColumn(e.target.value)}
|
||||
/>
|
||||
</div>
|
||||
</div>
|
||||
<button className="button-add" onClick={createTab}>
|
||||
Создать
|
||||
</button>
|
||||
</div>
|
||||
)}
|
||||
{modalType === "editColumn" && (
|
||||
<div>
|
||||
<div className="title-project">
|
||||
<h4>Введите новое название</h4>
|
||||
<div className="input-container">
|
||||
<input
|
||||
className="name-project"
|
||||
value={columnName}
|
||||
onChange={(e) => dispatch(setColumnName(e.target.value))}
|
||||
/>
|
||||
</div>
|
||||
</div>
|
||||
<button className="button-add" onClick={changeColumnName}>
|
||||
Сохранить
|
||||
</button>
|
||||
</div>
|
||||
)}
|
||||
|
||||
<span className="exit" onClick={() => setActive(false)}></span>
|
||||
</div>
|
||||
</div>
|
||||
);
|
||||
};
|
||||
|
||||
export default TrackerModal;
|
@ -20,7 +20,7 @@
|
||||
padding: 60px 60px 30px 60px;
|
||||
display: flex;
|
||||
flex-direction: column;
|
||||
align-items: flex-start;
|
||||
align-items: center;
|
||||
justify-content: center;
|
||||
}
|
||||
|
||||
@ -34,6 +34,7 @@
|
||||
height: 35px;
|
||||
background: #ffffff;
|
||||
border-radius: 8px;
|
||||
margin-top: 12px;
|
||||
}
|
||||
|
||||
h4 {
|
||||
@ -41,14 +42,13 @@
|
||||
font-size: 22px;
|
||||
line-height: 26px;
|
||||
color: #263238 !important;
|
||||
margin-bottom: 22px !important;
|
||||
}
|
||||
|
||||
&__decs {
|
||||
font-weight: 300;
|
||||
font-size: 12px;
|
||||
line-height: 14px;
|
||||
margin: -13px 0 16px 0;
|
||||
margin: 12px 0 16px 0;
|
||||
}
|
||||
|
||||
&__textarea {
|
||||
@ -61,6 +61,56 @@
|
||||
font-size: 15px;
|
||||
line-height: 18px;
|
||||
}
|
||||
|
||||
.select__worker {
|
||||
display: flex;
|
||||
justify-content: space-between;
|
||||
align-items: center;
|
||||
padding: 5px;
|
||||
background: white;
|
||||
border-radius: 5px;
|
||||
cursor: pointer;
|
||||
width: 100%;
|
||||
position: relative;
|
||||
|
||||
p {
|
||||
max-width: 150px;
|
||||
overflow: hidden;
|
||||
white-space: nowrap;
|
||||
text-overflow: ellipsis;
|
||||
font-size: 14px;
|
||||
}
|
||||
|
||||
img {
|
||||
transition: all 0.3s ease;
|
||||
width: 16px;
|
||||
height: 16px;
|
||||
}
|
||||
|
||||
&__dropDown {
|
||||
display: flex;
|
||||
flex-direction: column;
|
||||
position: absolute;
|
||||
width: 100%;
|
||||
padding: 5px;
|
||||
top: 35px;
|
||||
left: 0;
|
||||
background: white;
|
||||
border-radius: 5px;
|
||||
row-gap: 5px;
|
||||
|
||||
.worker {
|
||||
display: flex;
|
||||
justify-content: space-between;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
.open {
|
||||
.arrow {
|
||||
transform: rotate(180deg);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
.name-project {
|
||||
@ -73,7 +123,7 @@
|
||||
}
|
||||
|
||||
.button-add {
|
||||
margin: 20px 40% 0 0;
|
||||
margin: 20px;
|
||||
width: 130px;
|
||||
height: 37px;
|
||||
background: #52b709;
|
@ -1,4 +1,4 @@
|
||||
import React, { Children } from "react"
|
||||
import React from "react"
|
||||
import "./bookkeepingTemplete.css"
|
||||
import { Breadcrumps } from "../Breadcrumps/Breadcrumps"
|
||||
import { Sidebar } from "../Sidebar/Sidebar"
|
||||
|
@ -1,2 +1,2 @@
|
||||
export const FREQUENTLY_ASKED_QUESTIONS_ROUTE = '/frequently-asked-questions'
|
||||
export const FREQUENTLY_ASKED_QUESTION_ROUTE = '/frequently-asked-question'
|
||||
export const FREQUENTLY_ASKED_QUESTIONS_ROUTE = "/frequently-asked-questions";
|
||||
export const FREQUENTLY_ASKED_QUESTION_ROUTE = "/frequently-asked-question";
|
||||
|
10
src/images/blogArrow.svg
Normal file
@ -0,0 +1,10 @@
|
||||
<svg width="27" height="27" viewBox="0 0 27 27" fill="none" xmlns="http://www.w3.org/2000/svg" xmlns:xlink="http://www.w3.org/1999/xlink">
|
||||
<circle opacity="0.3" cx="13.5" cy="13.5" r="13.5" transform="matrix(-1 0 0 1 27 0)" fill="#807777"/>
|
||||
<rect x="18.5625" y="16.3125" width="10.0195" height="5.625" transform="rotate(180 18.5625 16.3125)" fill="url(#pattern0)"/>
|
||||
<defs>
|
||||
<pattern id="pattern0" patternContentUnits="objectBoundingBox" width="1" height="1">
|
||||
<use xlink:href="#image0_1082_1300" transform="matrix(0.0244088 0 0 0.0434783 -0.000381388 0)"/>
|
||||
</pattern>
|
||||
<image id="image0_1082_1300" width="41" height="23" xlink:href="data:image/png;base64,iVBORw0KGgoAAAANSUhEUgAAACkAAAAXCAMAAAChzpYZAAAAAXNSR0IB2cksfwAAAEJQTFRFAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAjuH28gAAABZ0Uk5TAEK0Cvb/vgaIlxC7oNwcwg/J2f1bS4SvOREAAABWSURBVHicY2CAA0YmZgaiACMLKxs7kQpZOUYV4lPIyUUYgBUSBRiYiFTIysDMBiS5eQgDBrBSXj5ifESSUn6ilQoIDi2lQkTlDqBSYRGiFAKViuKVBgCepwfqGDtvmgAAAABJRU5ErkJggg=="/>
|
||||
</defs>
|
||||
</svg>
|
After Width: | Height: | Size: 1.0 KiB |
BIN
src/images/cardArticleItem.png
Normal file
After Width: | Height: | Size: 88 KiB |
BIN
src/images/cardArticleItem2.png
Normal file
After Width: | Height: | Size: 107 KiB |
BIN
src/images/cardArticleItem3.png
Normal file
After Width: | Height: | Size: 74 KiB |
BIN
src/images/cardArticleItem4.png
Normal file
After Width: | Height: | Size: 112 KiB |
BIN
src/images/cardArticleItem5.png
Normal file
After Width: | Height: | Size: 94 KiB |
BIN
src/images/cardArticleItem6.png
Normal file
After Width: | Height: | Size: 81 KiB |
4
src/images/cardCalendar.svg
Normal file
After Width: | Height: | Size: 8.7 KiB |
BIN
src/images/mockImgArticle.png
Normal file
After Width: | Height: | Size: 449 KiB |
13
src/images/yandexZen.svg
Normal file
@ -0,0 +1,13 @@
|
||||
<svg width="39" height="39" viewBox="0 0 39 39" fill="none" xmlns="http://www.w3.org/2000/svg">
|
||||
<g clip-path="url(#clip0_1082_1494)">
|
||||
<path d="M0 19.021V17.1032C0.3039 16.8841 0.206332 16.5274 0.267112 16.2395C2.02653 7.82463 7.07288 2.56716 15.3933 0.428659C15.91 0.294303 16.5002 0.404667 16.9432 0C17.6358 0 18.3284 0 19.021 0C19.0513 0.0383874 19.1073 0.0783743 19.1057 0.116762C19.0386 3.38449 19.0354 6.66021 18.354 9.87836C17.3959 14.3953 14.736 17.2599 10.1599 18.274C7.99578 18.7538 5.80449 18.9602 3.59882 19.0114C2.40081 19.0402 1.19961 19.2113 0 19.021Z" fill="#040505"/>
|
||||
<path d="M0 20.2989C3.3541 20.2797 6.6986 20.3724 9.99992 21.0666C14.6448 22.0423 17.3847 24.8989 18.3524 29.4942C19.0034 32.5812 19.0338 35.7242 19.0977 38.8624C19.0977 38.9071 19.0481 38.9535 19.021 38.9999H16.9432C16.7721 38.6816 16.433 38.7632 16.1755 38.7184C8.40044 37.3812 2.16569 31.4424 0.454251 23.7649C0.326293 23.1939 0.415863 22.5541 0 22.0567V20.2989Z" fill="#040505"/>
|
||||
<path d="M38.9999 19.021C37.7284 19.1729 36.4552 19.0434 35.1868 18.9938C33.0355 18.9106 30.8874 18.7187 28.7921 18.1828C24.2416 17.0184 21.764 13.973 20.9419 9.4545C20.3741 6.32752 20.2637 3.16856 20.2989 0H21.7368C21.772 0.180741 21.9192 0.147152 22.0439 0.164746C30.0685 1.26678 36.3912 6.91773 38.4289 14.7664C38.6305 15.5437 38.6033 16.3754 38.9999 17.1016V19.0194V19.021Z" fill="#040505"/>
|
||||
<path d="M20.2992 39.0001C20.2912 35.7788 20.3984 32.5686 21.0622 29.3985C22.081 24.5329 25.1296 21.8154 29.9425 20.942C32.9383 20.3982 35.9645 20.2687 39.0003 20.2991V22.0569C38.6852 22.2296 38.7652 22.5671 38.7204 22.8246C37.3384 30.6685 31.4316 36.8456 23.6933 38.5555C23.0423 38.6994 22.3418 38.645 21.7372 38.9985H20.2992V39.0001Z" fill="#040505"/>
|
||||
</g>
|
||||
<defs>
|
||||
<clipPath id="clip0_1082_1494">
|
||||
<rect width="39" height="39" fill="white"/>
|
||||
</clipPath>
|
||||
</defs>
|
||||
</svg>
|
After Width: | Height: | Size: 1.8 KiB |
138
src/pages/Article/Article.jsx
Normal file
@ -0,0 +1,138 @@
|
||||
import React, { useState } from "react";
|
||||
import { Link } from "react-router-dom";
|
||||
|
||||
import AuthHeader from "../../components/AuthHeader/AuthHeader";
|
||||
import SideBar from "../../components/SideBar/SideBar";
|
||||
import { Footer } from "../../components/Footer/Footer";
|
||||
import { ProfileBreadcrumbs } from "../../components/ProfileBreadcrumbs/ProfileBreadcrumbs";
|
||||
import CardArticle from "../../components/UI/CardArticle/CardArticle";
|
||||
|
||||
import mockImgArticle from "../../images/mockImgArticle.png";
|
||||
import rightArrow from "../../images/right-arrow.png";
|
||||
import yandexZen from "../../images/yandexZen.svg";
|
||||
import cardCalendar from "../../images/cardCalendar.svg";
|
||||
import cardImg1 from "../../images/cardArticleItem.png";
|
||||
import cardImg2 from "../../images/cardArticleItem2.png";
|
||||
import cardImg3 from "../../images/cardArticleItem3.png";
|
||||
|
||||
import "./article.scss";
|
||||
|
||||
export const Article = ({}) => {
|
||||
const [article] = useState([
|
||||
{
|
||||
image: cardImg1,
|
||||
title: "Аутстаффинг джунов: почему это выгодно",
|
||||
data: "1 марта, 2023",
|
||||
},
|
||||
{
|
||||
image: cardImg2,
|
||||
title: "Аутстаффинг джунов: почему это выгодно",
|
||||
data: "1 марта, 2023",
|
||||
},
|
||||
{
|
||||
image: cardImg3,
|
||||
title: "Аутстаффинг джунов: почему это выгодно",
|
||||
data: "1 марта, 2023",
|
||||
},
|
||||
]);
|
||||
|
||||
return (
|
||||
<div className="article-blog">
|
||||
<AuthHeader />
|
||||
<SideBar />
|
||||
<div className="container">
|
||||
<div className="article-blog__breadCrumbs">
|
||||
<ProfileBreadcrumbs
|
||||
links={[
|
||||
{ name: "Главная", link: "/auth" },
|
||||
{ name: "Блог", link: "/blog" },
|
||||
{
|
||||
name: "Аутстаффинг джунов: почему это выгодно",
|
||||
link: "/blog",
|
||||
},
|
||||
]}
|
||||
/>
|
||||
</div>
|
||||
<div className="article-blog__title">
|
||||
<h1>Аутстаффинг джунов: почему это выгодно</h1>
|
||||
</div>
|
||||
|
||||
<div className="article-blog__return">
|
||||
<Link to={"/blog"} className="article-blog__return-link">
|
||||
<div className="article-blog__return-arrow">
|
||||
<img src={rightArrow} />
|
||||
</div>
|
||||
<span>вернуться к списку статей</span>
|
||||
</Link>
|
||||
</div>
|
||||
|
||||
<div className="article-blog__body">
|
||||
<div className="article-blog__body-text">
|
||||
<p>
|
||||
Нет, мы работаем только с юридическими лицами и индивидуальными
|
||||
предпринимателями и тщательно проверяем своих партнеров.
|
||||
Партнерами являются агентства, которые специализируются на
|
||||
оказании услуг в формате аутстафф-модели и обладают глубокой
|
||||
экспертизой в разработке и внедрении ИТ-проектов.
|
||||
</p>
|
||||
</div>
|
||||
<img src={mockImgArticle} className="article-blog__body-img" />
|
||||
<div className="article-blog__body-text">
|
||||
<p>
|
||||
С одной стороны, зарплаты в сфере разработки растут, с другой
|
||||
стороны, появляется огромное количество новичков, которые хотят
|
||||
легко и просто войти в ИТ-сферу на волне востребованности и
|
||||
больших зарплат. Разумеется, это приводит к осторожному отношению
|
||||
работодателя к выпускникам различных курсов. Нет такого курса,
|
||||
который даст на 100% готового джуна, слишком многое завязано на
|
||||
личной инициативе, обучаемости и желании.
|
||||
</p>
|
||||
<br />
|
||||
<p>
|
||||
В итоге получается, что взгляды работодателя и потенциального
|
||||
сотрудника расходятся: работодатель не хочет открывать ящик
|
||||
пандоры, на который нужно тратить время, а работник, только
|
||||
прошедший курсы, испытывает эффект завышенных ожиданий и имеет
|
||||
зачастую неадекватные запросы.
|
||||
</p>
|
||||
</div>
|
||||
<div className="article-blog__body-footer">
|
||||
<div className="yandex">
|
||||
<img src={yandexZen} />
|
||||
<div className="yandex__text">
|
||||
<p>Читать на Дзен</p>
|
||||
<span>dzen.ru</span>
|
||||
</div>
|
||||
</div>
|
||||
<div className="publication-date">
|
||||
<img src={cardCalendar} />
|
||||
<p>1 марта, 2023</p>
|
||||
</div>
|
||||
</div>
|
||||
<div className="more-articles">
|
||||
<h1>Читайте также</h1>
|
||||
<div className="more-articles__arrow">
|
||||
<img src={rightArrow} />
|
||||
</div>
|
||||
</div>
|
||||
<div className="blog__body">
|
||||
{article.map((item, index) => {
|
||||
return (
|
||||
<CardArticle
|
||||
images={item.image}
|
||||
title={item.title}
|
||||
data={item.data}
|
||||
key={index}
|
||||
id={index}
|
||||
/>
|
||||
);
|
||||
})}
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
<Footer />
|
||||
</div>
|
||||
);
|
||||
};
|
||||
|
||||
export default Article;
|
166
src/pages/Article/article.scss
Normal file
@ -0,0 +1,166 @@
|
||||
.article-blog {
|
||||
background: #f1f1f1;
|
||||
|
||||
&__breadCrumbs {
|
||||
margin-top: 30px;
|
||||
|
||||
@media (max-width: 1375px) {
|
||||
margin-top: 0;
|
||||
padding-top: 110px;
|
||||
}
|
||||
|
||||
@media (max-width: 600px) {
|
||||
display: none;
|
||||
}
|
||||
}
|
||||
|
||||
&__title {
|
||||
margin: 42px 0 44px 0;
|
||||
|
||||
h1 {
|
||||
font-weight: 500;
|
||||
font-size: 38px;
|
||||
line-height: 32px;
|
||||
}
|
||||
|
||||
@media (max-width: 600px) {
|
||||
padding-top: 80px;
|
||||
}
|
||||
}
|
||||
|
||||
&__return {
|
||||
display: flex;
|
||||
align-items: center;
|
||||
&-link {
|
||||
display: flex;
|
||||
align-items: center;
|
||||
}
|
||||
|
||||
span {
|
||||
font-weight: 400;
|
||||
font-size: 12px;
|
||||
line-height: 20px;
|
||||
color: #6f6f6f;
|
||||
margin-left: 19px;
|
||||
}
|
||||
|
||||
&-arrow {
|
||||
display: flex;
|
||||
align-items: center;
|
||||
justify-content: center;
|
||||
width: 48px;
|
||||
height: 48px;
|
||||
border-radius: 44px;
|
||||
background: #8dc63f78;
|
||||
|
||||
img {
|
||||
width: 45%;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
&__body {
|
||||
display: flex;
|
||||
flex-direction: column;
|
||||
|
||||
&-text {
|
||||
margin-top: 30px;
|
||||
padding: 33px;
|
||||
background: #ffffff;
|
||||
text-align: justify;
|
||||
border-radius: 12px;
|
||||
|
||||
p {
|
||||
font-style: normal;
|
||||
font-weight: 400;
|
||||
font-size: 20px;
|
||||
line-height: 24px;
|
||||
}
|
||||
}
|
||||
|
||||
&-img {
|
||||
margin-top: 16px;
|
||||
}
|
||||
|
||||
&-footer {
|
||||
margin: 30px 0 78px 25px;
|
||||
display: flex;
|
||||
|
||||
.yandex {
|
||||
display: flex;
|
||||
align-items: center;
|
||||
img {
|
||||
margin: 0;
|
||||
width: 39px;
|
||||
height: 39px;
|
||||
}
|
||||
|
||||
&__text {
|
||||
margin-left: 18px;
|
||||
p {
|
||||
font-weight: 500;
|
||||
font-size: 16px;
|
||||
line-height: 32px;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
.publication-date {
|
||||
margin-left: 76px;
|
||||
display: flex;
|
||||
align-items: center;
|
||||
|
||||
p {
|
||||
font-weight: 400;
|
||||
font-size: 14px;
|
||||
line-height: 20px;
|
||||
}
|
||||
|
||||
img {
|
||||
margin: 0 16px 0 0;
|
||||
}
|
||||
}
|
||||
|
||||
@media (max-width: 600px) {
|
||||
margin: 20px 0 20px 0;
|
||||
flex-direction: column;
|
||||
|
||||
.publication-date {
|
||||
margin: 0;
|
||||
}
|
||||
|
||||
.yandex {
|
||||
margin: 0 0 10px 0;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
.more-articles {
|
||||
display: flex;
|
||||
align-items: center;
|
||||
margin-bottom: 49px;
|
||||
|
||||
h1 {
|
||||
font-weight: 500;
|
||||
font-size: 33px;
|
||||
line-height: 32px;
|
||||
}
|
||||
|
||||
&__arrow {
|
||||
margin: 0 0 0 21px;
|
||||
display: flex;
|
||||
justify-content: center;
|
||||
align-items: center;
|
||||
width: 27px;
|
||||
height: 27px;
|
||||
background: #80777770;
|
||||
border-radius: 44px;
|
||||
|
||||
img {
|
||||
width: 50%;
|
||||
transform: rotate(180deg);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
106
src/pages/Blog/Blog.jsx
Normal file
@ -0,0 +1,106 @@
|
||||
import React, { useState } from "react";
|
||||
|
||||
import AuthHeader from "../../components/AuthHeader/AuthHeader";
|
||||
import SideBar from "../../components/SideBar/SideBar";
|
||||
import { ProfileBreadcrumbs } from "../../components/ProfileBreadcrumbs/ProfileBreadcrumbs";
|
||||
import { Footer } from "../../components/Footer/Footer";
|
||||
import CardArticle from "../../components/UI/CardArticle/CardArticle";
|
||||
|
||||
// import arrowRight from "../../images/arrowRight.png";
|
||||
import blogArrow from "../../images/blogArrow.svg";
|
||||
|
||||
import cardImg1 from "../../images/cardArticleItem.png";
|
||||
import cardImg2 from "../../images/cardArticleItem2.png";
|
||||
import cardImg3 from "../../images/cardArticleItem3.png";
|
||||
import cardImg4 from "../../images/cardArticleItem4.png";
|
||||
import cardImg5 from "../../images/cardArticleItem5.png";
|
||||
import cardImg6 from "../../images/cardArticleItem6.png";
|
||||
|
||||
import "./blog.scss";
|
||||
|
||||
export const Blog = ({}) => {
|
||||
const [article] = useState([
|
||||
{
|
||||
image: cardImg1,
|
||||
title: "Аутстаффинг джунов: почему это выгодно",
|
||||
data: "1 марта, 2023",
|
||||
},
|
||||
{
|
||||
image: cardImg2,
|
||||
title: "Аутстаффинг джунов: почему это выгодно",
|
||||
data: "1 марта, 2023",
|
||||
},
|
||||
{
|
||||
image: cardImg3,
|
||||
title: "Аутстаффинг джунов: почему это выгодно",
|
||||
data: "1 марта, 2023",
|
||||
},
|
||||
{
|
||||
image: cardImg4,
|
||||
title: "Аутстаффинг джунов: почему это выгодно",
|
||||
data: "1 марта, 2023",
|
||||
},
|
||||
{
|
||||
image: cardImg5,
|
||||
title: "Аутстаффинг джунов: почему это выгодно",
|
||||
data: "1 марта, 2023",
|
||||
},
|
||||
{
|
||||
image: cardImg6,
|
||||
title: "Аутстаффинг джунов: почему это выгодно",
|
||||
data: "1 марта, 2023",
|
||||
},
|
||||
]);
|
||||
|
||||
return (
|
||||
<div className="blog">
|
||||
<AuthHeader />
|
||||
<SideBar />
|
||||
|
||||
<div className="container">
|
||||
<div className="blog__breadCrumbs">
|
||||
<ProfileBreadcrumbs
|
||||
links={[
|
||||
{ name: "Главная", link: "/auth" },
|
||||
{ name: "Блог", link: "/blog" },
|
||||
]}
|
||||
/>
|
||||
</div>
|
||||
|
||||
<div className="blog__title">
|
||||
<div>
|
||||
<h1>Блог</h1>
|
||||
<img src={blogArrow} className="blog__title-arrow" />
|
||||
</div>
|
||||
|
||||
<h3>
|
||||
Из первых уст рассказываем о себе пользователям, делимся полезными и
|
||||
важными материалами, стремимся получать обратную связь
|
||||
</h3>
|
||||
</div>
|
||||
|
||||
<div className="blog__body">
|
||||
{article.map((item, index) => {
|
||||
return (
|
||||
<CardArticle
|
||||
images={item.image}
|
||||
title={item.title}
|
||||
data={item.data}
|
||||
key={index}
|
||||
id={index}
|
||||
/>
|
||||
);
|
||||
})}
|
||||
</div>
|
||||
|
||||
<div className="blog__load-more">
|
||||
<button>Загрузить еще</button>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<Footer />
|
||||
</div>
|
||||
);
|
||||
};
|
||||
|
||||
export default Blog;
|
77
src/pages/Blog/blog.scss
Normal file
@ -0,0 +1,77 @@
|
||||
.blog {
|
||||
background: #f1f1f1;
|
||||
|
||||
&__breadCrumbs {
|
||||
margin-top: 30px;
|
||||
|
||||
@media (max-width: 1375px) {
|
||||
margin-top: 0;
|
||||
padding-top: 100px;
|
||||
}
|
||||
|
||||
@media (max-width: 600px) {
|
||||
display: none;
|
||||
}
|
||||
}
|
||||
|
||||
&__title {
|
||||
display: flex;
|
||||
align-items: center;
|
||||
justify-content: center;
|
||||
margin-bottom: 40px;
|
||||
|
||||
h1 {
|
||||
font-weight: 500;
|
||||
font-size: 48px;
|
||||
line-height: 32px;
|
||||
}
|
||||
|
||||
h3 {
|
||||
font-weight: 400;
|
||||
font-size: 18px;
|
||||
line-height: 28px;
|
||||
text-align: justify;
|
||||
}
|
||||
|
||||
div {
|
||||
display: flex;
|
||||
}
|
||||
|
||||
&-arrow {
|
||||
margin: 0 27px 0 20px;
|
||||
}
|
||||
|
||||
@media (max-width: 600px) {
|
||||
padding-top: 110px;
|
||||
flex-direction: column;
|
||||
|
||||
div {
|
||||
margin: 0 0 15px 0;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
&__body {
|
||||
display: flex;
|
||||
flex-wrap: wrap;
|
||||
justify-content: space-around;
|
||||
}
|
||||
|
||||
&__load-more {
|
||||
display: flex;
|
||||
align-items: center;
|
||||
justify-content: center;
|
||||
margin-bottom: 35px;
|
||||
|
||||
button {
|
||||
background: #52b709;
|
||||
border-radius: 44px;
|
||||
width: 202px;
|
||||
height: 50px;
|
||||
color: white;
|
||||
font-size: 16px;
|
||||
line-height: 32px;
|
||||
border: none;
|
||||
}
|
||||
}
|
||||
}
|
@ -1,21 +0,0 @@
|
||||
import { useEffect, useState } from "react";
|
||||
import { Prism as SyntaxHighlighter } from 'react-syntax-highlighter';
|
||||
import { a11yDark } from 'react-syntax-highlighter/dist/esm/styles/prism';
|
||||
|
||||
export const CodeSnippetlighter = () => {
|
||||
const [codeString, setCodeString] = useState(``)
|
||||
//
|
||||
// useEffect(()=>{
|
||||
// fetch('/code.txt')
|
||||
// .then((r) => r.text())
|
||||
// .then(text => {
|
||||
// setCodeString(text)
|
||||
// })
|
||||
// }, [])
|
||||
|
||||
return (
|
||||
<SyntaxHighlighter language={"javascript"} style={a11yDark} wrapLongLines={false} customStyle={{fontSize:14}} showLineNumbers={true}>
|
||||
{codeString}
|
||||
</SyntaxHighlighter>
|
||||
);
|
||||
};
|
@ -1,13 +0,0 @@
|
||||
import React from 'react';
|
||||
import { BookkeepingTemplete } from "../components/features/bookkeeping/BookkeepingTemplete/BookkeepingTemplete"
|
||||
import { ContractContent } from "../components/features/bookkeeping/ContractContent/ContractContent"
|
||||
|
||||
export const ContractPage = () => {
|
||||
return (
|
||||
<div>
|
||||
<BookkeepingTemplete showBreadcrumps nameBreeadcrumps="Создание договора">
|
||||
<ContractContent />
|
||||
</BookkeepingTemplete>
|
||||
</div>
|
||||
)
|
||||
}
|
@ -1,13 +0,0 @@
|
||||
import React from 'react';
|
||||
import { BookkeepingTemplete } from "../components/features/bookkeeping/BookkeepingTemplete/BookkeepingTemplete"
|
||||
import { MoneyContent } from "../components/features/Money/MoneyContent"
|
||||
|
||||
export const MoneyPage = () => {
|
||||
return (
|
||||
<div>
|
||||
<BookkeepingTemplete>
|
||||
<MoneyContent></MoneyContent>
|
||||
</BookkeepingTemplete>
|
||||
</div>
|
||||
)
|
||||
}
|
@ -13,7 +13,7 @@ import { ProfileBreadcrumbs } from "../../components/ProfileBreadcrumbs/ProfileB
|
||||
import { Footer } from "../../components/Footer/Footer";
|
||||
import { Navigation } from "../../components/Navigation/Navigation";
|
||||
import { Loader } from "../../components/Loader/Loader";
|
||||
import ModalAdd from "../../components/UI/ModalAdd/ModalAdd";
|
||||
import ModalLayout from "../../components/UI/ModalLayout/ModalLayout";
|
||||
|
||||
import { apiRequest } from "../../api/request";
|
||||
import { getCorrectDate } from "../../components/Calendar/calendarHelper";
|
||||
@ -76,7 +76,7 @@ export const PartnerBid = () => {
|
||||
<div className="partnerBid">
|
||||
<ProfileHeader />
|
||||
<Navigation />
|
||||
<ModalAdd active={modalDelete} setActive={setModalDelete}>
|
||||
<ModalLayout active={modalDelete} setActive={setModalDelete}>
|
||||
<div className="title-project modal-title-delete">
|
||||
<h4>Подтверждение удаления</h4>
|
||||
<p className="title-project__decs modal-decs">
|
||||
@ -99,7 +99,7 @@ export const PartnerBid = () => {
|
||||
</button>
|
||||
</div>
|
||||
</div>
|
||||
</ModalAdd>
|
||||
</ModalLayout>
|
||||
|
||||
<div className="container">
|
||||
<ProfileBreadcrumbs
|
395
src/pages/ProjectTracker/ProjectTracker.js
Normal file
@ -0,0 +1,395 @@
|
||||
import React, { useEffect, useRef, useState } from "react";
|
||||
import { Link, useParams } from "react-router-dom";
|
||||
import { ProfileHeader } from "../../components/ProfileHeader/ProfileHeader";
|
||||
import { ProfileBreadcrumbs } from "../../components/ProfileBreadcrumbs/ProfileBreadcrumbs";
|
||||
import { Footer } from "../../components/Footer/Footer";
|
||||
import { Navigation } from "../../components/Navigation/Navigation";
|
||||
import { Loader } from "../../components/Loader/Loader";
|
||||
|
||||
import { useDispatch, useSelector } from "react-redux";
|
||||
import { apiRequest } from "../../api/request";
|
||||
import {
|
||||
getProjectBoard,
|
||||
getBoarderLoader,
|
||||
modalToggle,
|
||||
moveProjectTask,
|
||||
setProjectBoardFetch,
|
||||
setToggleTab,
|
||||
activeLoader,
|
||||
setColumnName,
|
||||
setColumnId,
|
||||
} from "../../redux/projectsTrackerSlice";
|
||||
|
||||
import ModalTicket from "../../components/UI/ModalTicket/ModalTicket";
|
||||
import TrackerModal from "../../components/UI/TrackerModal/TrackerModal";
|
||||
|
||||
import project from "../../images/trackerProject.svg";
|
||||
import tasks from "../../images/trackerTasks.svg";
|
||||
import archive from "../../images/archiveTracker.svg";
|
||||
import selectArrow from "../../images/select.svg";
|
||||
import commentsBoard from "../../images/commentsBoard.svg";
|
||||
import filesBoard from "../../images/filesBoard.svg";
|
||||
import arrow from "../../images/arrowCalendar.png";
|
||||
import del from "../../images/delete.svg";
|
||||
import edit from "../../images/edit.svg";
|
||||
|
||||
export const ProjectTracker = () => {
|
||||
const dispatch = useDispatch();
|
||||
const projectId = useParams();
|
||||
|
||||
const [openColumnSelect, setOpenColumnSelect] = useState({});
|
||||
const [selectedTab, setSelectedTab] = useState(0);
|
||||
const [priorityTask, setPriorityTask] = useState(0);
|
||||
const [wrapperHover, setWrapperHover] = useState({});
|
||||
const [modalAdd, setModalAdd] = useState(false);
|
||||
const [modalActiveTicket, setModalActiveTicket] = useState(false);
|
||||
const [selectedTicket, setSelectedTicket] = useState({});
|
||||
|
||||
const startWrapperIndexTest = useRef({});
|
||||
const projectBoard = useSelector(getProjectBoard);
|
||||
const loader = useSelector(getBoarderLoader);
|
||||
|
||||
useEffect(() => {
|
||||
dispatch(activeLoader());
|
||||
dispatch(setProjectBoardFetch(projectId.id));
|
||||
}, []);
|
||||
|
||||
useEffect(() => {
|
||||
if (Object.keys(projectBoard).length) {
|
||||
projectBoard.columns.forEach((column) => {
|
||||
setOpenColumnSelect((prevState) => ({
|
||||
...prevState,
|
||||
[column.id]: false,
|
||||
}));
|
||||
setWrapperHover((prevState) => ({ ...prevState, [column.id]: false }));
|
||||
});
|
||||
}
|
||||
}, [projectBoard]);
|
||||
|
||||
// function toggleMoreTasks(columnId) {
|
||||
// setTabTaskMok((prevArray) =>
|
||||
// prevArray.map((elem, index) => {
|
||||
// if (columnId === index) {
|
||||
// return { ...elem, open: !elem.open };
|
||||
// } else {
|
||||
// return elem;
|
||||
// }
|
||||
// })
|
||||
// );
|
||||
// }
|
||||
|
||||
function dragStartHandler(e, task, columnId) {
|
||||
startWrapperIndexTest.current = { task: task, index: columnId };
|
||||
setTimeout(() => {
|
||||
e.target.classList.add("tasks__board__item__hide");
|
||||
}, 0);
|
||||
}
|
||||
|
||||
function dragEndHandler(e) {
|
||||
setWrapperHover((prevState) => ({
|
||||
[prevState]: false,
|
||||
}));
|
||||
e.target.classList.remove("tasks__board__item__hide");
|
||||
}
|
||||
|
||||
function dragOverHandler(e) {
|
||||
e.preventDefault();
|
||||
}
|
||||
|
||||
function dragEnterHandler(columnId) {
|
||||
if (columnId === startWrapperIndexTest.current.index) {
|
||||
return;
|
||||
}
|
||||
|
||||
setWrapperHover((prevState) => ({
|
||||
[prevState]: false,
|
||||
[columnId]: true,
|
||||
}));
|
||||
}
|
||||
function dragDropHandler(e, columnId) {
|
||||
e.preventDefault();
|
||||
if (startWrapperIndexTest.current.index === columnId) {
|
||||
return;
|
||||
}
|
||||
|
||||
setWrapperHover((prevState) => ({
|
||||
[prevState]: false,
|
||||
}));
|
||||
|
||||
if (columnId !== startWrapperIndexTest.current.index) {
|
||||
dispatch(
|
||||
moveProjectTask({
|
||||
startWrapperIndex: startWrapperIndexTest.current,
|
||||
columnId,
|
||||
})
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
function selectedTabTask(columnId, length) {
|
||||
setSelectedTab(columnId);
|
||||
dispatch(modalToggle("createTiketProject"));
|
||||
setModalAdd(true);
|
||||
setPriorityTask(length + 1)
|
||||
}
|
||||
|
||||
function openTicket(e, task) {
|
||||
setSelectedTicket(task);
|
||||
setModalActiveTicket(true);
|
||||
}
|
||||
|
||||
function deleteColumn(id) {
|
||||
apiRequest("/project-column/update-column", {
|
||||
method: "PUT",
|
||||
data: {
|
||||
column_id: id,
|
||||
project_id: projectBoard.id,
|
||||
status: 0,
|
||||
},
|
||||
}).then((res) => {
|
||||
dispatch(setProjectBoardFetch(projectBoard.id));
|
||||
});
|
||||
}
|
||||
|
||||
return (
|
||||
<div className="tracker">
|
||||
<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 tab"
|
||||
onClick={() => dispatch(setToggleTab(1))}
|
||||
>
|
||||
<img src={project} alt="img" />
|
||||
<p>Проекты </p>
|
||||
</Link>
|
||||
<Link
|
||||
to="/profile/tracker"
|
||||
className="tab"
|
||||
onClick={() => dispatch(setToggleTab(2))}
|
||||
>
|
||||
<img src={tasks} alt="img" />
|
||||
<p>Все мои задачи</p>
|
||||
</Link>
|
||||
<Link
|
||||
to="/profile/tracker"
|
||||
className="tab"
|
||||
onClick={() => dispatch(setToggleTab(3))}
|
||||
>
|
||||
<img src={archive} alt="img" />
|
||||
<p>Архив</p>
|
||||
</Link>
|
||||
</div>
|
||||
<div className="tracker__tabs__content">
|
||||
<TrackerModal
|
||||
active={modalAdd}
|
||||
setActive={setModalAdd}
|
||||
selectedTab={selectedTab}
|
||||
priorityTask={priorityTask}
|
||||
/>
|
||||
|
||||
{loader && <Loader style="green" />}
|
||||
{!loader && (
|
||||
<div className="tracker__tabs__content__tasks tasks active__content">
|
||||
<div className="tasks__head">
|
||||
<div className="tasks__head__wrapper">
|
||||
<h4>Проект : {projectBoard.name}</h4>
|
||||
|
||||
<div className="tasks__head__add">
|
||||
<span
|
||||
onClick={() => {
|
||||
dispatch(modalToggle("createColumn"));
|
||||
setModalAdd(true);
|
||||
}}
|
||||
>
|
||||
+
|
||||
</span>
|
||||
<p>добавить колонку</p>
|
||||
</div>
|
||||
<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={() => {
|
||||
dispatch(modalToggle("addWorker"));
|
||||
setModalAdd(true);
|
||||
}}
|
||||
>
|
||||
+
|
||||
</span>
|
||||
<p>добавить участника</p>
|
||||
</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="tasks__head__back">
|
||||
<p>Вернуться на проекты</p>
|
||||
<img src={arrow} alt="arrow" />
|
||||
</Link>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
{Boolean(modalActiveTicket) && <ModalTicket
|
||||
active={modalActiveTicket}
|
||||
setActive={setModalActiveTicket}
|
||||
task={selectedTicket}
|
||||
projectId={projectBoard.id}
|
||||
projectName={projectBoard.name}
|
||||
/>}
|
||||
|
||||
<div className="tasks__container">
|
||||
{Boolean(projectBoard?.columns) &&
|
||||
Boolean(projectBoard.columns.length) &&
|
||||
projectBoard.columns.map((column) => {
|
||||
return (
|
||||
<div
|
||||
key={column.id}
|
||||
onDragOver={(e) => dragOverHandler(e)}
|
||||
onDragEnter={(e) => dragEnterHandler(column.id)}
|
||||
onDrop={(e) => dragDropHandler(e, column.id)}
|
||||
className={`tasks__board ${
|
||||
column.tasks.length >= 3 ? "tasks__board__more" : ""
|
||||
} ${
|
||||
wrapperHover[column.id] ? "tasks__board__hover" : ""
|
||||
}`}
|
||||
>
|
||||
<div className="board__head">
|
||||
{/*<span className={wrapperIndex === 3 ? "done" : ""}>*/}
|
||||
<span>{column.title}</span>
|
||||
<div>
|
||||
<span
|
||||
className="add"
|
||||
onClick={() => selectedTabTask(column.id, column.tasks.length)}
|
||||
>
|
||||
+
|
||||
</span>
|
||||
<span
|
||||
onClick={() => {
|
||||
setOpenColumnSelect((prevState) => ({
|
||||
...prevState,
|
||||
[column.id]: true,
|
||||
}));
|
||||
}}
|
||||
className="more"
|
||||
>
|
||||
...
|
||||
</span>
|
||||
</div>
|
||||
</div>
|
||||
{openColumnSelect[column.id] && (
|
||||
<div className="column__select">
|
||||
<div
|
||||
className="column__select__item"
|
||||
onClick={() => {
|
||||
setOpenColumnSelect((prevState) => ({
|
||||
...prevState,
|
||||
[column.id]: false,
|
||||
}));
|
||||
dispatch(modalToggle("editColumn"));
|
||||
dispatch(setColumnName(column.title))
|
||||
dispatch(setColumnId(column.id))
|
||||
setModalAdd(true);
|
||||
}}
|
||||
>
|
||||
<img src={edit} alt="edit" />
|
||||
<span>Изменить</span>
|
||||
</div>
|
||||
<div
|
||||
className="column__select__item"
|
||||
onClick={() => deleteColumn(column.id)}
|
||||
>
|
||||
<img src={del} alt="delete" />
|
||||
<span>Удалить</span>
|
||||
</div>
|
||||
</div>
|
||||
)}
|
||||
{column.tasks.map((task, index) => {
|
||||
// if (index > 2) {
|
||||
// if (!column.open) {
|
||||
// return;
|
||||
// }
|
||||
// }
|
||||
return (
|
||||
<div
|
||||
key={task.id}
|
||||
className="tasks__board__item"
|
||||
draggable={true}
|
||||
onDragStart={(e) =>
|
||||
dragStartHandler(e, task, column.id)
|
||||
}
|
||||
onDragEnd={(e) => dragEndHandler(e)}
|
||||
onClick={(e) => openTicket(e, task)}
|
||||
>
|
||||
<div className="tasks__board__item__title">
|
||||
<p>{task.title}</p>
|
||||
</div>
|
||||
<p className="tasks__board__item__description">
|
||||
{task.description}
|
||||
</p>
|
||||
<div className="tasks__board__item__info">
|
||||
<div className="tasks__board__item__info__more">
|
||||
<img src={commentsBoard} alt="commentsImg" />
|
||||
<span>{task.comment_count} коментариев</span>
|
||||
</div>
|
||||
<div className="tasks__board__item__info__more">
|
||||
<img src={filesBoard} alt="filesImg" />
|
||||
<span>{task.files} файлов</span>
|
||||
</div>
|
||||
{/*<div className="tasks__board__item__info__avatars">*/}
|
||||
{/* <img src={task.avatarCreated} alt="avatar" />*/}
|
||||
{/* <img src={task.avatarDo} alt="avatar" />*/}
|
||||
{/*</div>*/}
|
||||
</div>
|
||||
</div>
|
||||
);
|
||||
})}
|
||||
{/*{column.tasks.length > 3 && (*/}
|
||||
{/* <span*/}
|
||||
{/* className={*/}
|
||||
{/* column.open*/}
|
||||
{/* ? "lessItems openItems"*/}
|
||||
{/* : "moreItems openItems"*/}
|
||||
{/* }*/}
|
||||
{/* // onClick={() => toggleMoreTasks(column.id)}*/}
|
||||
{/* >*/}
|
||||
{/* {column.open ? "-" : "+"}*/}
|
||||
{/* </span>*/}
|
||||
{/*)}*/}
|
||||
</div>
|
||||
);
|
||||
})}
|
||||
{Boolean(projectBoard?.columns) &&
|
||||
!Boolean(projectBoard.columns.length) && (
|
||||
<div className="tasks__board__noItems">
|
||||
В проекте нет задач.
|
||||
</div>
|
||||
)}
|
||||
</div>
|
||||
</div>
|
||||
)}
|
||||
</div>
|
||||
</div>
|
||||
<Footer />
|
||||
</div>
|
||||
);
|
||||
};
|
@ -1,11 +0,0 @@
|
||||
import React from 'react';
|
||||
import { BookkeepingTemplete } from "../components/features/bookkeeping/BookkeepingTemplete/BookkeepingTemplete"
|
||||
import { TaxContent } from "../components/features/Taxes/TaxContent/TaxContent"
|
||||
|
||||
export const TaxPage = () => {
|
||||
return (
|
||||
<BookkeepingTemplete>
|
||||
<TaxContent></TaxContent>
|
||||
</BookkeepingTemplete>
|
||||
)
|
||||
}
|
@ -53,6 +53,7 @@
|
||||
padding: 12px 40px 15px 19px;
|
||||
cursor: pointer;
|
||||
align-items: center;
|
||||
color: black;
|
||||
|
||||
@media (max-width: 490px) {
|
||||
padding: 8px 20px 8px 14px;
|
||||
@ -250,6 +251,8 @@
|
||||
font-size: 14px;
|
||||
line-height: 17px;
|
||||
max-width: 120px;
|
||||
display: flex;
|
||||
align-items: center;
|
||||
}
|
||||
}
|
||||
|
||||
@ -286,7 +289,7 @@
|
||||
color: #252c32;
|
||||
border: 1px solid #dde2e4;
|
||||
background: white;
|
||||
left: -25px;
|
||||
left: -18px;
|
||||
z-index: 2;
|
||||
}
|
||||
|
||||
@ -294,8 +297,8 @@
|
||||
background: #00c5a8;
|
||||
color: white;
|
||||
font-size: 14px;
|
||||
left: -35px;
|
||||
z-index: 1;
|
||||
left: -30px;
|
||||
z-index: 2;
|
||||
}
|
||||
|
||||
p {
|
||||
@ -326,6 +329,7 @@
|
||||
cursor: pointer;
|
||||
display: flex;
|
||||
align-items: center;
|
||||
color: black;
|
||||
|
||||
p {
|
||||
font-weight: 400;
|
||||
@ -395,6 +399,9 @@
|
||||
border-radius: 6px;
|
||||
background: #ffffff;
|
||||
cursor: pointer;
|
||||
display: flex;
|
||||
flex-direction: column;
|
||||
justify-content: space-between;
|
||||
|
||||
&__hide {
|
||||
opacity: 0;
|
||||
@ -446,6 +453,8 @@
|
||||
|
||||
&__more {
|
||||
cursor: pointer;
|
||||
display: flex;
|
||||
align-items: center;
|
||||
span {
|
||||
font-weight: 500;
|
||||
font-size: 12px;
|
||||
@ -494,6 +503,37 @@
|
||||
&__more {
|
||||
padding-bottom: 50px;
|
||||
}
|
||||
|
||||
.column__select {
|
||||
position: absolute;
|
||||
padding: 15px;
|
||||
background: #e1fccf;
|
||||
border-radius: 12px;
|
||||
right: -20px;
|
||||
top: 5px;
|
||||
z-index: 7;
|
||||
row-gap: 10px;
|
||||
display: flex;
|
||||
flex-direction: column;
|
||||
|
||||
&__item {
|
||||
cursor: pointer;
|
||||
display: flex;
|
||||
align-content: center;
|
||||
img {
|
||||
margin-right: 5px;
|
||||
}
|
||||
|
||||
span {
|
||||
font-size: 14px;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
&__noItems {
|
||||
font-weight: 500;
|
||||
font-size: 25px;
|
||||
}
|
||||
}
|
||||
|
||||
.board {
|
||||
|
@ -1,175 +0,0 @@
|
||||
import React, {useEffect, useState} from 'react'
|
||||
import {Link, Navigate} from "react-router-dom";
|
||||
|
||||
import {useSelector} from "react-redux";
|
||||
import {getReportDate} from "../../redux/reportSlice";
|
||||
|
||||
import {Loader} from "../../components/Loader/Loader"
|
||||
import {ProfileHeader} from "../../components/ProfileHeader/ProfileHeader";
|
||||
import {ProfileBreadcrumbs} from "../../components/ProfileBreadcrumbs/ProfileBreadcrumbs"
|
||||
import {Footer} from "../../components/Footer/Footer";
|
||||
|
||||
import {apiRequest} from "../../api/request";
|
||||
import {getCorrectDate, getCreatedDate, hourOfNum} from '../../components/Calendar/calendarHelper'
|
||||
|
||||
import arrow from "../../images/right-arrow.png";
|
||||
import arrowSwitchDate from "../../images/arrowViewReport.png";
|
||||
|
||||
import './viewReport.scss'
|
||||
import { Navigation } from '../../components/Navigation/Navigation';
|
||||
|
||||
export const ViewReport = () => {
|
||||
if(localStorage.getItem('role_status') === '18') {
|
||||
return <Navigate to="/profile" replace/>
|
||||
}
|
||||
const reportDate = useSelector(getReportDate);
|
||||
|
||||
const [taskText, setTaskText] = useState([]);
|
||||
const [difficulties, setDifficulties] = useState([]);
|
||||
const [tomorrowTask, setTomorrowTask] = useState([]);
|
||||
const [totalHours, setTotalHours] = useState(0);
|
||||
const [reportDay] = useState(new Date (getCreatedDate(reportDate)));
|
||||
const [currentDay] = useState(new Date ());
|
||||
const [loader, setLoader] = useState(false);
|
||||
|
||||
function getReportFromDate(day) {
|
||||
setLoader(true);
|
||||
setTaskText([]);
|
||||
setDifficulties([]);
|
||||
setTomorrowTask([]);
|
||||
apiRequest(`reports/find-by-date?user_card_id=${localStorage.getItem('cardId')}&date=${day}`)
|
||||
.then(res => {
|
||||
let spendTime = 0;
|
||||
for (const item of res) {
|
||||
if(item.difficulties) {
|
||||
setDifficulties(prevArray => [...prevArray, item.difficulties])
|
||||
}
|
||||
if(item.tomorrow) {
|
||||
setTomorrowTask(prevArray => [...prevArray, item.tomorrow])
|
||||
}
|
||||
item.task.map((task) => {
|
||||
const taskInfo = {
|
||||
hours: task.hours_spent,
|
||||
task: task.task,
|
||||
id: task.id
|
||||
};
|
||||
if(task.hours_spent) {
|
||||
spendTime += Number(task.hours_spent)
|
||||
}
|
||||
setTaskText(prevArray => [...prevArray, taskInfo])
|
||||
})
|
||||
}
|
||||
setTotalHours(spendTime);
|
||||
setLoader(false)
|
||||
})
|
||||
}
|
||||
|
||||
function nextDay() {
|
||||
reportDay.setDate(reportDay.getDate() + 1);
|
||||
getReportFromDate(getCreatedDate(reportDay))
|
||||
}
|
||||
|
||||
function previousDay() {
|
||||
reportDay.setDate(reportDay.getDate() - 1);
|
||||
getReportFromDate(getCreatedDate(reportDay))
|
||||
}
|
||||
|
||||
useEffect(() => {
|
||||
getReportFromDate(getCreatedDate(reportDate))
|
||||
}, []);
|
||||
return (
|
||||
<div className='viewReport'>
|
||||
<ProfileHeader/>
|
||||
<Navigation />
|
||||
<div className='container'>
|
||||
<div className='viewReport__info'>
|
||||
<ProfileBreadcrumbs links={[{name: 'Главная', link: '/profile'},
|
||||
{name: 'Ваша отчетность', link: '/profile/calendar'},
|
||||
{name: 'Просмотр отчета за день', link: '/profile/view'}]}
|
||||
/>
|
||||
<h2 className='viewReport__title'>Ваши отчеты - <span>просмотр отчета за день</span></h2>
|
||||
<Link className='viewReport__back' to={`/profile/calendar`}>
|
||||
<img src={arrow} alt='arrow'/><p>Вернуться</p>
|
||||
</Link>
|
||||
<div className='viewReport__bar'>
|
||||
<h3 className='viewReport__bar__date'>{getCorrectDate(reportDay)}</h3>
|
||||
<p className='viewReport__bar__hours'>Вами потрачено на работу : <span>{totalHours} {hourOfNum(totalHours)}</span></p>
|
||||
{/*<div className='viewReport__bar__progressBar'>*/}
|
||||
{/* <span></span>*/}
|
||||
{/*</div>*/}
|
||||
{/*<p className='viewReport__bar__total'>122 часа из 160</p>*/}
|
||||
</div>
|
||||
</div>
|
||||
<div className='viewReport__switchDate'>
|
||||
<div className='viewReport__switchDate__prev switchDate' onClick={() => previousDay()}>
|
||||
<img src={arrowSwitchDate} alt='arrow'/>
|
||||
</div>
|
||||
<p>{getCorrectDate(reportDay)}</p>
|
||||
<div className={`viewReport__switchDate__next switchDate ${getCreatedDate(currentDay) === getCreatedDate(reportDay) ? 'disable' : ''}`} onClick={() => nextDay()}>
|
||||
<img src={arrowSwitchDate} alt='arrow'/>
|
||||
</div>
|
||||
</div>
|
||||
{loader &&
|
||||
<Loader width={75} height={75}/>
|
||||
}
|
||||
{Boolean(taskText.length) &&
|
||||
<div className='viewReport__content'>
|
||||
<div className='table__container'>
|
||||
<table className='viewReport__done'>
|
||||
<thead>
|
||||
<tr>
|
||||
<th><p>Какие задачи были выполнены?</p></th>
|
||||
<th><p>Время</p></th>
|
||||
</tr>
|
||||
</thead>
|
||||
<tbody>
|
||||
{taskText.length && taskText.map((task, index) => {
|
||||
return <tr key={task.id}>
|
||||
<td>
|
||||
<p>{index + 1}. {task.task}</p>
|
||||
</td>
|
||||
<td>
|
||||
<div className='viewReport__done__hours__item'>
|
||||
<span>{task.hours}</span>
|
||||
<p className='hours'>{hourOfNum(task.hours)} на задачу</p>
|
||||
</div>
|
||||
</td>
|
||||
</tr>
|
||||
})}
|
||||
<tr>
|
||||
<td></td>
|
||||
<td><span>Всего: {totalHours} {hourOfNum(totalHours)}</span></td>
|
||||
</tr>
|
||||
</tbody>
|
||||
</table>
|
||||
</div>
|
||||
{Boolean(difficulties.length) &&
|
||||
<div className='viewReport__item'>
|
||||
<h3>Какие сложности возникли?</h3>
|
||||
{difficulties.map((item, index) => {
|
||||
return <p key={index}>{item}</p>
|
||||
}
|
||||
)}
|
||||
</div>
|
||||
}
|
||||
{Boolean(tomorrowTask.length) &&
|
||||
<div className='viewReport__item'>
|
||||
<h3>Что планируется сделать завтра?</h3>
|
||||
{tomorrowTask.map((item, index) => {
|
||||
return <p key={index}>{item}</p>
|
||||
}
|
||||
)}
|
||||
</div>
|
||||
}
|
||||
</div>
|
||||
}
|
||||
{!Boolean(taskText.length) && !loader &&
|
||||
<div className='viewReport__noTask'>
|
||||
<p>В этот день вы <span>не заполняли</span> отчет</p>
|
||||
</div>
|
||||
}
|
||||
<Footer />
|
||||
</div>
|
||||
</div>
|
||||
)
|
||||
};
|
225
src/pages/ViewReport/ViewReport.jsx
Normal file
@ -0,0 +1,225 @@
|
||||
import React, { useEffect, useState } from "react";
|
||||
import { Link, Navigate, useParams } from "react-router-dom";
|
||||
|
||||
import { Loader } from "../../components/Loader/Loader";
|
||||
import { ProfileHeader } from "../../components/ProfileHeader/ProfileHeader";
|
||||
import { ProfileBreadcrumbs } from "../../components/ProfileBreadcrumbs/ProfileBreadcrumbs";
|
||||
import { Footer } from "../../components/Footer/Footer";
|
||||
import { Navigation } from "../../components/Navigation/Navigation";
|
||||
|
||||
import { apiRequest } from "../../api/request";
|
||||
import {
|
||||
getCorrectDate,
|
||||
getCreatedDate,
|
||||
hourOfNum,
|
||||
} from "../../components/Calendar/calendarHelper";
|
||||
|
||||
import arrow from "../../images/right-arrow.png";
|
||||
import arrowSwitchDate from "../../images/arrowViewReport.png";
|
||||
|
||||
import "./viewReport.scss";
|
||||
|
||||
export const ViewReport = () => {
|
||||
if (localStorage.getItem("role_status") === "18") {
|
||||
return <Navigate to="/profile" replace />;
|
||||
}
|
||||
|
||||
const dateReport = useParams();
|
||||
const [previousReportDay] = useState(new Date(dateReport.id));
|
||||
const [nextReportDay] = useState(new Date(dateReport.id));
|
||||
|
||||
const [taskText, setTaskText] = useState([]);
|
||||
const [difficulties, setDifficulties] = useState([]);
|
||||
const [tomorrowTask, setTomorrowTask] = useState([]);
|
||||
const [totalHours, setTotalHours] = useState(0);
|
||||
const [currentDay] = useState(new Date());
|
||||
const [loader, setLoader] = useState(false);
|
||||
|
||||
function getReportFromDate(day) {
|
||||
setLoader(true);
|
||||
setTaskText([]);
|
||||
setDifficulties([]);
|
||||
setTomorrowTask([]);
|
||||
apiRequest(
|
||||
`reports/find-by-date?user_card_id=${localStorage.getItem(
|
||||
"cardId"
|
||||
)}&date=${day}`
|
||||
).then((res) => {
|
||||
let spendTime = 0;
|
||||
for (const item of res) {
|
||||
if (item.difficulties) {
|
||||
setDifficulties((prevArray) => [...prevArray, item.difficulties]);
|
||||
}
|
||||
if (item.tomorrow) {
|
||||
setTomorrowTask((prevArray) => [...prevArray, item.tomorrow]);
|
||||
}
|
||||
item.task.map((task) => {
|
||||
const taskInfo = {
|
||||
hours: task.hours_spent,
|
||||
task: task.task,
|
||||
id: task.id,
|
||||
};
|
||||
if (task.hours_spent) {
|
||||
spendTime += Number(task.hours_spent);
|
||||
}
|
||||
setTaskText((prevArray) => [...prevArray, taskInfo]);
|
||||
});
|
||||
}
|
||||
setTotalHours(spendTime);
|
||||
setLoader(false);
|
||||
});
|
||||
previousReportDay.setDate(previousReportDay.getDate() - 1);
|
||||
nextReportDay.setDate(nextReportDay.getDate() + 1);
|
||||
}
|
||||
|
||||
function nextDay() {
|
||||
getReportFromDate(getCreatedDate(nextReportDay));
|
||||
previousReportDay.setDate(previousReportDay.getDate() + 2);
|
||||
}
|
||||
|
||||
function previousDay() {
|
||||
getReportFromDate(getCreatedDate(previousReportDay));
|
||||
nextReportDay.setDate(nextReportDay.getDate() - 2);
|
||||
}
|
||||
|
||||
useEffect(() => {
|
||||
getReportFromDate(dateReport.id);
|
||||
}, []);
|
||||
|
||||
return (
|
||||
<div className="viewReport">
|
||||
<ProfileHeader />
|
||||
<Navigation />
|
||||
<div className="container">
|
||||
<div className="viewReport__info">
|
||||
<ProfileBreadcrumbs
|
||||
links={[
|
||||
{ name: "Главная", link: "/profile" },
|
||||
{ name: "Ваша отчетность", link: "/profile/calendar" },
|
||||
{ name: "Просмотр отчета за день", link: "/profile/view" },
|
||||
]}
|
||||
/>
|
||||
<h2 className="viewReport__title">
|
||||
Ваши отчеты - <span>просмотр отчета за день</span>
|
||||
</h2>
|
||||
<Link className="viewReport__back" to={`/profile/calendar`}>
|
||||
<img src={arrow} alt="arrow" />
|
||||
<p>Вернуться</p>
|
||||
</Link>
|
||||
<div className="viewReport__bar">
|
||||
<h3 className="viewReport__bar__date">
|
||||
{getCorrectDate(dateReport.id)}
|
||||
</h3>
|
||||
<p className="viewReport__bar__hours">
|
||||
Вами потрачено на работу :{" "}
|
||||
<span>
|
||||
{totalHours} {hourOfNum(totalHours)}
|
||||
</span>
|
||||
</p>
|
||||
</div>
|
||||
</div>
|
||||
<div className="viewReport__switchDate">
|
||||
<div
|
||||
onClick={() => {
|
||||
previousDay();
|
||||
}}
|
||||
>
|
||||
<Link to={`../view/${getCreatedDate(previousReportDay)}`}>
|
||||
<div className="viewReport__switchDate__prev switchDate">
|
||||
<img src={arrowSwitchDate} alt="arrow" />
|
||||
</div>
|
||||
</Link>
|
||||
</div>
|
||||
|
||||
<p>{getCorrectDate(dateReport.id)}</p>
|
||||
|
||||
<div
|
||||
onClick={() => nextDay()}
|
||||
className={`${
|
||||
getCreatedDate(currentDay) === dateReport.id ? "disable" : ""
|
||||
}`}
|
||||
>
|
||||
<Link to={`../view/${getCreatedDate(nextReportDay)}`}>
|
||||
<div className={`viewReport__switchDate__next switchDate`}>
|
||||
<img src={arrowSwitchDate} alt="arrow" />
|
||||
</div>
|
||||
</Link>
|
||||
</div>
|
||||
</div>
|
||||
{loader && <Loader width={75} height={75} />}
|
||||
{Boolean(taskText.length) && (
|
||||
<div className="viewReport__content">
|
||||
<div className="table__container">
|
||||
<table className="viewReport__done">
|
||||
<thead>
|
||||
<tr>
|
||||
<th>
|
||||
<p>Какие задачи были выполнены?</p>
|
||||
</th>
|
||||
<th>
|
||||
<p>Время</p>
|
||||
</th>
|
||||
</tr>
|
||||
</thead>
|
||||
<tbody>
|
||||
{taskText.length &&
|
||||
taskText.map((task, index) => {
|
||||
return (
|
||||
<tr key={task.id}>
|
||||
<td>
|
||||
<p>
|
||||
{index + 1}. {task.task}
|
||||
</p>
|
||||
</td>
|
||||
<td>
|
||||
<div className="viewReport__done__hours__item">
|
||||
<span>{task.hours}</span>
|
||||
<p className="hours">
|
||||
{hourOfNum(task.hours)} на задачу
|
||||
</p>
|
||||
</div>
|
||||
</td>
|
||||
</tr>
|
||||
);
|
||||
})}
|
||||
<tr>
|
||||
<td></td>
|
||||
<td>
|
||||
<span>
|
||||
Всего: {totalHours} {hourOfNum(totalHours)}
|
||||
</span>
|
||||
</td>
|
||||
</tr>
|
||||
</tbody>
|
||||
</table>
|
||||
</div>
|
||||
{Boolean(difficulties.length) && (
|
||||
<div className="viewReport__item">
|
||||
<h3>Какие сложности возникли?</h3>
|
||||
{difficulties.map((item, index) => {
|
||||
return <p key={index}>{item}</p>;
|
||||
})}
|
||||
</div>
|
||||
)}
|
||||
{Boolean(tomorrowTask.length) && (
|
||||
<div className="viewReport__item">
|
||||
<h3>Что планируется сделать завтра?</h3>
|
||||
{tomorrowTask.map((item, index) => {
|
||||
return <p key={index}>{item}</p>;
|
||||
})}
|
||||
</div>
|
||||
)}
|
||||
</div>
|
||||
)}
|
||||
{!Boolean(taskText.length) && !loader && (
|
||||
<div className="viewReport__noTask">
|
||||
<p>
|
||||
В этот день вы <span>не заполняли</span> отчет
|
||||
</p>
|
||||
</div>
|
||||
)}
|
||||
<Footer />
|
||||
</div>
|
||||
</div>
|
||||
);
|
||||
};
|
@ -1 +0,0 @@
|
||||
Hellow
|
@ -1,34 +1,114 @@
|
||||
import { createSlice } from "@reduxjs/toolkit";
|
||||
import { createAsyncThunk, createSlice } from "@reduxjs/toolkit";
|
||||
import { apiRequest } from "../api/request";
|
||||
|
||||
const initialState = {
|
||||
project: [
|
||||
{
|
||||
name: "Разработка трекера",
|
||||
count: 4,
|
||||
},
|
||||
{
|
||||
name: "Кинотеатр",
|
||||
count: 4,
|
||||
},
|
||||
{
|
||||
name: "Проект страхование",
|
||||
count: 4,
|
||||
},
|
||||
],
|
||||
projects: [],
|
||||
projectBoard: {},
|
||||
toggleTab: 1,
|
||||
modalType: "",
|
||||
boardLoader: false,
|
||||
columnName: "",
|
||||
columnId: 0
|
||||
};
|
||||
|
||||
export const setProjectBoardFetch = createAsyncThunk("userInfo", (id) =>
|
||||
apiRequest(`/project/get-project?project_id=${id}&expand=columns`)
|
||||
);
|
||||
|
||||
export const projectsTrackerSlice = createSlice({
|
||||
name: "projectsTracker",
|
||||
initialState,
|
||||
reducers: {
|
||||
setAllProjects: (state, action) => {
|
||||
state.projects = action.payload;
|
||||
},
|
||||
setProject: (state, action) => {
|
||||
state.project.push(action.payload);
|
||||
state.projects.push(action.payload);
|
||||
},
|
||||
setToggleTab: (state, action) => {
|
||||
state.toggleTab = action.payload;
|
||||
},
|
||||
deleteProject: (state, action) => {
|
||||
state.projects.forEach((project) => {
|
||||
if (project.id === action.payload.id) {
|
||||
project.status = 10;
|
||||
}
|
||||
});
|
||||
},
|
||||
activeLoader: (state) => {
|
||||
state.boardLoader = true;
|
||||
},
|
||||
moveProjectTask: (state, action) => {
|
||||
state.projectBoard.columns.forEach((column, index) => {
|
||||
if (column.id === action.payload.columnId) {
|
||||
column.tasks.push(action.payload.startWrapperIndex.task);
|
||||
apiRequest(`/task/update-task`, {
|
||||
method: "PUT",
|
||||
data: {
|
||||
task_id: action.payload.startWrapperIndex.task.id,
|
||||
column_id: column.id,
|
||||
},
|
||||
}).then((res) => {});
|
||||
}
|
||||
if (column.id === action.payload.startWrapperIndex.index) {
|
||||
state.projectBoard.columns[index].tasks = column.tasks.filter(
|
||||
(task) => task.id !== action.payload.startWrapperIndex.task.id
|
||||
);
|
||||
}
|
||||
});
|
||||
},
|
||||
setColumnName: (state, action) => {
|
||||
state.columnName = action.payload
|
||||
},
|
||||
setColumnId: (state, action) => {
|
||||
state.columnId = action.payload
|
||||
},
|
||||
editProjectName: (state, action) => {
|
||||
state.projects.forEach((project) => {
|
||||
if (project.id === action.payload.id) {
|
||||
project.name = action.payload.name;
|
||||
}
|
||||
});
|
||||
},
|
||||
editColumnName: (state, action) => {
|
||||
state.projectBoard.columns.forEach((column) => {
|
||||
if (column.id === action.payload.id) {
|
||||
column.title = action.payload.title
|
||||
}
|
||||
})
|
||||
},
|
||||
modalToggle: (state, action) => {
|
||||
state.modalType = action.payload;
|
||||
},
|
||||
},
|
||||
extraReducers: {
|
||||
[setProjectBoardFetch.fulfilled]: (state, action) => {
|
||||
state.projectBoard = action.payload;
|
||||
state.boardLoader = false;
|
||||
},
|
||||
},
|
||||
});
|
||||
|
||||
export const { setProject } = projectsTrackerSlice.actions;
|
||||
export const {
|
||||
setProject,
|
||||
setColumnName,
|
||||
deleteProject,
|
||||
setAllProjects,
|
||||
moveProjectTask,
|
||||
setToggleTab,
|
||||
modalToggle,
|
||||
activeLoader,
|
||||
editProjectName,
|
||||
editColumnName,
|
||||
setColumnId
|
||||
} = projectsTrackerSlice.actions;
|
||||
|
||||
export const getProjects = (state) => state.tracker.project;
|
||||
export const getProjects = (state) => state.tracker.projects;
|
||||
export const getProjectBoard = (state) => state.tracker.projectBoard;
|
||||
export const getToggleTab = (state) => state.tracker.toggleTab;
|
||||
export const getValueModalType = (state) => state.tracker.modalType;
|
||||
export const getBoarderLoader = (state) => state.tracker.boardLoader;
|
||||
export const getColumnName = (state) => state.tracker.columnName;
|
||||
export const getColumnId = (state) => state.tracker.columnId;
|
||||
|
||||
export default projectsTrackerSlice.reducer;
|
||||
|
@ -1,13 +1,14 @@
|
||||
import { createSlice } from '@reduxjs/toolkit';
|
||||
import { createSlice } from "@reduxjs/toolkit";
|
||||
|
||||
const initialState = {
|
||||
dateSelected: '',
|
||||
reportDate: '',
|
||||
requestDates: ''
|
||||
dateSelected: "",
|
||||
reportDate: "",
|
||||
requestDates: "",
|
||||
sendRequest: "",
|
||||
};
|
||||
|
||||
export const reportSlice = createSlice({
|
||||
name: 'report',
|
||||
name: "report",
|
||||
initialState,
|
||||
reducers: {
|
||||
dateSelected: (state, action) => {
|
||||
@ -17,17 +18,23 @@ export const reportSlice = createSlice({
|
||||
state.reportDate = action.payload;
|
||||
},
|
||||
setRequestDate: (state, action) => {
|
||||
state.requestDates = action.payload
|
||||
}
|
||||
state.requestDates = action.payload;
|
||||
},
|
||||
setSendRequest: (state, action) => {
|
||||
state.sendRequest = action.payload;
|
||||
},
|
||||
},
|
||||
});
|
||||
|
||||
export const { dateSelected, setReportDate, setRequestDate} = reportSlice.actions;
|
||||
export const { dateSelected, setReportDate, setRequestDate, setSendRequest } =
|
||||
reportSlice.actions;
|
||||
|
||||
export const selectDate = (state) => state.report.dateSelected;
|
||||
|
||||
export const getReportDate = (state) => state.report.reportDate;
|
||||
|
||||
export const getRequestDates = (state) => state.report.requestDates
|
||||
export const getRequestDates = (state) => state.report.requestDates;
|
||||
|
||||
export const getSendRequest = (state) => state.report.sendRequest;
|
||||
|
||||
export default reportSlice.reducer;
|
||||
|