landing #32

Merged
nik.polishuk merged 3 commits from landing into main 2024-04-16 17:04:04 +03:00
15 changed files with 1481 additions and 1212 deletions
Showing only changes of commit a1da184bb0 - Show all commits

View File

@ -1,4 +1,5 @@
import React, { useState } from "react";
import { Link, useNavigate } from "react-router-dom";
import { useFormValidation } from "@hooks/useFormValidation";
import { useNotification } from "@hooks/useNotification";
@ -17,6 +18,7 @@ import "./modalRegistration.scss";
export const ModalRegistration = ({ active, setActive }) => {
const [loader, setLoader] = useState(false);
const [isPartner, setIsPartner] = useState(false);
const navigate = useNavigate();
const fields = {
username: "",
@ -145,6 +147,7 @@ export const ModalRegistration = ({ active, setActive }) => {
onClick={async (e) => {
e.preventDefault();
await handleSubmit(e);
navigate("/welcome-page");
}}
styles="button-box__submit"
>

View File

@ -719,7 +719,6 @@
.button-add {
margin: 0 30px;
align-self: flex-end;
}
}

View File

@ -0,0 +1,193 @@
import React, { useEffect, useState } from "react";
import { useDispatch } from "react-redux";
import { movePositionProjectTask } from "@redux/projectsTrackerSlice";
import { getCorrectDate } from "@utils/calendarHelper";
import { removeLast, urlForLocal } from "@utils/helper";
import TrackerSelectColumn from "@components/TrackerSelectColumn/TrackerSelectColumn";
import commentsBoard from "assets/icons/commentsBoard.svg";
import filesBoard from "assets/icons/filesBoard.svg";
import avatarMok from "assets/images/avatarMok.png";
import "./trackerCardTask.scss";
const TrackerCardTask = ({
task,
projectBoard,
titleColor,
column,
openTicket,
startWrapperIndexTest,
setWrapperHover
}) => {
const dispatch = useDispatch();
const [taskHover, setTaskHover] = useState({});
const priority = {
2: "Высокий",
1: "Средний",
0: "Низкий"
};
const priorityClass = {
2: "high",
1: "middle",
0: "low"
};
function dragDropTaskHandler(e, task, column) {
e.preventDefault();
if (task.id === startWrapperIndexTest.current.task.id) {
return;
}
const finishTask = column.tasks.indexOf(task);
dispatch(
movePositionProjectTask({
startTask: startWrapperIndexTest.current.task,
finishTask: task,
finishIndex: finishTask
})
);
}
function dragOverTaskHandler(e, task) {
e.preventDefault();
if (startWrapperIndexTest.current.task.id === task.id) {
return;
}
setTaskHover((prevState) => ({ [prevState]: false, [task.id]: true }));
}
function dragStartHandler(e, task, columnId) {
startWrapperIndexTest.current = { task: task, index: columnId };
}
function dragLeaveTaskHandler() {
setTaskHover((prevState) => ({ [prevState]: false }));
}
function dragEndTaskHandler() {
setTaskHover((prevState) => ({ [prevState]: false }));
setWrapperHover((prevState) => ({
[prevState]: false
}));
}
useEffect(() => {
const tasksHover = {};
const columnHover = {};
if (Object.keys(projectBoard).length) {
projectBoard.columns.forEach((column) => {
columnHover[column.id] = false;
column.tasks.forEach((task) => (tasksHover[task.id] = false));
});
}
setWrapperHover(columnHover);
setTaskHover(tasksHover);
}, [projectBoard]);
return (
<div
key={task.id}
className={`tasks__board__item ${
taskHover[task.id] ? "task__hover" : ""
}`}
draggable={true}
onDragStart={(e) => dragStartHandler(e, task, column.id)}
onDragOver={(e) => dragOverTaskHandler(e, task)}
onDragLeave={(e) => dragLeaveTaskHandler(e)}
onDragEnd={() => dragEndTaskHandler()}
onDrop={(e) => dragDropTaskHandler(e, task, column)}
onClick={(e) => openTicket(e, task)}
>
<div
className="tasks__board__item__title"
onClick={() => {
if (window.innerWidth < 985) {
window.location.replace(`/tracker/task/${task.id}`);
}
}}
>
<p className="task__board__item__title">{task.title}</p>
</div>
<p
dangerouslySetInnerHTML={{
__html: task.description
}}
className="tasks__board__item__description"
></p>
{Boolean(task.mark.length) && (
<div className="tasks__board__item__tags">
{task.mark.map((tag) => {
return (
<div
className="tag-item"
key={tag.id}
style={{ background: tag.color }}
>
<p>{tag.slug}</p>
</div>
);
})}
</div>
)}
<div className="tasks__board__item__container">
{typeof task.execution_priority === "number" && (
<div className="tasks__board__item__priority">
<p></p>
<span className={priorityClass[task.execution_priority]}>
{priority[task.execution_priority]}
</span>
</div>
)}
{task.dead_line && (
<div className="tasks__board__item__dead-line">
<p></p>
<span style={{ color: titleColor }}>
{getCorrectDate(task.dead_line)}
</span>
</div>
)}
</div>
<div className="tasks__board__item__info">
<div className="tasks__board__item__executor">
<img
src={
task.executor?.avatar
? urlForLocal(task.executor?.avatar)
: avatarMok
}
alt="avatar"
/>
<span>
{removeLast(task.executor?.fio) || "Исполнитель не назначен"}
</span>
</div>
<div className="tasks__board__item__info__tags">
<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.file_count}</span>
</div>
</div>
</div>
<TrackerSelectColumn
columns={projectBoard.columns.filter((item) => item.id !== column.id)}
currentColumn={column}
task={task}
/>
</div>
);
};
export default TrackerCardTask;

View File

@ -0,0 +1,365 @@
.tasks {
&__board {
background: #f5f7f9;
box-shadow: 0px 2px 5px rgba(60, 66, 87, 0.04),
0px 0px 0px 1px rgba(60, 66, 87, 0.08), 0px 1px 1px rgba(0, 0, 0, 0.06);
border-radius: 8px;
padding: 12px 10px 12px 8px;
width: 360px;
display: flex;
flex-direction: column;
row-gap: 10px;
height: fit-content;
position: relative;
transition: all 0.3s ease;
transform: scaleY(-1);
min-height: 815px;
@media (max-width: 900px) {
min-width: auto;
width: 100%;
max-width: none;
transform: scaleX(1);
}
.tasks-container {
display: flex;
flex-direction: column;
row-gap: 8px;
max-height: 750px;
overflow: auto;
padding: 5px;
&::-webkit-scrollbar {
width: 3px;
border-radius: 10px;
}
&::-webkit-scrollbar-thumb {
background: #cbd9f9;
border-radius: 20px;
}
&::-webkit-scrollbar-track {
background: #c5c0c6;
border-radius: 20px;
}
}
&__hover {
box-shadow: 0px 2px 10px #9cc480, 0px 0px 0px 1px rgba(60, 66, 87, 0.08),
0px 1px 1px rgba(0, 0, 0, 0.06);
}
.task__hover {
box-shadow: 0 0 5px gray;
}
&__item {
width: 328px;
padding: 6px 10px 8px 10px;
position: relative;
box-shadow: 0px 3px 2px -2px rgba(0, 0, 0, 0.06),
0px 5px 3px -2px rgba(0, 0, 0, 0.02);
border-radius: 6px;
background: #ffffff;
cursor: pointer;
display: flex;
flex-direction: column;
justify-content: space-between;
transition: 0.4s;
&:hover {
transform: scale(1.025);
transition: 0.3s;
}
@media (max-width: 900px) {
width: 100%;
max-height: none;
&:hover {
transform: none;
}
}
&__hide {
opacity: 0;
}
&__title {
display: flex;
justify-content: space-between;
position: relative;
p {
color: #1a1919;
font-weight: 500;
font-size: 16px;
line-height: 20px;
margin-bottom: 0;
max-height: 100px;
overflow: hidden;
text-overflow: ellipsis;
-webkit-line-clamp: 3;
}
span {
cursor: pointer;
display: flex;
border-radius: 6px;
align-items: center;
justify-content: center;
font-size: 20px;
padding-bottom: 10px;
width: 24px;
height: 24px;
border: 1px solid #dddddd;
}
}
&__description {
margin: 4px 0;
color: #5c6165;
font-weight: 400;
font-size: 14px;
line-height: 120%;
max-height: 100px;
overflow: hidden;
text-overflow: ellipsis;
display: -webkit-box;
-webkit-line-clamp: 2;
-webkit-box-orient: vertical;
}
&__container {
display: flex;
justify-content: space-between;
}
&__info {
display: flex;
column-gap: 10px;
align-items: center;
justify-content: space-between;
pointer-events: none;
margin-top: 5px;
&__tags {
display: flex;
column-gap: 5px;
}
&__more {
cursor: pointer;
display: flex;
align-items: center;
span {
font-weight: 500;
font-size: 12px;
line-height: 15px;
color: #6e7c87;
margin-left: 5px;
}
}
&__avatars {
position: relative;
img {
position: relative;
}
img:first-child {
right: -15px;
z-index: 2;
}
}
}
&__priority {
display: flex;
align-items: center;
column-gap: 5px;
margin-top: 3px;
p {
font-weight: 500;
font-size: 14px;
}
span {
font-weight: 500;
font-size: 14px;
}
.high {
color: red;
}
.middle {
color: #cece00;
}
.low {
color: green;
}
}
&__dead-line {
display: flex;
align-items: center;
column-gap: 5px;
p {
font-weight: 500;
font-size: 14px;
color: #1458dd;
}
span {
font-weight: 500;
font-size: 14px;
}
img {
margin-top: -2px;
width: 18px;
}
}
&__executor {
display: flex;
align-items: center;
font-size: 14px;
font-weight: 500;
column-gap: 5px;
span {
max-width: 210px;
overflow: hidden;
text-overflow: ellipsis;
white-space: nowrap;
}
img {
width: 25px;
height: 25px;
}
}
&__tags {
display: flex;
flex-wrap: wrap;
column-gap: 6px;
row-gap: 3px;
margin: 3px 0;
.tag-item {
padding: 3px 10px;
border-radius: 10px;
color: white;
font-size: 12px;
}
}
}
.open-items {
cursor: pointer;
border-radius: 44px;
width: 33px;
height: 33px;
display: flex;
justify-content: center;
align-items: center;
position: absolute;
bottom: -15px;
font-size: 20px;
left: 165px;
color: white;
}
.more-items {
background: #8bcc60;
}
.less-items {
background: #f92828;
}
&__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;
@media (max-width: 910px) {
right: 10px;
top: 40px;
}
&__item {
cursor: pointer;
display: flex;
align-content: center;
img {
margin-right: 5px;
}
span {
font-size: 14px;
}
}
}
&__no-items {
font-weight: 500;
font-size: 25px;
transform: scaleY(-1);
@media (max-width: 900px) {
transform: none;
}
}
&__no-tasks {
display: flex;
flex-direction: column;
transform: scaleY(-1);
&-info {
display: flex;
align-items: center;
margin-bottom: 15px;
img {
width: 27px;
height: 27px;
margin-right: 5px;
}
p {
font-weight: 700;
font-size: 22px;
line-height: 32px;
}
}
&-more {
font-size: 14px;
line-height: 22px;
}
@media (max-width: 900px) {
transform: none;
}
}
}
}

View File

@ -0,0 +1,113 @@
import React, { useEffect, useState } from "react";
import { useDispatch } from "react-redux";
import { filteredExecutorTasks } from "@redux/projectsTrackerSlice";
import { removeLast, urlForLocal } from "@utils/helper";
import arrowDown from "assets/icons/arrows/selectArrow.png";
import close from "assets/icons/close.png";
import avatarMok from "assets/images/avatarMok.png";
import "./trackerSelectExecutor.scss";
const TrackerSelectExecutor = ({
selectedExecutor,
setSelectedExecutor,
deleteSelectedExecutor,
projectBoard
}) => {
const [selectExecutorOpen, setSelectedExecutorOpen] = useState(false);
const dispatch = useDispatch();
const initListeners = () => {
document.addEventListener("click", closeByClickingOut);
};
const closeByClickingOut = (event) => {
const path = event.path || (event.composedPath && event.composedPath());
if (
event &&
!path.find(
(div) =>
div.classList &&
(div.classList.contains("tasks__head__executor") ||
div.classList.contains("tasks__head__executor-dropdown"))
)
) {
setSelectedExecutorOpen(false);
}
};
function executorFilter(user) {
dispatch(filteredExecutorTasks(user.user_id));
setSelectedExecutor(user);
}
useEffect(() => {
initListeners();
}, []);
if (selectedExecutor) {
return (
<div className="tasks__head__executor-selected">
<p>{removeLast(selectedExecutor.user.fio)}</p>
<img
className="avatar"
src={
selectedExecutor.user?.avatar
? urlForLocal(selectedExecutor.user.avatar)
: avatarMok
}
alt="avatar"
/>
<img
className="delete"
src={close}
alt="delete"
onClick={deleteSelectedExecutor}
/>
</div>
);
} else {
return (
<div
className="tasks__head__executor"
onClick={() => setSelectedExecutorOpen(!selectExecutorOpen)}
>
<p>Выберите исполнителя</p>
<img
className={selectExecutorOpen ? "open" : ""}
src={arrowDown}
alt="arrow"
/>
{selectExecutorOpen && (
<div className="tasks__head__executor-dropdown">
{projectBoard.projectUsers.map((user) => {
return (
<div
className="executor-dropdown__person"
key={user.user_id}
onClick={() => executorFilter(user)}
>
<p>{removeLast(user.user?.fio)}</p>
<img
src={
user.user?.avatar
? urlForLocal(user.user.avatar)
: avatarMok
}
alt="avatar"
/>
</div>
);
})}
</div>
)}
</div>
);
}
};
export default TrackerSelectExecutor;

View File

@ -0,0 +1,129 @@
.tasks {
&__head {
&__executor {
display: flex;
align-items: center;
justify-content: space-between;
cursor: pointer;
margin-right: 10px;
border-radius: 8px;
border: 1px solid #e3e2e2;
padding: 2px 6px;
position: relative;
max-width: 190px;
width: 100%;
@media (max-width: 915px) {
margin-right: 0;
width: 100%;
max-width: none;
}
@media (max-width: 650px) {
border-color: gray;
}
&-selected {
display: flex;
align-items: center;
border-radius: 8px;
max-width: 220px;
width: 100%;
margin-right: 10px;
justify-content: center;
p {
color: #252c32;
font-weight: 400;
font-size: 14px;
line-height: 24px;
max-width: 155px;
overflow: hidden;
white-space: nowrap;
text-overflow: ellipsis;
}
.avatar {
margin: 0 5px;
}
.delete {
cursor: pointer;
}
img {
display: flex;
width: 20px;
height: 20px;
}
@media (max-width: 915px) {
width: 100%;
max-width: none;
justify-content: start;
p {
font-size: 16px;
max-width: none;
}
}
}
p {
color: #252c32;
font-weight: 400;
font-size: 14px;
line-height: 24px;
}
img {
transition: all 0.15s ease;
margin-left: 5px;
}
.open {
transform: rotate(180deg);
}
&-dropdown {
position: absolute;
top: 33px;
left: 0;
background: white;
border-radius: 8px;
z-index: 5;
padding: 10px 10px;
display: flex;
flex-direction: column;
row-gap: 7px;
width: 100%;
.executor-dropdown__person {
display: flex;
justify-content: space-between;
align-items: center;
p {
max-width: 155px;
overflow: hidden;
white-space: nowrap;
text-overflow: ellipsis;
@media (max-width: 915px) {
max-width: none;
}
}
img {
width: 20px;
height: 20px;
}
&:hover {
p {
font-weight: 600;
}
}
}
}
}
}
}

View File

@ -0,0 +1,275 @@
import React, { useEffect, useState } from "react";
import { HexColorPicker } from "react-colorful";
import { useDispatch } from "react-redux";
import { useParams } from "react-router-dom";
import {
addNewTagToProject,
deleteTagProject,
setProjectBoardFetch
} from "@redux/projectsTrackerSlice";
import { apiRequest } from "@api/request";
import { useNotification } from "@hooks/useNotification";
import arrowDown from "assets/icons/arrows/selectArrow.png";
import close from "assets/icons/close.png";
import edit from "assets/icons/edit.svg";
import "./trackerTagList.scss";
const TrackerTagList = ({ projectBoard }) => {
const dispatch = useDispatch();
const projectId = useParams();
const { showNotification } = useNotification();
const [tagInfo, setTagInfo] = useState({ description: "", name: "" });
const [color, setColor] = useState("#aabbcc");
const [tags, setTags] = useState({
open: false,
add: false,
edit: false
});
function deleteTag(tagId) {
apiRequest("/mark/detach", {
method: "DELETE",
data: {
mark_id: tagId,
entity_type: 1,
entity_id: projectId.id
}
}).then(() => {
dispatch(deleteTagProject(tagId));
showNotification({
show: true,
text: "Тег удален",
type: "success"
});
});
}
function addNewTag() {
apiRequest("/mark/create", {
method: "POST",
data: {
title: tagInfo.description,
slug: tagInfo.name,
color: color,
status: 1
}
}).then((data) => {
apiRequest("/mark/attach", {
method: "POST",
data: {
mark_id: data.id,
entity_type: 1,
entity_id: projectId.id
}
}).then((data) => {
dispatch(addNewTagToProject(data.mark));
setTags((prevState) => ({
...prevState,
add: false
}));
showNotification({
show: true,
text: "Тег успешно создан",
type: "success"
});
});
});
}
function editTag() {
apiRequest("/mark/update", {
method: "PUT",
data: {
mark_id: tagInfo.editMarkId,
title: tagInfo.description,
slug: tagInfo.name,
color: color
}
}).then(() => {
dispatch(setProjectBoardFetch(projectId.id));
setTags((prevState) => ({
...prevState,
edit: false
}));
setTagInfo({ description: "", name: "" });
setColor("#aabbcc");
showNotification({
show: true,
text: "Тег успешно изменён",
type: "success"
});
});
}
const initListeners = () => {
document.addEventListener("click", closeByClickingOut);
};
const closeByClickingOut = (event) => {
const path = event.path || (event.composedPath && event.composedPath());
if (
event &&
!path.find(
(div) =>
div.classList &&
(div.classList.contains("tasks__head__tags") ||
div.classList.contains("tags__list"))
)
) {
setTags({ open: false, add: false, edit: false });
setTagInfo({ description: "", name: "" });
setColor("#aabbcc");
}
};
useEffect(() => {
initListeners();
}, []);
return (
<div className="tasks__head__tags">
<div
className="tags__add"
onClick={() => {
setTags((prevState) => ({
...prevState,
open: !tags.open
}));
}}
>
<p>Список тегов</p>
<img className={tags.open ? "open" : ""} src={arrowDown} alt="arrow" />
</div>
{tags.open && (
<div className="tags__list">
{!tags.add && !tags.edit && (
<div
className="add-new-tag"
onClick={() =>
setTags((prevState) => ({
...prevState,
add: true
}))
}
>
<p>Добавить новый тег</p>
<span>+</span>
</div>
)}
{!tags.add && !tags.edit && (
<div className="tags__list__created">
{projectBoard.mark.map((tag) => {
return (
<div className="tag-item" key={tag.id}>
<div className="tag-item__info">
<span
className="tag-item__color"
style={{ background: tag.color }}
/>
<div>
<span className="tag-item__info__name">{tag.slug}</span>
<p className="tag-item__description">{tag.title}</p>
</div>
</div>
<div className="tag-item__images">
<img
src={edit}
alt="edit"
onClick={() => {
setTags((prevState) => ({
...prevState,
edit: true
}));
setTagInfo({
description: tag.title,
name: tag.slug,
editMarkId: tag.id
});
setColor(tag.color);
}}
/>
<img
onClick={() => deleteTag(tag.id)}
className="delete"
src={close}
alt="delete"
/>
</div>
</div>
);
})}
</div>
)}
{(tags.add || tags.edit) && (
<div className="form-tag">
<input
className="form-tag__input"
placeholder="Описание метки"
maxLength="25"
value={tagInfo.description}
onChange={(e) =>
setTagInfo((prevState) => ({
...prevState,
description: e.target.value
}))
}
/>
<input
className="form-tag__input"
placeholder="Тег"
value={tagInfo.name}
maxLength="10"
onChange={(e) =>
setTagInfo((prevState) => ({
...prevState,
name: e.target.value
}))
}
/>
<HexColorPicker color={color} onChange={setColor} />
<button
onClick={() => {
tags.add ? addNewTag() : editTag();
}}
className={
tagInfo.name && tagInfo.description
? "form-tag__btn"
: "form-tag__btn disable"
}
>
{tags.add ? "Добавить" : "Изменить"}
</button>
{(tags.add || tags.edit) && (
<button
className={"form-tag__btn"}
onClick={() => {
setTags((prevState) => ({
...prevState,
add: false,
edit: false
}));
setTagInfo({
description: "",
name: ""
});
setColor("#aabbcc");
}}
>
Отмена
</button>
)}
</div>
)}
</div>
)}
</div>
);
};
export default TrackerTagList;

View File

@ -0,0 +1,208 @@
.tasks {
&__head {
&__tags {
position: relative;
img {
transition: all 0.15s ease;
margin-left: 5px;
}
.open {
transform: rotate(180deg);
}
.tags {
&__add {
display: flex;
align-items: center;
margin: 0 10px;
column-gap: 5px;
cursor: pointer;
padding: 5px;
border-radius: 8px;
border: 1px solid #e3e2e2;
max-height: 30px;
p {
white-space: nowrap;
font-weight: 400;
font-size: 14px;
line-height: 17px;
}
span {
width: 14px;
height: 14px;
font-weight: 400;
line-height: 16px;
border-radius: 50px;
align-items: center;
justify-content: center;
display: flex;
background: #99b4f3;
color: white;
font-size: 14px;
transition: all 0.15s ease;
}
}
&__list {
position: absolute;
border-radius: 8px;
background: #d9d9d9;
z-index: 8;
top: 30px;
left: -35px;
width: 216px;
display: flex;
flex-direction: column;
@media (max-width: 900px) {
left: 0px;
}
.close {
cursor: pointer;
width: 20px;
height: 20px;
position: absolute;
right: 10px;
top: 2px;
}
&__created {
display: flex;
flex-direction: column;
row-gap: 8px;
margin-top: 8px;
padding: 0 8px 8px;
.tag-item {
display: flex;
align-items: center;
flex-direction: row;
justify-content: space-between;
column-gap: 5px;
padding: 0px 8px;
border-radius: 8px;
height: 40px;
max-height: 40px;
background: #fff;
&__description {
font-size: 12px;
word-break: break-word;
max-width: 115px;
max-height: 40px;
overflow: hidden;
text-wrap: wrap;
text-overflow: ellipsis;
}
&__color {
width: 22.25px;
height: 23.217px;
border-radius: 8px;
}
&__images {
display: flex;
flex-direction: column-reverse;
row-gap: 3px;
img {
height: 14px;
width: 14px;
cursor: pointer;
}
.delete {
width: 16px;
height: 16px;
}
}
&__info {
display: flex;
align-items: center;
column-gap: 10px;
&__name {
font-size: 12px;
font-weight: 600;
}
}
}
}
.add-new-tag {
display: flex;
align-items: center;
column-gap: 15px;
border-radius: 8px;
background: white;
color: #252c32;
height: 40px;
cursor: pointer;
justify-content: center;
margin: 8px 8px 0px;
p {
font-size: 15px;
}
span {
width: 19px;
height: 19px;
border-radius: 50px;
align-items: center;
justify-content: center;
display: flex;
background: #52b709;
color: white;
font-size: 16px;
transition: all 0.15s ease;
}
}
.form-tag {
display: flex;
flex-direction: column;
padding: 8px;
row-gap: 8px;
.arrow {
position: absolute;
cursor: pointer;
top: 5px;
width: 15px;
height: 15px;
transform: rotate(180deg);
}
&__input {
outline: none;
border-radius: 8px;
border: 1px solid #e3e2e2;
font-size: 15px;
padding: 5px;
}
&__btn {
outline: none;
border: none;
background: #252c32;
color: whitesmoke;
margin: 0 auto 0;
border-radius: 10px;
font-size: 15px;
padding: 5px 12px;
}
.disable {
opacity: 0.5;
pointer-events: none;
}
}
}
}
}
}
}

View File

@ -1,20 +1,15 @@
import moment from "moment";
import React, { useEffect, useRef, useState } from "react";
import { HexColorPicker } from "react-colorful";
import { useDispatch, useSelector } from "react-redux";
import { Link, useParams } from "react-router-dom";
import {
activeLoader,
addNewTagToProject,
deleteTagProject,
filterCreatedByMe,
filteredExecutorTasks,
filteredParticipateTasks,
getBoarderLoader,
getProjectBoard,
modalToggle,
movePositionProjectTask,
moveProjectTask,
setColumnId,
setColumnName,
@ -23,7 +18,7 @@ import {
setToggleTab
} from "@redux/projectsTrackerSlice";
import { removeLast, urlForLocal } from "@utils/helper";
import { urlForLocal } from "@utils/helper";
import { apiRequest } from "@api/request";
@ -39,25 +34,20 @@ import TrackerModal from "@components/Modal/Tracker/TrackerModal/TrackerModal";
import { Navigation } from "@components/Navigation/Navigation";
import { ProfileBreadcrumbs } from "@components/ProfileBreadcrumbs/ProfileBreadcrumbs";
import { ProfileHeader } from "@components/ProfileHeader/ProfileHeader";
import TrackerSelectColumn from "@components/TrackerSelectColumn/TrackerSelectColumn";
import TrackerCardTask from "@components/TrackerCardTask/TrackerCardTask";
import TrackerSelectExecutor from "@components/TrackerSelectExecutor/TrackerSelectExecutor";
import TrackerTagList from "@components/TrackerTagList/TrackerTagList";
import arrow from "assets/icons/arrows/arrowRight.png";
import arrowDown from "assets/icons/arrows/selectArrow.png";
import category from "assets/icons/category.svg";
import close from "assets/icons/close.png";
import commentsBoard from "assets/icons/commentsBoard.svg";
import del from "assets/icons/delete.svg";
import edit from "assets/icons/edit.svg";
import filesBoard from "assets/icons/filesBoard.svg";
import trackerNoTasks from "assets/icons/trackerNoTasks.svg";
import project from "assets/icons/trackerProject.svg";
import tasks from "assets/icons/trackerTasks.svg";
import accept from "assets/images/accept.png";
import archive from "assets/images/archiveIcon.png";
import avatarMok from "assets/images/avatarMok.png";
import { getCorrectDate } from "../../utils/calendarHelper";
export const ProjectTracker = () => {
const dispatch = useDispatch();
const projectId = useParams();
@ -66,43 +56,22 @@ export const ProjectTracker = () => {
const [selectedTab, setSelectedTab] = useState(0);
const [priorityTask, setPriorityTask] = useState(0);
const [wrapperHover, setWrapperHover] = useState({});
const [taskHover, setTaskHover] = useState({});
const [modalAdd, setModalAdd] = useState(false);
const [modalActiveTicket, setModalActiveTicket] = useState(false);
const [selectedTicket, setSelectedTicket] = useState({});
const [personListOpen, setPersonListOpen] = useState(false);
const [tags, setTags] = useState({
open: false,
add: false,
edit: false
});
const [acceptModalOpen, setAcceptModalOpen] = useState(false);
const [currentColumnDelete, setCurrentColumnDelete] = useState(null);
const [color, setColor] = useState("#aabbcc");
const [tagInfo, setTagInfo] = useState({ description: "", name: "" });
const [checkBoxParticipateTasks, setCheckBoxParticipateTasks] =
useState(false);
const [filteredNoTasks, setFilteredNoTasks] = useState(false);
const [checkBoxMyTasks, setCheckBoxMyTasks] = useState(false);
const [selectedExecutor, setSelectedExecutor] = useState(null);
const [selectExecutorOpen, setSelectedExecutorOpen] = useState(false);
const startWrapperIndexTest = useRef({});
const projectBoard = useSelector(getProjectBoard);
const loader = useSelector(getBoarderLoader);
const { showNotification } = useNotification();
const priority = {
2: "Высокий",
1: "Средний",
0: "Низкий"
};
const priorityClass = {
2: "high",
1: "middle",
0: "low"
};
useEffect(() => {
dispatch(activeLoader());
dispatch(setProjectBoardFetch(projectId.id));
@ -110,8 +79,6 @@ export const ProjectTracker = () => {
}, []);
useEffect(() => {
const tasksHover = {};
const columnHover = {};
let columnsTasksEmpty = true;
if (Object.keys(projectBoard).length) {
projectBoard.columns.forEach((column) => {
@ -120,8 +87,6 @@ export const ProjectTracker = () => {
...prevState,
[column.id]: false
}));
columnHover[column.id] = false;
column.tasks.forEach((task) => (tasksHover[task.id] = false));
});
}
if (
@ -132,48 +97,8 @@ export const ProjectTracker = () => {
} else {
setFilteredNoTasks(false);
}
setWrapperHover(columnHover);
setTaskHover(tasksHover);
}, [projectBoard]);
function dragStartHandler(e, task, columnId) {
startWrapperIndexTest.current = { task: task, index: columnId };
}
function dragOverTaskHandler(e, task) {
e.preventDefault();
if (startWrapperIndexTest.current.task.id === task.id) {
return;
}
setTaskHover((prevState) => ({ [prevState]: false, [task.id]: true }));
}
function dragLeaveTaskHandler() {
setTaskHover((prevState) => ({ [prevState]: false }));
}
function dragEndTaskHandler() {
setTaskHover((prevState) => ({ [prevState]: false }));
setWrapperHover((prevState) => ({
[prevState]: false
}));
}
function dragDropTaskHandler(e, task, column) {
e.preventDefault();
if (task.id === startWrapperIndexTest.current.task.id) {
return;
}
const finishTask = column.tasks.indexOf(task);
dispatch(
movePositionProjectTask({
startTask: startWrapperIndexTest.current.task,
finishTask: task,
finishIndex: finishTask
})
);
}
function dragOverHandler(e) {
e.preventDefault();
}
@ -288,11 +213,6 @@ export const ProjectTracker = () => {
setCheckBoxMyTasks(!checkBoxMyTasks);
}
function executorFilter(user) {
dispatch(filteredExecutorTasks(user.user_id));
setSelectedExecutor(user);
}
function deleteSelectedExecutorFilter() {
setSelectedExecutor(null);
setCheckBoxParticipateTasks(false);
@ -300,81 +220,6 @@ export const ProjectTracker = () => {
dispatch(setProjectBoardFetch(projectId.id));
}
function addNewTag() {
apiRequest("/mark/create", {
method: "POST",
data: {
title: tagInfo.description,
slug: tagInfo.name,
color: color,
status: 1
}
}).then((data) => {
apiRequest("/mark/attach", {
method: "POST",
data: {
mark_id: data.id,
entity_type: 1,
entity_id: projectId.id
}
}).then((data) => {
dispatch(addNewTagToProject(data.mark));
setTags((prevState) => ({
...prevState,
add: false
}));
showNotification({
show: true,
text: "Тег успешно создан",
type: "success"
});
});
});
}
function editTag() {
apiRequest("/mark/update", {
method: "PUT",
data: {
mark_id: tagInfo.editMarkId,
title: tagInfo.description,
slug: tagInfo.name,
color: color
}
}).then(() => {
dispatch(setProjectBoardFetch(projectId.id));
setTags((prevState) => ({
...prevState,
edit: false
}));
setTagInfo({ description: "", name: "" });
setColor("#aabbcc");
showNotification({
show: true,
text: "Тег успешно изменён",
type: "success"
});
});
}
function deleteTag(tagId) {
apiRequest("/mark/detach", {
method: "DELETE",
data: {
mark_id: tagId,
entity_type: 1,
entity_id: projectId.id
}
}).then(() => {
dispatch(deleteTagProject(tagId));
showNotification({
show: true,
text: "Тег удален",
type: "success"
});
});
}
const initListeners = () => {
document.addEventListener("click", closeByClickingOut);
};
@ -394,32 +239,6 @@ export const ProjectTracker = () => {
setPersonListOpen(false);
}
if (
event &&
!path.find(
(div) =>
div.classList &&
(div.classList.contains("tasks__head__executor") ||
div.classList.contains("tasks__head__executor-dropdown"))
)
) {
setSelectedExecutorOpen(false);
}
if (
event &&
!path.find(
(div) =>
div.classList &&
(div.classList.contains("tasks__head__tags") ||
div.classList.contains("tags__list"))
)
) {
setTags({ open: false, add: false, edit: false });
setTagInfo({ description: "", name: "" });
setColor("#aabbcc");
}
if (
event &&
!path.find(
@ -577,207 +396,16 @@ export const ProjectTracker = () => {
{checkBoxMyTasks && <img src={accept} alt="accept" />}
</div>
</div>
{selectedExecutor ? (
<div className="tasks__head__executor-selected">
<p>{removeLast(selectedExecutor.user.fio)}</p>
<img
className="avatar"
src={
selectedExecutor.user?.avatar
? urlForLocal(selectedExecutor.user.avatar)
: avatarMok
}
alt="avatar"
<TrackerSelectExecutor
deleteSelectedExecutor={deleteSelectedExecutorFilter}
projectBoard={projectBoard}
selectedExecutor={selectedExecutor}
setSelectedExecutor={setSelectedExecutor}
/>
<img
className="delete"
src={close}
alt="delete"
onClick={deleteSelectedExecutorFilter}
/>
</div>
) : (
<div
className="tasks__head__executor"
onClick={() =>
setSelectedExecutorOpen(!selectExecutorOpen)
}
>
<p>Выберите исполнителя</p>
<img
className={selectExecutorOpen ? "open" : ""}
src={arrowDown}
alt="arrow"
/>
{selectExecutorOpen && (
<div className="tasks__head__executor-dropdown">
{projectBoard.projectUsers.map((user) => {
return (
<div
className="executor-dropdown__person"
key={user.user_id}
onClick={() => executorFilter(user)}
>
<p>{removeLast(user.user?.fio)}</p>
<img
src={
user.user?.avatar
? urlForLocal(user.user.avatar)
: avatarMok
}
alt="avatar"
/>
</div>
);
})}
</div>
)}
</div>
)}
<div className="tasks__head__tags">
<div
className="tags__add"
onClick={() => {
setTags((prevState) => ({
...prevState,
open: !tags.open
}));
}}
>
<p>Список тегов</p>
<img
className={tags.open ? "open" : ""}
src={arrowDown}
alt="arrow"
/>
</div>
{tags.open && (
<div className="tags__list">
{!tags.add && !tags.edit && (
<div
className="add-new-tag"
onClick={() =>
setTags((prevState) => ({
...prevState,
add: true
}))
}
>
<p>Добавить новый тег</p>
<span>+</span>
</div>
)}
{!tags.add && !tags.edit && (
<div className="tags__list__created">
{projectBoard.mark.map((tag) => {
return (
<div className="tag-item" key={tag.id}>
<div className="tag-item__info">
<span
className="tag-item__color"
style={{ background: tag.color }}
/>
<div>
<span className="tag-item__info__name">
{tag.slug}
</span>
<p className="tag-item__description">
{tag.title}
</p>
</div>
</div>
<div className="tag-item__images">
<img
src={edit}
alt="edit"
onClick={() => {
setTags((prevState) => ({
...prevState,
edit: true
}));
setTagInfo({
description: tag.title,
name: tag.slug,
editMarkId: tag.id
});
setColor(tag.color);
}}
/>
<img
onClick={() => deleteTag(tag.id)}
className="delete"
src={close}
alt="delete"
/>
</div>
</div>
);
})}
</div>
)}
{(tags.add || tags.edit) && (
<div className="form-tag">
<input
className="form-tag__input"
placeholder="Описание метки"
maxLength="25"
value={tagInfo.description}
onChange={(e) =>
setTagInfo((prevState) => ({
...prevState,
description: e.target.value
}))
}
/>
<input
className="form-tag__input"
placeholder="Тег"
value={tagInfo.name}
maxLength="10"
onChange={(e) =>
setTagInfo((prevState) => ({
...prevState,
name: e.target.value
}))
}
/>
<HexColorPicker color={color} onChange={setColor} />
<button
onClick={() => {
tags.add ? addNewTag() : editTag();
}}
className={
tagInfo.name && tagInfo.description
? "form-tag__btn"
: "form-tag__btn disable"
}
>
{tags.add ? "Добавить" : "Изменить"}
</button>
{(tags.add || tags.edit) && (
<button
className={"form-tag__btn"}
onClick={() => {
setTags((prevState) => ({
...prevState,
add: false,
edit: false
}));
setTagInfo({
description: "",
name: ""
});
setColor("#aabbcc");
}}
>
Отмена
</button>
)}
</div>
)}
</div>
)}
</div>
<TrackerTagList projectBoard={projectBoard} />
<Link to="/profile/tracker" className="tasks__head__back">
<p>К списку проектов</p>
<img src={arrow} alt="arrow" />
@ -878,7 +506,7 @@ export const ProjectTracker = () => {
</div>
)}
<div className="tasks-container">
{column.tasks.map((task) => {
{column.tasks.map((task, index) => {
const dateDeadline = new Date(task.dead_line);
const currentDate = moment().format(
"YYYY-MM-DD HH:mm:ss"
@ -889,119 +517,16 @@ export const ProjectTracker = () => {
? "red"
: "#1a1919";
return (
<div
key={task.id}
className={`tasks__board__item ${
taskHover[task.id] ? "task__hover" : ""
}`}
draggable={true}
onDragStart={(e) =>
dragStartHandler(e, task, column.id)
}
onDragOver={(e) => dragOverTaskHandler(e, task)}
onDragLeave={(e) => dragLeaveTaskHandler(e)}
onDragEnd={() => dragEndTaskHandler()}
onDrop={(e) =>
dragDropTaskHandler(e, task, column)
}
onClick={(e) => openTicket(e, task)}
>
<div
className="tasks__board__item__title"
onClick={() => {
if (window.innerWidth < 985) {
window.location.replace(
`/tracker/task/${task.id}`
);
}
}}
>
<p className="task__board__item__title">
{task.title}
</p>
</div>
<p
dangerouslySetInnerHTML={{
__html: task.description
}}
className="tasks__board__item__description"
></p>
{Boolean(task.mark.length) && (
<div className="tasks__board__item__tags">
{task.mark.map((tag) => {
return (
<div
className="tag-item"
key={tag.id}
style={{ background: tag.color }}
>
<p>{tag.slug}</p>
</div>
);
})}
</div>
)}
<div className="tasks__board__item__container">
{typeof task.execution_priority ===
"number" && (
<div className="tasks__board__item__priority">
<p></p>
<span
className={
priorityClass[task.execution_priority]
}
>
{priority[task.execution_priority]}
</span>
</div>
)}
{task.dead_line && (
<div className="tasks__board__item__dead-line">
<p></p>
<span style={{ color: titleColor }}>
{getCorrectDate(task.dead_line)}
</span>
</div>
)}
</div>
<div className="tasks__board__item__info">
<div className="tasks__board__item__executor">
<img
src={
task.executor?.avatar
? urlForLocal(task.executor?.avatar)
: avatarMok
}
alt="avatar"
/>
<span>
{removeLast(task.executor?.fio) ||
"Исполнитель не назначен"}
</span>
</div>
<div className="tasks__board__item__info__tags">
<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.file_count}</span>
</div>
</div>
</div>
<TrackerSelectColumn
columns={projectBoard.columns.filter(
(item) => item.id !== column.id
)}
currentColumn={column}
<TrackerCardTask
column={column}
key={index}
openTicket={openTicket}
projectBoard={projectBoard}
setWrapperHover={setWrapperHover}
startWrapperIndexTest={startWrapperIndexTest}
task={task}
titleColor={titleColor}
/>
</div>
);
})}
</div>

View File

@ -361,132 +361,6 @@
}
}
&__executor {
display: flex;
align-items: center;
justify-content: space-between;
cursor: pointer;
margin-right: 10px;
border-radius: 8px;
border: 1px solid #e3e2e2;
padding: 2px 6px;
position: relative;
max-width: 190px;
width: 100%;
@media (max-width: 915px) {
margin-right: 0;
width: 100%;
max-width: none;
}
@media (max-width: 650px) {
border-color: gray;
}
&-selected {
display: flex;
align-items: center;
border-radius: 8px;
max-width: 220px;
width: 100%;
margin-right: 10px;
justify-content: center;
p {
color: #252c32;
font-weight: 400;
font-size: 14px;
line-height: 24px;
max-width: 155px;
overflow: hidden;
white-space: nowrap;
text-overflow: ellipsis;
}
.avatar {
margin: 0 5px;
}
.delete {
cursor: pointer;
}
img {
display: flex;
width: 20px;
height: 20px;
}
@media (max-width: 915px) {
width: 100%;
max-width: none;
justify-content: start;
p {
font-size: 16px;
max-width: none;
}
}
}
p {
color: #252c32;
font-weight: 400;
font-size: 14px;
line-height: 24px;
}
img {
transition: all 0.15s ease;
margin-left: 5px;
}
.open {
transform: rotate(180deg);
}
&-dropdown {
position: absolute;
top: 33px;
left: 0;
background: white;
border-radius: 8px;
z-index: 5;
padding: 10px 10px;
display: flex;
flex-direction: column;
row-gap: 7px;
width: 100%;
.executor-dropdown__person {
display: flex;
justify-content: space-between;
align-items: center;
p {
max-width: 155px;
overflow: hidden;
white-space: nowrap;
text-overflow: ellipsis;
@media (max-width: 915px) {
max-width: none;
}
}
img {
width: 20px;
height: 20px;
}
&:hover {
p {
font-weight: 600;
}
}
}
}
}
&__back {
cursor: pointer;
display: flex;
@ -517,211 +391,6 @@
display: none;
}
}
&__tags {
position: relative;
img {
transition: all 0.15s ease;
margin-left: 5px;
}
.open {
transform: rotate(180deg);
}
.tags {
&__add {
display: flex;
align-items: center;
margin: 0 10px;
column-gap: 5px;
cursor: pointer;
padding: 5px;
border-radius: 8px;
border: 1px solid #e3e2e2;
max-height: 30px;
p {
white-space: nowrap;
font-weight: 400;
font-size: 14px;
line-height: 17px;
}
span {
width: 14px;
height: 14px;
font-weight: 400;
line-height: 16px;
border-radius: 50px;
align-items: center;
justify-content: center;
display: flex;
background: #99b4f3;
color: white;
font-size: 14px;
transition: all 0.15s ease;
}
}
&__list {
position: absolute;
border-radius: 8px;
background: #d9d9d9;
z-index: 8;
top: 30px;
left: -35px;
width: 216px;
display: flex;
flex-direction: column;
@media (max-width: 900px) {
left: 0px;
}
.close {
cursor: pointer;
width: 20px;
height: 20px;
position: absolute;
right: 10px;
top: 2px;
}
&__created {
display: flex;
flex-direction: column;
row-gap: 8px;
margin-top: 8px;
padding: 0 8px 8px;
.tag-item {
display: flex;
align-items: center;
flex-direction: row;
justify-content: space-between;
column-gap: 5px;
padding: 0px 8px;
border-radius: 8px;
height: 40px;
max-height: 40px;
background: #fff;
&__description {
font-size: 12px;
word-break: break-word;
max-width: 115px;
max-height: 40px;
overflow: hidden;
text-wrap: wrap;
text-overflow: ellipsis;
}
&__color {
width: 22.25px;
height: 23.217px;
border-radius: 8px;
}
&__images {
display: flex;
flex-direction: column-reverse;
row-gap: 3px;
img {
height: 14px;
width: 14px;
cursor: pointer;
}
.delete {
width: 16px;
height: 16px;
}
}
&__info {
display: flex;
align-items: center;
column-gap: 10px;
&__name {
font-size: 12px;
font-weight: 600;
}
}
}
}
.add-new-tag {
display: flex;
align-items: center;
column-gap: 15px;
border-radius: 8px;
background: white;
color: #252c32;
height: 40px;
cursor: pointer;
justify-content: center;
margin: 8px 8px 0px;
p {
font-size: 15px;
}
span {
width: 19px;
height: 19px;
border-radius: 50px;
align-items: center;
justify-content: center;
display: flex;
background: #52b709;
color: white;
font-size: 16px;
transition: all 0.15s ease;
}
}
.form-tag {
display: flex;
flex-direction: column;
padding: 8px;
row-gap: 8px;
.arrow {
position: absolute;
cursor: pointer;
top: 5px;
width: 15px;
height: 15px;
transform: rotate(180deg);
}
&__input {
outline: none;
border-radius: 8px;
border: 1px solid #e3e2e2;
font-size: 15px;
padding: 5px;
}
&__btn {
outline: none;
border: none;
background: #252c32;
color: whitesmoke;
margin: 0 auto 0;
border-radius: 10px;
font-size: 15px;
padding: 5px 12px;
}
.disable {
opacity: 0.5;
pointer-events: none;
}
}
}
}
}
}
&__container {
@ -757,372 +426,6 @@
}
}
&__board {
background: #f5f7f9;
box-shadow: 0px 2px 5px rgba(60, 66, 87, 0.04),
0px 0px 0px 1px rgba(60, 66, 87, 0.08),
0px 1px 1px rgba(0, 0, 0, 0.06);
border-radius: 8px;
padding: 12px 10px 12px 8px;
width: 360px;
display: flex;
flex-direction: column;
row-gap: 10px;
height: fit-content;
position: relative;
transition: all 0.3s ease;
transform: scaleY(-1);
min-height: 815px;
@media (max-width: 900px) {
min-width: auto;
width: 100%;
max-width: none;
transform: scaleX(1);
}
.tasks-container {
display: flex;
flex-direction: column;
row-gap: 8px;
max-height: 750px;
overflow: auto;
padding: 5px;
&::-webkit-scrollbar {
width: 3px;
border-radius: 10px;
}
&::-webkit-scrollbar-thumb {
background: #cbd9f9;
border-radius: 20px;
}
&::-webkit-scrollbar-track {
background: #c5c0c6;
border-radius: 20px;
}
}
&__hover {
box-shadow: 0px 2px 10px #9cc480,
0px 0px 0px 1px rgba(60, 66, 87, 0.08),
0px 1px 1px rgba(0, 0, 0, 0.06);
}
.task__hover {
box-shadow: 0 0 5px gray;
}
&__item {
width: 328px;
padding: 6px 10px 8px 10px;
position: relative;
box-shadow: 0px 3px 2px -2px rgba(0, 0, 0, 0.06),
0px 5px 3px -2px rgba(0, 0, 0, 0.02);
border-radius: 6px;
background: #ffffff;
cursor: pointer;
display: flex;
flex-direction: column;
justify-content: space-between;
transition: 0.4s;
&:hover {
transform: scale(1.025);
transition: 0.3s;
}
@media (max-width: 900px) {
width: 100%;
max-height: none;
&:hover {
transform: none;
}
}
&__hide {
opacity: 0;
}
&__title {
display: flex;
justify-content: space-between;
position: relative;
p {
color: #1a1919;
font-weight: 500;
font-size: 16px;
line-height: 20px;
margin-bottom: 0;
max-height: 100px;
overflow: hidden;
text-overflow: ellipsis;
-webkit-line-clamp: 3;
}
span {
cursor: pointer;
display: flex;
border-radius: 6px;
align-items: center;
justify-content: center;
font-size: 20px;
padding-bottom: 10px;
width: 24px;
height: 24px;
border: 1px solid #dddddd;
}
}
&__description {
margin: 4px 0;
color: #5c6165;
font-weight: 400;
font-size: 14px;
line-height: 120%;
max-height: 100px;
overflow: hidden;
text-overflow: ellipsis;
display: -webkit-box;
-webkit-line-clamp: 2;
-webkit-box-orient: vertical;
}
&__container {
display: flex;
justify-content: space-between;
}
&__info {
display: flex;
column-gap: 10px;
align-items: center;
justify-content: space-between;
pointer-events: none;
margin-top: 5px;
&__tags {
display: flex;
column-gap: 5px;
}
&__more {
cursor: pointer;
display: flex;
align-items: center;
span {
font-weight: 500;
font-size: 12px;
line-height: 15px;
color: #6e7c87;
margin-left: 5px;
}
}
&__avatars {
position: relative;
img {
position: relative;
}
img:first-child {
right: -15px;
z-index: 2;
}
}
}
&__priority {
display: flex;
align-items: center;
column-gap: 5px;
margin-top: 3px;
p {
font-weight: 500;
font-size: 14px;
}
span {
font-weight: 500;
font-size: 14px;
}
.high {
color: red;
}
.middle {
color: #cece00;
}
.low {
color: green;
}
}
&__dead-line {
display: flex;
align-items: center;
column-gap: 5px;
p {
font-weight: 500;
font-size: 14px;
color: #1458dd;
}
span {
font-weight: 500;
font-size: 14px;
}
img {
margin-top: -2px;
width: 18px;
}
}
&__executor {
display: flex;
align-items: center;
font-size: 14px;
font-weight: 500;
column-gap: 5px;
span {
max-width: 210px;
overflow: hidden;
text-overflow: ellipsis;
white-space: nowrap;
}
img {
width: 25px;
height: 25px;
}
}
&__tags {
display: flex;
flex-wrap: wrap;
column-gap: 6px;
row-gap: 3px;
margin: 3px 0;
.tag-item {
padding: 3px 10px;
border-radius: 10px;
color: white;
font-size: 12px;
}
}
}
.open-items {
cursor: pointer;
border-radius: 44px;
width: 33px;
height: 33px;
display: flex;
justify-content: center;
align-items: center;
position: absolute;
bottom: -15px;
font-size: 20px;
left: 165px;
color: white;
}
.more-items {
background: #8bcc60;
}
.less-items {
background: #f92828;
}
&__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;
@media (max-width: 910px) {
right: 10px;
top: 40px;
}
&__item {
cursor: pointer;
display: flex;
align-content: center;
img {
margin-right: 5px;
}
span {
font-size: 14px;
}
}
}
&__no-items {
font-weight: 500;
font-size: 25px;
transform: scaleY(-1);
@media (max-width: 900px) {
transform: none;
}
}
&__no-tasks {
display: flex;
flex-direction: column;
transform: scaleY(-1);
&-info {
display: flex;
align-items: center;
margin-bottom: 15px;
img {
width: 27px;
height: 27px;
margin-right: 5px;
}
p {
font-weight: 700;
font-size: 22px;
line-height: 32px;
}
}
&-more {
font-size: 14px;
line-height: 22px;
}
@media (max-width: 900px) {
transform: none;
}
}
}
.board {
&__head {
position: relative;
@ -1958,7 +1261,7 @@
.table {
&__search {
display: flex;
background: #F0F2F5;
background: #f0f2f5;
border-radius: 5px;
width: 100%;
padding: 14px 12px;
@ -1976,11 +1279,11 @@
border: none;
outline: none;
font-size: 16px;
color: #9BABC5;
color: #9babc5;
width: 100%;
&::placeholder {
color: #9BABC5;
color: #9babc5;
}
}
}
@ -1995,12 +1298,12 @@
width: 32px;
border-radius: 5px;
height: 32px;
color: #2E3A59;
color: #2e3a59;
}
.switch {
border: none;
background: #F0F2F5;
background: #f0f2f5;
font-weight: 600;
}
@ -2016,12 +1319,12 @@
background: white;
.page {
border: 1px solid #E8ECF8;
border: 1px solid #e8ecf8;
background: none;
&--active {
border: none;
background: #9DA65D;
background: #9da65d;
color: white;
}
}
@ -2029,17 +1332,20 @@
}
table {
grid-template-columns: minmax(0px, 2fr) minmax(0px, 1fr) minmax(0px, 1fr) minmax(0px, 1fr);
grid-template-columns: minmax(0px, 2fr) minmax(0px, 1fr) minmax(0px, 1fr) minmax(
0px,
1fr
);
th {
border-top: none;
border-bottom: 1px solid #F5F6F8;
color: #2E3A59;
border-bottom: 1px solid #f5f6f8;
color: #2e3a59;
padding: 0 7.5px 15px;
}
td {
padding: 22px 7.5px;
color: #2E3A59;
color: #2e3a59;
border-top: none;
p {

View File

@ -1,7 +1,8 @@
import React, { useEffect, useState } from "react";
import { useDispatch } from "react-redux";
import { useDispatch, useSelector } from "react-redux";
import { Link, useParams } from "react-router-dom";
import { getProfileInfo } from "@redux/outstaffingSlice";
import { setEditReport } from "@redux/reportSlice";
import {
@ -9,6 +10,7 @@ import {
getCreatedDate,
hourOfNum
} from "@utils/calendarHelper";
import { urlForLocal } from "@utils/helper";
import { apiRequest } from "@api/request";
@ -20,8 +22,9 @@ import { Navigation } from "@components/Navigation/Navigation";
import { ProfileBreadcrumbs } from "@components/ProfileBreadcrumbs/ProfileBreadcrumbs";
import { ProfileHeader } from "@components/ProfileHeader/ProfileHeader";
import arrowSwitchDate from "assets/icons/arrows/arrowViewReport.png";
import arrowSwitchDate from "assets/icons/arrows/arrowCalendar_right.png";
import arrow from "assets/icons/arrows/left-arrow.png";
import avatarMok from "assets/images/avatarMok.png";
import "./viewReport.scss";
@ -31,6 +34,7 @@ export const ViewReport = () => {
const [previousReportDay] = useState(new Date(params.date));
const [nextReportDay] = useState(new Date(params.date));
const profileInfo = useSelector(getProfileInfo);
const [taskText, setTaskText] = useState([]);
const [difficulties, setDifficulties] = useState([]);
const [tomorrowTask, setTomorrowTask] = useState([]);
@ -140,6 +144,33 @@ export const ViewReport = () => {
<img src={arrow} alt="#" />
<p>Вернуться</p>
</Link>
<div className="summary__info">
<div className="summary__person">
<img
src={
profileInfo?.photo
? urlForLocal(profileInfo.photo)
: avatarMok
}
className="summary__avatar"
alt="avatar"
/>
<p className="summary__name">
{profileInfo?.fio || profileInfo?.username}{" "}
{profileInfo.specification}
</p>
<hr />
<div className="summary__direction">Front End</div>
<div className="summary__level">Middle+</div>
</div>
<div className="summary__skill">
<p>Ключевые навыки:</p>
<div>Java</div>
<div>Java</div>
<div>Solid</div>
<div>Java</div>
</div>
</div>
{localStorage.getItem("role_status") !== "18" && (
<div className="view-report__bar">
<Link

View File

@ -4,6 +4,25 @@
flex-direction: column;
min-height: 100vh;
.summary__skill {
color: #6f6f6f;
font-size: 14px;
font-weight: 300;
display: flex;
flex-direction: row;
align-items: center;
column-gap: 10px;
div {
font-size: 12px;
font-weight: 400;
border: #8dc63f 0.5px solid;
border-radius: 44px;
padding: 3px 20px;
}
}
.container {
max-width: 1160px;
display: flex;
@ -68,7 +87,7 @@
background: #ffffff;
border-top-left-radius: 12px;
border-top-right-radius: 12px;
padding: 20px 33px;
padding: 55px;
align-items: center;
column-gap: 20px;
height: 72px;

View File

@ -0,0 +1,57 @@
import React, { useState } from "react";
import { useNavigate } from "react-router";
import { AuthHeader } from "@components/Common/AuthHeader/AuthHeader";
import BaseButton from "@components/Common/BaseButton/BaseButton";
import { Footer } from "@components/Common/Footer/Footer";
import { Loader } from "@components/Common/Loader/Loader";
import SideBar from "@components/SideBar/SideBar";
import ITguild from "assets/images/logo/ITguild.svg";
import "./welcomePage.scss";
export const WelcomePage = () => {
const [loader, setLoader] = useState(false);
const navigate = useNavigate();
return (
<div className="welcome-page">
<AuthHeader />
<div className="welcome-page-component">
<div className="welcome-page-component__title">
<img src={ITguild}></img>
<h1>
Добро пожаловать в <span>ITGuild</span>
</h1>
</div>
<div className="welcome-page-component__text">
<p>
Создавайте и редактируйте задачи и проекты вместе с другими
участниками
<br /> команды. Сервис для работы с IT специалистами
</p>
</div>
<div className="button-box">
{loader ? (
<Loader />
) : (
<BaseButton
onClick={() => {
setLoader(true);
navigate("/profile");
}}
styles="button-box__submit"
>
Начать
</BaseButton>
)}
</div>
</div>
<SideBar />
<Footer />
</div>
);
};

View File

@ -0,0 +1,44 @@
.welcome-page {
overflow: hidden;
background-color: #f1f1f1;
min-height: 100vh;
display: flex;
flex-direction: column;
&-component {
display: flex;
align-items: center;
flex-direction: column;
flex: 1;
&__title {
display: flex;
flex-direction: column;
align-items: center;
font-weight: 500;
font-size: 30px;
line-height: 32px;
margin-bottom: 45px;
img {
width: 151px;
margin: 70px 0 19px 0;
}
}
&__text {
text-align: center;
font-weight: 400;
font-size: 16px;
line-height: 22px;
margin-bottom: 45px;
}
.button-box__submit {
width: 174px;
height: 50px;
font-size: 18px;
font-weight: 500;
}
}
}

View File

@ -17,6 +17,7 @@ import { RegistrationSetting } from "@pages/RegistrationSetting/RegistrationSett
import { SingleReportPage } from "@pages/SingleReportPage/SingleReportPage";
import { Stack } from "@pages/Stack/Stack";
import { TrackerIntro } from "@pages/TrackerIntro/TrackerIntro";
import { WelcomePage } from "@pages/WelcomePage/WelcomePage";
import { FreeDevelopers } from "@components/FreeDevelopers/FreeDevelopers";
@ -24,6 +25,7 @@ export const GuestPage = () => {
return (
<Routes>
<Route exact path="/auth" element={<Auth />} />
<Route exact path="/welcome-page" element={<WelcomePage />} />
<Route exact path="/stack" element={<Stack />} />
<Route exact path="/" element={<Landing />} />
<Route path="*" element={<Navigate to="/auth" replace />} />