guild_front/src/pages/ProjectTracker/ProjectTracker.jsx
2024-04-11 22:24:08 +03:00

572 lines
20 KiB
JavaScript
Raw Blame History

This file contains ambiguous Unicode characters

This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.

import moment from "moment";
import React, { useEffect, useRef, useState } from "react";
import { useDispatch, useSelector } from "react-redux";
import { Link, useParams } from "react-router-dom";
import {
activeLoader,
filterCreatedByMe,
filteredParticipateTasks,
getBoarderLoader,
getProjectBoard,
modalToggle,
moveProjectTask,
setColumnId,
setColumnName,
setColumnPriority,
setProjectBoardFetch,
setToggleTab
} from "@redux/projectsTrackerSlice";
import { urlForLocal } from "@utils/helper";
import { apiRequest } from "@api/request";
import { useNotification } from "@hooks/useNotification";
import BaseButton from "@components/Common/BaseButton/BaseButton";
import { Footer } from "@components/Common/Footer/Footer";
import { Loader } from "@components/Common/Loader/Loader";
import AcceptModal from "@components/Modal/AcceptModal/AcceptModal";
import ListEmployees from "@components/Modal/Tracker/ListEmployees/ListEmployees";
import ModalTicket from "@components/Modal/Tracker/ModalTicket/ModalTicket";
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 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 category from "assets/icons/category.svg";
import del from "assets/icons/delete.svg";
import edit from "assets/icons/edit.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 avatarMok from "assets/images/avatarMok.png";
export const ProjectTracker = () => {
const dispatch = useDispatch();
const projectId = useParams();
const [openColumnSelect, setOpenColumnSelect] = useState({});
const [selectedTab, setSelectedTab] = useState(0);
const [priorityTask, setPriorityTask] = useState(0);
const [wrapperHover, setWrapperHover] = useState({});
const [modalAdd, setModalAdd] = useState(false);
const [modalActiveTicket, setModalActiveTicket] = useState(false);
const [selectedTicket, setSelectedTicket] = useState({});
const [personListOpen, setPersonListOpen] = useState(false);
const [acceptModalOpen, setAcceptModalOpen] = useState(false);
const [currentColumnDelete, setCurrentColumnDelete] = useState(null);
const [checkBoxParticipateTasks, setCheckBoxParticipateTasks] =
useState(false);
const [filteredNoTasks, setFilteredNoTasks] = useState(false);
const [checkBoxMyTasks, setCheckBoxMyTasks] = useState(false);
const [selectedExecutor, setSelectedExecutor] = useState(null);
const startWrapperIndexTest = useRef({});
const projectBoard = useSelector(getProjectBoard);
const loader = useSelector(getBoarderLoader);
const { showNotification } = useNotification();
useEffect(() => {
dispatch(activeLoader());
dispatch(setProjectBoardFetch(projectId.id));
initListeners();
}, []);
useEffect(() => {
let columnsTasksEmpty = true;
if (Object.keys(projectBoard).length) {
projectBoard.columns.forEach((column) => {
if (column.tasks.length) columnsTasksEmpty = false;
setOpenColumnSelect((prevState) => ({
...prevState,
[column.id]: false
}));
});
}
if (
columnsTasksEmpty &&
(checkBoxMyTasks || selectedExecutor || checkBoxParticipateTasks)
) {
setFilteredNoTasks(true);
} else {
setFilteredNoTasks(false);
}
}, [projectBoard]);
function dragOverHandler(e) {
e.preventDefault();
}
function dragEnterHandler(columnId) {
if (columnId === startWrapperIndexTest.current.index) {
return;
}
setWrapperHover((prevState) => ({
[prevState]: false,
[columnId]: true
}));
}
function dragDropHandler(e, columnId) {
e.preventDefault();
setWrapperHover((prevState) => ({
[prevState]: false
}));
if (
startWrapperIndexTest.current.index === columnId ||
e.target.className.includes("__item")
) {
return;
}
if (columnId !== startWrapperIndexTest.current.index) {
dispatch(
moveProjectTask({
startWrapperIndex: startWrapperIndexTest.current,
columnId
})
);
}
}
function selectedTabTask(columnId, length) {
setSelectedTab(columnId);
dispatch(modalToggle("create-ticket-project"));
setModalAdd(true);
setPriorityTask(length);
}
function openTicket(e, task) {
setSelectedTicket(task);
setModalActiveTicket(true);
document.body.style.overflow = "hidden";
}
function deleteColumn(column) {
const priorityColumns = [];
apiRequest("/project-column/update-column", {
method: "PUT",
data: {
column_id: column.id,
project_id: projectBoard.id,
status: 0
}
}).then(() => {
if (column.priority < projectBoard.columns.length) {
for (let i = column.priority; i < projectBoard.columns.length; i++) {
const currentColumn = {
column_id: projectBoard.columns[i].id,
priority: i
};
priorityColumns.push(currentColumn);
}
apiRequest("/project-column/set-priority", {
method: "POST",
data: {
project_id: projectBoard.id,
data: JSON.stringify(priorityColumns)
}
}).then(() => {
dispatch(setProjectBoardFetch(projectBoard.id));
});
} else {
dispatch(setProjectBoardFetch(projectBoard.id));
}
showNotification({
show: true,
text: "Колонка удалена",
type: "error"
});
});
}
function filterParticipateTasks() {
if (!checkBoxParticipateTasks) {
dispatch(filteredParticipateTasks(Number(localStorage.getItem("id"))));
} else {
dispatch(setProjectBoardFetch(projectId.id));
setCheckBoxParticipateTasks(false);
setCheckBoxMyTasks(false);
setSelectedExecutor(null);
}
setCheckBoxParticipateTasks(!checkBoxParticipateTasks);
}
function filterMyTask() {
if (!checkBoxMyTasks) {
dispatch(filterCreatedByMe(Number(localStorage.getItem("id"))));
} else {
dispatch(setProjectBoardFetch(projectId.id));
setCheckBoxParticipateTasks(false);
setCheckBoxMyTasks(false);
setSelectedExecutor(null);
}
setCheckBoxMyTasks(!checkBoxMyTasks);
}
function deleteSelectedExecutorFilter() {
setSelectedExecutor(null);
setCheckBoxParticipateTasks(false);
setCheckBoxMyTasks(false);
dispatch(setProjectBoardFetch(projectId.id));
}
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("add-person") ||
div.classList.contains("persons__list"))
)
) {
setPersonListOpen(false);
}
if (
event &&
!path.find(
(div) =>
div.classList &&
(div.classList.contains("board__head__more") ||
div.classList.contains("column__select"))
)
) {
setOpenColumnSelect((prevState) => {
const newState = {};
for (const key in prevState) {
newState[key] = false;
}
return newState;
});
}
};
function closeAcceptModal() {
setAcceptModalOpen(false);
}
return (
<div className="tracker">
<ProfileHeader />
<Navigation />
<div className="container">
<div className="tracker__content">
<ProfileBreadcrumbs
links={[
{ name: "Главная", link: "/profile" },
{ name: "Трекер", link: "/profile/tracker" }
]}
/>
<h2 className="tracker__title">Управление проектами с трекером</h2>
</div>
</div>
<div className="tracker__tabs">
<div className="tracker__tabs__head">
<Link
to="/profile/tracker"
className="tab active-tab tab"
onClick={() => dispatch(setToggleTab(1))}
>
<img src={project} alt="img" />
<p>Проекты </p>
</Link>
<Link
to="/profile/tracker"
className="tab"
onClick={() => dispatch(setToggleTab(2))}
>
<img src={tasks} alt="img" />
<p>Все мои задачи</p>
</Link>
</div>
<div className="tracker__tabs__content">
<TrackerModal
active={modalAdd}
setActive={setModalAdd}
selectedTab={selectedTab}
priorityTask={priorityTask}
projectUsers={projectBoard.projectUsers}
projectMarks={projectBoard.mark}
/>
{loader && <Loader style="green" />}
{!loader && (
<div className="tracker__tabs__content__tasks tasks active__content">
<div className="tasks__head">
<div className="tasks__head__wrapper">
<div className="tasks__head__title">
<img src={category}></img>
<h5>{projectBoard.name}</h5>
</div>
<div className="tasks__head__add">
<BaseButton
onClick={() => {
dispatch(modalToggle("create-column"));
setModalAdd(true);
}}
styles={"button-add-column"}
>
+
</BaseButton>
<p>добавить колонку</p>
</div>
<div
className={
projectBoard.projectUsers?.length
? "tasks__head__persons"
: "tasks__head__persons no-project-users"
}
>
{Boolean(projectBoard.projectUsers?.length) && (
<div
className={
projectBoard.projectUsers?.length == 1
? "one-person"
: "project-persons"
}
>
{projectBoard.projectUsers.slice(0, 3).map((person) => {
return (
<img
key={person.user_id}
src={
person.user?.avatar
? urlForLocal(person.user.avatar)
: avatarMok
}
alt="avatar"
/>
);
})}
</div>
)}
{projectBoard.projectUsers?.length > 3 && (
<span className="count-persons">
+{projectBoard.projectUsers?.length - 3}
</span>
)}
<span
className="add-person"
onClick={() => {
setPersonListOpen(true);
}}
>
+
</span>
<ListEmployees
active={personListOpen}
setActiveListEmpl={setPersonListOpen}
setModalAdd={setModalAdd}
projectBoard={projectBoard}
/>
</div>
<div
className="tasks__head__checkBox"
onClick={filterParticipateTasks}
>
<span>Участвую</span>
<div className="tasks__head__checkBox__box">
{checkBoxParticipateTasks && (
<img src={accept} alt="accept" />
)}
</div>
</div>
<div className="tasks__head__checkBox" onClick={filterMyTask}>
<span>Мои</span>
<div className="tasks__head__checkBox__box">
{checkBoxMyTasks && <img src={accept} alt="accept" />}
</div>
</div>
<TrackerSelectExecutor
deleteSelectedExecutor={deleteSelectedExecutorFilter}
projectBoard={projectBoard}
selectedExecutor={selectedExecutor}
setSelectedExecutor={setSelectedExecutor}
/>
<TrackerTagList projectBoard={projectBoard} />
<Link to="/profile/tracker" className="tasks__head__back">
<p>К списку проектов</p>
<img src={arrow} alt="arrow" />
</Link>
</div>
</div>
{Boolean(modalActiveTicket) && (
<ModalTicket
active={modalActiveTicket}
setActive={setModalActiveTicket}
task={selectedTicket}
projectId={projectBoard.id}
projectName={projectBoard.name}
projectUsers={projectBoard.projectUsers}
projectOwnerId={projectBoard.owner_id}
projectMarks={projectBoard.mark}
/>
)}
<div className="tasks__container">
{Boolean(projectBoard?.columns) &&
!filteredNoTasks &&
Boolean(projectBoard.columns.length) &&
projectBoard.columns.map((column) => {
return (
<div
key={column.id}
onDragOver={(e) => dragOverHandler(e)}
onDragEnter={() => dragEnterHandler(column.id)}
onDrop={(e) => dragDropHandler(e, column.id)}
className={`tasks__board ${
wrapperHover[column.id] ? "tasks__board__hover" : ""
}`}
>
<div className="board__head">
<span>{column.title}</span>
<div className="board__head__more">
<span
className="add"
onClick={() => {
selectedTabTask(
column.id,
projectBoard?.columns && column.tasks.length
? column.tasks[0].priority - 1
: 1
);
}}
>
+
</span>
<span
onClick={() => {
setOpenColumnSelect((prevState) => ({
...prevState,
[column.id]: true
}));
}}
className="more"
>
...
</span>
</div>
</div>
{openColumnSelect[column.id] && (
<div className="column__select">
<div
className="column__select__item"
onClick={() => {
setOpenColumnSelect((prevState) => ({
...prevState,
[column.id]: false
}));
dispatch(modalToggle("edit-column"));
dispatch(setColumnName(column.title));
dispatch(setColumnId(column.id));
dispatch(setColumnPriority(column.priority));
setModalAdd(true);
}}
>
<img src={edit} alt="edit" />
<span>Изменить</span>
</div>
<div
className="column__select__item"
onClick={() => {
if (column.tasks.length) {
setAcceptModalOpen(true);
setCurrentColumnDelete(column);
} else {
deleteColumn(column);
}
}}
>
<img src={del} alt="delete" />
<span>Удалить</span>
</div>
</div>
)}
<div className="tasks-container">
{column.tasks.map((task, index) => {
const dateDeadline = new Date(task.dead_line);
const currentDate = moment().format(
"YYYY-MM-DD HH:mm:ss"
);
const titleColor =
task.dead_line &&
dateDeadline < new Date(currentDate)
? "red"
: "#1a1919";
return (
<TrackerCardTask
column={column}
key={index}
openTicket={openTicket}
projectBoard={projectBoard}
setWrapperHover={setWrapperHover}
startWrapperIndexTest={startWrapperIndexTest}
task={task}
titleColor={titleColor}
/>
);
})}
</div>
</div>
);
})}
{Boolean(projectBoard?.columns) &&
!Boolean(projectBoard.columns.length) && (
<div className="tasks__board__no-items">
В проекте нет задач.
</div>
)}
{filteredNoTasks && (
<div className="tasks__board__no-tasks">
<div className="tasks__board__no-tasks-info">
<img src={trackerNoTasks} alt="no-tasks" />
<p>Пока нет подходящих задач</p>
</div>
<p className="tasks__board__no-tasks-more">
Ставьте задачи, следите за прогрессом, ведите учёт
рабочего времени
</p>
</div>
)}
</div>
</div>
)}
</div>
</div>
{acceptModalOpen && (
<AcceptModal
title={
"В колонке ещё есть задачи, Вы действительно хотите удалить её ?"
}
closeModal={closeAcceptModal}
agreeHandler={() => deleteColumn(currentColumnDelete)}
/>
)}
<Footer />
</div>
);
};