Compare commits

..

12 Commits

9 changed files with 1322 additions and 1209 deletions

View File

@ -719,7 +719,6 @@
.button-add { .button-add {
margin: 0 30px; 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 moment from "moment";
import React, { useEffect, useRef, useState } from "react"; import React, { useEffect, useRef, useState } from "react";
import { HexColorPicker } from "react-colorful";
import { useDispatch, useSelector } from "react-redux"; import { useDispatch, useSelector } from "react-redux";
import { Link, useParams } from "react-router-dom"; import { Link, useParams } from "react-router-dom";
import { import {
activeLoader, activeLoader,
addNewTagToProject,
deleteTagProject,
filterCreatedByMe, filterCreatedByMe,
filteredExecutorTasks,
filteredParticipateTasks, filteredParticipateTasks,
getBoarderLoader, getBoarderLoader,
getProjectBoard, getProjectBoard,
modalToggle, modalToggle,
movePositionProjectTask,
moveProjectTask, moveProjectTask,
setColumnId, setColumnId,
setColumnName, setColumnName,
@ -23,7 +18,7 @@ import {
setToggleTab setToggleTab
} from "@redux/projectsTrackerSlice"; } from "@redux/projectsTrackerSlice";
import { removeLast, urlForLocal } from "@utils/helper"; import { urlForLocal } from "@utils/helper";
import { apiRequest } from "@api/request"; import { apiRequest } from "@api/request";
@ -39,25 +34,20 @@ import TrackerModal from "@components/Modal/Tracker/TrackerModal/TrackerModal";
import { Navigation } from "@components/Navigation/Navigation"; import { Navigation } from "@components/Navigation/Navigation";
import { ProfileBreadcrumbs } from "@components/ProfileBreadcrumbs/ProfileBreadcrumbs"; import { ProfileBreadcrumbs } from "@components/ProfileBreadcrumbs/ProfileBreadcrumbs";
import { ProfileHeader } from "@components/ProfileHeader/ProfileHeader"; 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 arrow from "assets/icons/arrows/arrowRight.png";
import arrowDown from "assets/icons/arrows/selectArrow.png";
import category from "assets/icons/category.svg"; 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 del from "assets/icons/delete.svg";
import edit from "assets/icons/edit.svg"; import edit from "assets/icons/edit.svg";
import filesBoard from "assets/icons/filesBoard.svg";
import trackerNoTasks from "assets/icons/trackerNoTasks.svg"; import trackerNoTasks from "assets/icons/trackerNoTasks.svg";
import project from "assets/icons/trackerProject.svg"; import project from "assets/icons/trackerProject.svg";
import tasks from "assets/icons/trackerTasks.svg"; import tasks from "assets/icons/trackerTasks.svg";
import accept from "assets/images/accept.png"; import accept from "assets/images/accept.png";
import archive from "assets/images/archiveIcon.png";
import avatarMok from "assets/images/avatarMok.png"; import avatarMok from "assets/images/avatarMok.png";
import { getCorrectDate } from "../../utils/calendarHelper";
export const ProjectTracker = () => { export const ProjectTracker = () => {
const dispatch = useDispatch(); const dispatch = useDispatch();
const projectId = useParams(); const projectId = useParams();
@ -66,43 +56,22 @@ export const ProjectTracker = () => {
const [selectedTab, setSelectedTab] = useState(0); const [selectedTab, setSelectedTab] = useState(0);
const [priorityTask, setPriorityTask] = useState(0); const [priorityTask, setPriorityTask] = useState(0);
const [wrapperHover, setWrapperHover] = useState({}); const [wrapperHover, setWrapperHover] = useState({});
const [taskHover, setTaskHover] = useState({});
const [modalAdd, setModalAdd] = useState(false); const [modalAdd, setModalAdd] = useState(false);
const [modalActiveTicket, setModalActiveTicket] = useState(false); const [modalActiveTicket, setModalActiveTicket] = useState(false);
const [selectedTicket, setSelectedTicket] = useState({}); const [selectedTicket, setSelectedTicket] = useState({});
const [personListOpen, setPersonListOpen] = useState(false); const [personListOpen, setPersonListOpen] = useState(false);
const [tags, setTags] = useState({
open: false,
add: false,
edit: false
});
const [acceptModalOpen, setAcceptModalOpen] = useState(false); const [acceptModalOpen, setAcceptModalOpen] = useState(false);
const [currentColumnDelete, setCurrentColumnDelete] = useState(null); const [currentColumnDelete, setCurrentColumnDelete] = useState(null);
const [color, setColor] = useState("#aabbcc");
const [tagInfo, setTagInfo] = useState({ description: "", name: "" });
const [checkBoxParticipateTasks, setCheckBoxParticipateTasks] = const [checkBoxParticipateTasks, setCheckBoxParticipateTasks] =
useState(false); useState(false);
const [filteredNoTasks, setFilteredNoTasks] = useState(false); const [filteredNoTasks, setFilteredNoTasks] = useState(false);
const [checkBoxMyTasks, setCheckBoxMyTasks] = useState(false); const [checkBoxMyTasks, setCheckBoxMyTasks] = useState(false);
const [selectedExecutor, setSelectedExecutor] = useState(null); const [selectedExecutor, setSelectedExecutor] = useState(null);
const [selectExecutorOpen, setSelectedExecutorOpen] = useState(false);
const startWrapperIndexTest = useRef({}); const startWrapperIndexTest = useRef({});
const projectBoard = useSelector(getProjectBoard); const projectBoard = useSelector(getProjectBoard);
const loader = useSelector(getBoarderLoader); const loader = useSelector(getBoarderLoader);
const { showNotification } = useNotification(); const { showNotification } = useNotification();
const priority = {
2: "Высокий",
1: "Средний",
0: "Низкий"
};
const priorityClass = {
2: "high",
1: "middle",
0: "low"
};
useEffect(() => { useEffect(() => {
dispatch(activeLoader()); dispatch(activeLoader());
dispatch(setProjectBoardFetch(projectId.id)); dispatch(setProjectBoardFetch(projectId.id));
@ -110,8 +79,6 @@ export const ProjectTracker = () => {
}, []); }, []);
useEffect(() => { useEffect(() => {
const tasksHover = {};
const columnHover = {};
let columnsTasksEmpty = true; let columnsTasksEmpty = true;
if (Object.keys(projectBoard).length) { if (Object.keys(projectBoard).length) {
projectBoard.columns.forEach((column) => { projectBoard.columns.forEach((column) => {
@ -120,8 +87,6 @@ export const ProjectTracker = () => {
...prevState, ...prevState,
[column.id]: false [column.id]: false
})); }));
columnHover[column.id] = false;
column.tasks.forEach((task) => (tasksHover[task.id] = false));
}); });
} }
if ( if (
@ -132,48 +97,8 @@ export const ProjectTracker = () => {
} else { } else {
setFilteredNoTasks(false); setFilteredNoTasks(false);
} }
setWrapperHover(columnHover);
setTaskHover(tasksHover);
}, [projectBoard]); }, [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) { function dragOverHandler(e) {
e.preventDefault(); e.preventDefault();
} }
@ -288,11 +213,6 @@ export const ProjectTracker = () => {
setCheckBoxMyTasks(!checkBoxMyTasks); setCheckBoxMyTasks(!checkBoxMyTasks);
} }
function executorFilter(user) {
dispatch(filteredExecutorTasks(user.user_id));
setSelectedExecutor(user);
}
function deleteSelectedExecutorFilter() { function deleteSelectedExecutorFilter() {
setSelectedExecutor(null); setSelectedExecutor(null);
setCheckBoxParticipateTasks(false); setCheckBoxParticipateTasks(false);
@ -300,81 +220,6 @@ export const ProjectTracker = () => {
dispatch(setProjectBoardFetch(projectId.id)); 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 = () => { const initListeners = () => {
document.addEventListener("click", closeByClickingOut); document.addEventListener("click", closeByClickingOut);
}; };
@ -394,32 +239,6 @@ export const ProjectTracker = () => {
setPersonListOpen(false); 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 ( if (
event && event &&
!path.find( !path.find(
@ -577,207 +396,16 @@ export const ProjectTracker = () => {
{checkBoxMyTasks && <img src={accept} alt="accept" />} {checkBoxMyTasks && <img src={accept} alt="accept" />}
</div> </div>
</div> </div>
{selectedExecutor ? (
<div className="tasks__head__executor-selected"> <TrackerSelectExecutor
<p>{removeLast(selectedExecutor.user.fio)}</p> deleteSelectedExecutor={deleteSelectedExecutorFilter}
<img projectBoard={projectBoard}
className="avatar" selectedExecutor={selectedExecutor}
src={ setSelectedExecutor={setSelectedExecutor}
selectedExecutor.user?.avatar
? urlForLocal(selectedExecutor.user.avatar)
: avatarMok
}
alt="avatar"
/> />
<img
className="delete" <TrackerTagList projectBoard={projectBoard} />
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>
<Link to="/profile/tracker" className="tasks__head__back"> <Link to="/profile/tracker" className="tasks__head__back">
<p>К списку проектов</p> <p>К списку проектов</p>
<img src={arrow} alt="arrow" /> <img src={arrow} alt="arrow" />
@ -878,7 +506,7 @@ export const ProjectTracker = () => {
</div> </div>
)} )}
<div className="tasks-container"> <div className="tasks-container">
{column.tasks.map((task) => { {column.tasks.map((task, index) => {
const dateDeadline = new Date(task.dead_line); const dateDeadline = new Date(task.dead_line);
const currentDate = moment().format( const currentDate = moment().format(
"YYYY-MM-DD HH:mm:ss" "YYYY-MM-DD HH:mm:ss"
@ -889,119 +517,16 @@ export const ProjectTracker = () => {
? "red" ? "red"
: "#1a1919"; : "#1a1919";
return ( return (
<div <TrackerCardTask
key={task.id} column={column}
className={`tasks__board__item ${ key={index}
taskHover[task.id] ? "task__hover" : "" openTicket={openTicket}
}`} projectBoard={projectBoard}
draggable={true} setWrapperHover={setWrapperHover}
onDragStart={(e) => startWrapperIndexTest={startWrapperIndexTest}
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} task={task}
titleColor={titleColor}
/> />
</div>
); );
})} })}
</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 { &__back {
cursor: pointer; cursor: pointer;
display: flex; display: flex;
@ -517,211 +391,6 @@
display: none; 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 { &__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 { .board {
&__head { &__head {
position: relative; position: relative;
@ -1958,7 +1261,7 @@
.table { .table {
&__search { &__search {
display: flex; display: flex;
background: #F0F2F5; background: #f0f2f5;
border-radius: 5px; border-radius: 5px;
width: 100%; width: 100%;
padding: 14px 12px; padding: 14px 12px;
@ -1976,11 +1279,11 @@
border: none; border: none;
outline: none; outline: none;
font-size: 16px; font-size: 16px;
color: #9BABC5; color: #9babc5;
width: 100%; width: 100%;
&::placeholder { &::placeholder {
color: #9BABC5; color: #9babc5;
} }
} }
} }
@ -1995,12 +1298,12 @@
width: 32px; width: 32px;
border-radius: 5px; border-radius: 5px;
height: 32px; height: 32px;
color: #2E3A59; color: #2e3a59;
} }
.switch { .switch {
border: none; border: none;
background: #F0F2F5; background: #f0f2f5;
font-weight: 600; font-weight: 600;
} }
@ -2016,12 +1319,12 @@
background: white; background: white;
.page { .page {
border: 1px solid #E8ECF8; border: 1px solid #e8ecf8;
background: none; background: none;
&--active { &--active {
border: none; border: none;
background: #9DA65D; background: #9da65d;
color: white; color: white;
} }
} }
@ -2029,17 +1332,20 @@
} }
table { 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 { th {
border-top: none; border-top: none;
border-bottom: 1px solid #F5F6F8; border-bottom: 1px solid #f5f6f8;
color: #2E3A59; color: #2e3a59;
padding: 0 7.5px 15px; padding: 0 7.5px 15px;
} }
td { td {
padding: 22px 7.5px; padding: 22px 7.5px;
color: #2E3A59; color: #2e3a59;
border-top: none; border-top: none;
p { p {