diff --git a/src/components/Modal/Tracker/TrackerModal/trackerModal.scss b/src/components/Modal/Tracker/TrackerModal/trackerModal.scss index b9836970..3177638e 100644 --- a/src/components/Modal/Tracker/TrackerModal/trackerModal.scss +++ b/src/components/Modal/Tracker/TrackerModal/trackerModal.scss @@ -719,7 +719,6 @@ .button-add { margin: 0 30px; - align-self: flex-end; } } diff --git a/src/components/TrackerCardTask/TrackerCardTask.jsx b/src/components/TrackerCardTask/TrackerCardTask.jsx new file mode 100644 index 00000000..19093ef5 --- /dev/null +++ b/src/components/TrackerCardTask/TrackerCardTask.jsx @@ -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 ( +
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)} + > +
{ + if (window.innerWidth < 985) { + window.location.replace(`/tracker/task/${task.id}`); + } + }} + > +

{task.title}

+
+

+ {Boolean(task.mark.length) && ( +
+ {task.mark.map((tag) => { + return ( +
+

{tag.slug}

+
+ ); + })} +
+ )} +
+ {typeof task.execution_priority === "number" && ( +
+

+ + {priority[task.execution_priority]} + +
+ )} + {task.dead_line && ( +
+

+ + {getCorrectDate(task.dead_line)} + +
+ )} +
+ +
+
+ avatar + + {removeLast(task.executor?.fio) || "Исполнитель не назначен"} + +
+
+
+ commentsImg + {task.comment_count} +
+
+ filesImg + {task.file_count} +
+
+
+ item.id !== column.id)} + currentColumn={column} + task={task} + /> +
+ ); +}; + +export default TrackerCardTask; diff --git a/src/components/TrackerCardTask/trackerCardTask.scss b/src/components/TrackerCardTask/trackerCardTask.scss new file mode 100644 index 00000000..706bf955 --- /dev/null +++ b/src/components/TrackerCardTask/trackerCardTask.scss @@ -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; + } + } + } +} diff --git a/src/components/TrackerSelectExecutor/TrackerSelectExecutor.jsx b/src/components/TrackerSelectExecutor/TrackerSelectExecutor.jsx new file mode 100644 index 00000000..b4b014b4 --- /dev/null +++ b/src/components/TrackerSelectExecutor/TrackerSelectExecutor.jsx @@ -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 ( +
+

{removeLast(selectedExecutor.user.fio)}

+ avatar + delete +
+ ); + } else { + return ( +
setSelectedExecutorOpen(!selectExecutorOpen)} + > +

Выберите исполнителя

+ arrow + {selectExecutorOpen && ( +
+ {projectBoard.projectUsers.map((user) => { + return ( +
executorFilter(user)} + > +

{removeLast(user.user?.fio)}

+ avatar +
+ ); + })} +
+ )} +
+ ); + } +}; + +export default TrackerSelectExecutor; diff --git a/src/components/TrackerSelectExecutor/trackerSelectExecutor.scss b/src/components/TrackerSelectExecutor/trackerSelectExecutor.scss new file mode 100644 index 00000000..e7cce324 --- /dev/null +++ b/src/components/TrackerSelectExecutor/trackerSelectExecutor.scss @@ -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; + } + } + } + } + } + } +} diff --git a/src/components/TrackerTagList/TrackerTagList.jsx b/src/components/TrackerTagList/TrackerTagList.jsx new file mode 100644 index 00000000..6dceb9a1 --- /dev/null +++ b/src/components/TrackerTagList/TrackerTagList.jsx @@ -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 ( +
+
{ + setTags((prevState) => ({ + ...prevState, + open: !tags.open + })); + }} + > +

Список тегов

+ arrow +
+ {tags.open && ( +
+ {!tags.add && !tags.edit && ( +
+ setTags((prevState) => ({ + ...prevState, + add: true + })) + } + > +

Добавить новый тег

+ + +
+ )} + {!tags.add && !tags.edit && ( +
+ {projectBoard.mark.map((tag) => { + return ( +
+
+ +
+ {tag.slug} +

{tag.title}

+
+
+
+ edit { + setTags((prevState) => ({ + ...prevState, + edit: true + })); + setTagInfo({ + description: tag.title, + name: tag.slug, + editMarkId: tag.id + }); + setColor(tag.color); + }} + /> + deleteTag(tag.id)} + className="delete" + src={close} + alt="delete" + /> +
+
+ ); + })} +
+ )} + {(tags.add || tags.edit) && ( +
+ + setTagInfo((prevState) => ({ + ...prevState, + description: e.target.value + })) + } + /> + + setTagInfo((prevState) => ({ + ...prevState, + name: e.target.value + })) + } + /> + + + {(tags.add || tags.edit) && ( + + )} +
+ )} +
+ )} +
+ ); +}; + +export default TrackerTagList; diff --git a/src/components/TrackerTagList/trackerTagList.scss b/src/components/TrackerTagList/trackerTagList.scss new file mode 100644 index 00000000..932f4b15 --- /dev/null +++ b/src/components/TrackerTagList/trackerTagList.scss @@ -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; + } + } + } + } + } + } +} diff --git a/src/pages/ProjectTracker/ProjectTracker.jsx b/src/pages/ProjectTracker/ProjectTracker.jsx index 5494adea..0f1d9925 100644 --- a/src/pages/ProjectTracker/ProjectTracker.jsx +++ b/src/pages/ProjectTracker/ProjectTracker.jsx @@ -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 && accept} - {selectedExecutor ? ( -
-

{removeLast(selectedExecutor.user.fio)}

- avatar - delete -
- ) : ( -
- setSelectedExecutorOpen(!selectExecutorOpen) - } - > -

Выберите исполнителя

- arrow - {selectExecutorOpen && ( -
- {projectBoard.projectUsers.map((user) => { - return ( -
executorFilter(user)} - > -

{removeLast(user.user?.fio)}

- avatar -
- ); - })} -
- )} -
- )} -
-
{ - setTags((prevState) => ({ - ...prevState, - open: !tags.open - })); - }} - > -

Список тегов

- arrow -
- {tags.open && ( -
- {!tags.add && !tags.edit && ( -
- setTags((prevState) => ({ - ...prevState, - add: true - })) - } - > -

Добавить новый тег

- + -
- )} - {!tags.add && !tags.edit && ( -
- {projectBoard.mark.map((tag) => { - return ( -
-
- -
- - {tag.slug} - -

- {tag.title} -

-
-
-
- edit { - setTags((prevState) => ({ - ...prevState, - edit: true - })); - setTagInfo({ - description: tag.title, - name: tag.slug, - editMarkId: tag.id - }); - setColor(tag.color); - }} - /> - deleteTag(tag.id)} - className="delete" - src={close} - alt="delete" - /> -
-
- ); - })} -
- )} - {(tags.add || tags.edit) && ( -
- - setTagInfo((prevState) => ({ - ...prevState, - description: e.target.value - })) - } - /> - - setTagInfo((prevState) => ({ - ...prevState, - name: e.target.value - })) - } - /> - - - {(tags.add || tags.edit) && ( - - )} -
- )} -
- )} -
+ + + + +

К списку проектов

arrow @@ -878,7 +506,7 @@ export const ProjectTracker = () => { )}
- {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 ( -
- 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)} - > -
{ - if (window.innerWidth < 985) { - window.location.replace( - `/tracker/task/${task.id}` - ); - } - }} - > -

- {task.title} -

-
-

- {Boolean(task.mark.length) && ( -
- {task.mark.map((tag) => { - return ( -
-

{tag.slug}

-
- ); - })} -
- )} -
- {typeof task.execution_priority === - "number" && ( -
-

- - {priority[task.execution_priority]} - -
- )} - {task.dead_line && ( -
-

- - {getCorrectDate(task.dead_line)} - -
- )} -
- -
-
- avatar - - {removeLast(task.executor?.fio) || - "Исполнитель не назначен"} - -
-
-
- commentsImg - {task.comment_count} -
-
- filesImg - {task.file_count} -
-
-
- item.id !== column.id - )} - currentColumn={column} - task={task} - /> -
+ ); })}
diff --git a/src/pages/Tracker/tracker.scss b/src/pages/Tracker/tracker.scss index 06bd1c07..fa6e8206 100644 --- a/src/pages/Tracker/tracker.scss +++ b/src/pages/Tracker/tracker.scss @@ -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 {