2024-07-23 13:34:05 +03:00

963 lines
31 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 ClassicEditor from "@ckeditor/ckeditor5-build-classic";
import { CKEditor } from "@ckeditor/ckeditor5-react";
import ru from "date-fns/locale/ru";
import React, { useEffect, useState } from "react";
import DatePicker, { registerLocale } from "react-datepicker";
import "react-datepicker/dist/react-datepicker.css";
import { useDispatch, useSelector } from "react-redux";
import { getProfileInfo } from "@redux/outstaffingSlice";
import {
addPersonToProject,
editColumnName,
editProjectName,
getColumnId,
getColumnName,
getColumnPriority,
getProjectBoard,
getValueModalType,
setColumnName,
setColumnPriority,
setProject,
setProjectBoardFetch
} from "@redux/projectsTrackerSlice";
import { getCorrectDate } from "@utils/calendarHelper";
import { getCorrectRequestDate, removeLast, urlForLocal } from "@utils/helper";
import { apiRequest } from "@api/request";
import { useNotification } from "@hooks/useNotification";
import BaseButton from "@components/Common/BaseButton/BaseButton";
import { Loader } from "@components/Common/Loader/Loader";
import ModalLayout from "@components/Common/ModalLayout/ModalLayout";
import arrowRight from "assets/icons/arrows/arrowRightCreateTask.svg";
import arrowDown from "assets/icons/arrows/selectArrow.png";
import close from "assets/icons/close.png";
import calendarImg from "assets/icons/createTaskCalendar.svg";
import crossWhite from "assets/icons/crossWhite.svg";
import avatarMok from "assets/images/avatarMok.webp";
import "./trackerModal.scss";
registerLocale("ru", ru);
export const TrackerModal = ({
active,
setActive,
selectedTab,
defautlInput,
titleProject,
projectId,
priorityTask,
projectUsers,
projectMarks
}) => {
const dispatch = useDispatch();
const projectBoard = useSelector(getProjectBoard);
const columnName = useSelector(getColumnName);
const columnId = useSelector(getColumnId);
const columnPriority = useSelector(getColumnPriority);
const profileInfo = useSelector(getProfileInfo);
const modalType = useSelector(getValueModalType);
const [projectName, setProjectName] = useState(defautlInput);
const [valueColumn, setValueColumn] = useState("");
const [nameProject, setNameProject] = useState("");
const [valueTicket, setValueTicket] = useState("");
const [descriptionTicket, setDescriptionTicket] = useState("");
const [workers, setWorkers] = useState([]);
const [selectWorkersOpen, setSelectWorkersOpen] = useState(false);
const [selectedWorker, setSelectedWorker] = useState(null);
const [emailWorker, setEmailWorker] = useState("");
const [emailError, setEmailError] = useState("");
const [selectColumnPriority, setSelectColumnPriority] = useState(
"Выберите приоритет колонки"
);
const [selectedExecutorTask, setSelectedExecutorTask] = useState(
"Выберите исполнителя"
);
const [selectExecutorTaskOpen, setSelectExecutorTaskOpen] = useState(false);
const [correctProjectUsers, setCorrectProjectUsers] = useState([]);
const [correctProjectTags, setCorrectProjectTags] = useState([]);
const [taskTags, setTaskTags] = useState([]);
const [selectTagsOpen, setSelectTagsOpen] = useState(false);
const [selectedPriority, setSelectedPriority] = useState(null);
const [selectPriority, setSelectPriority] = useState(false);
const [selectColumnPriorityOpen, setSelectColumnPriorityOpen] =
useState(false);
const { showNotification } = useNotification();
const [deadLineDate, setDeadLineDate] = useState("");
const [datePickerOpen, setDatePickerOpen] = useState(false);
const [startDate, setStartDate] = useState(new Date());
const [loader, setLoader] = useState(false);
const priority = [
{
name: "Высокий",
key: 2
},
{
name: "Средний",
key: 1
},
{
name: "Низкий",
key: 0
}
];
function createTab() {
if (!valueColumn) {
showNotification({ show: true, text: "Введите название", type: "error" });
return;
}
const existingColumn = projectBoard.columns.find(
(column) => column.title === valueColumn
);
if (existingColumn) {
showNotification({
show: true,
text: "Колонка с таким названием уже существует",
type: "error"
});
return;
}
apiRequest("/project-column/create-column", {
method: "POST",
data: {
project_id: projectBoard.id,
priority: projectBoard.columns.length
? projectBoard.columns.at(-1).priority + 1
: 1,
title: valueColumn
}
})
.then(() => {
dispatch(setProjectBoardFetch(projectBoard.id));
showNotification({
show: true,
text: "Колонка успешно создана",
type: "success"
});
})
.catch(() => {
showNotification({
show: true,
text: "Ошибка при создании колонки",
type: "error"
});
});
setValueColumn("");
setActive(false);
}
function createTicket() {
if (!valueTicket || !descriptionTicket) {
showNotification({
show: true,
text: "Введите название и описание",
type: "error"
});
return;
}
setLoader(true);
apiRequest("/task/create-task", {
method: "POST",
data: {
project_id: projectBoard.id,
title: valueTicket,
description: descriptionTicket,
status: 1,
user_id: localStorage.getItem("id"),
column_id: selectedTab,
execution_priority: selectedPriority ? selectedPriority.key : "",
priority: priorityTask,
dead_line: deadLineDate ? getCorrectRequestDate(deadLineDate) : ""
}
}).then((res) => {
if (res.status === 500) {
showNotification({
show: true,
text: "Задача с таким именем уже существует",
type: "error"
});
setLoader(false);
} else {
for (let i = 0; i < taskTags.length; i++) {
apiRequest("/mark/attach", {
method: "POST",
data: {
mark_id: taskTags[i].id,
entity_type: 2,
entity_id: res.id
}
}).then(() => {
setTaskTags([]);
});
}
if (selectedExecutorTask.user_id) {
apiRequest("/task/update-task", {
method: "PUT",
data: {
task_id: res.id,
executor_id: selectedExecutorTask.user_id
}
}).then(() => {
dispatch(setProjectBoardFetch(projectBoard.id));
setActive(false);
setValueTicket("");
setDescriptionTicket("");
setSelectedExecutorTask("Выберите исполнителя");
setSelectedPriority(null);
setLoader(false);
});
} else {
setActive(false);
setLoader(false);
setValueTicket("");
setDescriptionTicket("");
dispatch(setProjectBoardFetch(projectBoard.id));
}
setDeadLineDate("");
showNotification({
show: true,
text: "Задача успешно создана",
type: "success"
});
}
});
}
function editProject() {
apiRequest("/project/update", {
method: "PUT",
data: {
project_id: projectId,
name: projectName
}
}).then(() => {
setActive(false);
dispatch(editProjectName({ id: projectId, name: projectName }));
showNotification({
show: true,
text: "Название проекта успешно изменено",
type: "success"
});
});
}
function changeColumnParams() {
projectBoard.columns.forEach((column) => {
if (column.id === columnId && column.priority !== columnPriority) {
const priorityColumns = [
{
column_id: column.id,
priority: Number(columnPriority)
}
];
for (let i = column.priority; i < columnPriority; i++) {
const currentColumn = {
column_id: projectBoard.columns[i].id,
priority: i
};
priorityColumns.push(currentColumn);
}
for (let i = column.priority; i > columnPriority; i--) {
const currentColumn = {
column_id: projectBoard.columns[i - 2].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));
});
}
});
changeColumnTitle();
}
function changeColumnTitle() {
apiRequest("/project-column/update-column", {
method: "PUT",
data: {
column_id: columnId,
title: columnName
}
}).then(() => {
const existingColumn = projectBoard.columns.find(
(column) => column.title === columnName
);
if (existingColumn) {
showNotification({
show: true,
text: "Колонка с таким названием уже существует",
type: "error"
});
} else {
setActive(false);
dispatch(editColumnName({ id: columnId, title: columnName }));
showNotification({
show: true,
text: "Колонка успешно изменена",
type: "success"
});
}
});
}
function createProject() {
if (nameProject !== "") {
apiRequest("/project/create", {
method: "POST",
data: {
user_id: localStorage.getItem("id"),
name: nameProject,
status: 19
}
}).then((res) => {
if (!Array.isArray(res.name)) {
const result = { ...res, columns: [] };
dispatch(setProject(result));
setActive(false);
setNameProject("");
showNotification({
show: true,
text: "Проект успешно создан",
type: "success"
});
} else {
showNotification({
show: true,
text: "Проект с таким именем уже существует",
type: "error"
});
}
});
}
}
function addUserToProject() {
apiRequest("/project/add-user", {
method: "POST",
data: {
user_id: selectedWorker.user_id,
project_id: projectBoard.id
}
}).then((el) => {
dispatch(addPersonToProject(el));
setActive(false);
setSelectedWorker("");
setSelectWorkersOpen(false);
showNotification({
show: true,
text: "Приглашение отправлено",
type: "success"
});
});
}
const validateEmail = (email) => {
// Простая валидация адреса электронной почты
const emailPattern = /^[^\s@]+@[^\s@]+\.[^\s@]+$/;
return emailPattern.test(email);
};
const inviteUserByEmail = () => {
if (validateEmail(emailWorker)) {
setEmailError("");
apiRequest("/project/add-user-by-email", {
method: "POST",
data: {
email: emailWorker,
project_id: projectBoard.id
}
}).then((response) => {
if (response.status === 400) {
showNotification({
show: true,
text: "Участник уже добавлен в проект",
type: "error"
});
} else if (response.status === 404) {
showNotification({
show: true,
text: "Данной почты не существует",
type: "error"
});
} else {
setActive(false);
setEmailWorker("");
dispatch(addPersonToProject(response));
showNotification({
show: true,
text: "Приглашение отправлено",
type: "success"
});
}
});
} else {
setEmailError("Некорректный e-mail адрес");
}
};
useEffect(() => {
modalType === "add-worker"
? apiRequest("/project/my-employee").then((el) => {
let persons = el.managerEmployees;
let ids = projectBoard.projectUsers.map((user) => user.user_id);
setWorkers(
persons.reduce((acc, cur) => {
if (!ids.includes(cur.user_id)) acc.push(cur);
return acc;
}, [])
);
})
: "";
if (
localStorage.getItem("role_status") !== "18" &&
projectUsers &&
Boolean(
!projectUsers.find((item) => item.user_id === profileInfo.id_user)
)
) {
setCorrectProjectUsers([
...projectUsers,
{
user: {
avatar: profileInfo.photo,
fio: profileInfo.fio
},
user_id: profileInfo.id_user
}
]);
} else {
setCorrectProjectUsers(projectUsers);
}
initListeners();
}, [active]);
useEffect(() => {
let tagIds = taskTags.map((tag) => tag.id);
if (projectMarks) {
setCorrectProjectTags(
projectMarks.reduce((acc, cur) => {
if (!tagIds.includes(cur.id)) acc.push(cur);
return acc;
}, [])
);
}
}, [taskTags, projectMarks]);
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("tags__selected__name") ||
div.classList.contains("tags__dropDown"))
)
) {
setSelectTagsOpen(false);
}
if (
event &&
!path.find(
(div) =>
div.classList &&
(div.classList.contains("select__executor") ||
div.classList.contains("select__executor__dropDown"))
)
) {
setSelectExecutorTaskOpen(false);
}
};
const handleModalClose = () => {
setEmailError("");
setEmailWorker("");
};
return (
<ModalLayout
active={active}
setActive={setActive}
onClose={handleModalClose}
type={modalType}
>
{modalType === "add-worker" && (
<>
<div className="select__person">
<div className="title-project select-person">
<h4>Добавьте участника</h4>
<div className="invite__blocks">
<div className="add-person-block">
<p className="select-person__info">
Выберите пользователя в списке
</p>
<div
className={
selectWorkersOpen
? "select__worker open"
: "select__worker"
}
onClick={() => setSelectWorkersOpen(!selectWorkersOpen)}
>
<p>
{selectedWorker
? removeLast(selectedWorker.employee.fio)
: "Выберите пользователя"}
</p>
<img className="arrow" src={arrowDown} alt="arrow" />
{Boolean(selectWorkersOpen) && (
<div className="select__worker__dropDown">
{Boolean(workers.length) ? (
workers.map((worker) => {
if (worker === selectedWorker) {
return;
}
return (
<div
className="worker"
key={worker.id}
onClick={() => {
setSelectedWorker(worker);
}}
>
<span>{removeLast(worker.employee.fio)}</span>
<img
src={urlForLocal(worker.employee.avatar)}
alt="avatar"
/>
</div>
);
})
) : (
<div>Нет пользователей</div>
)}
</div>
)}
</div>
<BaseButton
styles={"button-add add-person-btn"}
onClick={addUserToProject}
>
Добавить
</BaseButton>
</div>
<div className="invite-person-block">
<span>или добавьте по e-mail</span>
<div className="input-container invite-person-block__input">
<input
className="name-project"
placeholder="e-mail"
type="email"
value={emailWorker}
onChange={(e) => setEmailWorker(e.target.value)}
/>
<div className="email-error-message">{emailError}</div>
</div>
<BaseButton
styles={"button-add add-person-btn"}
onClick={inviteUserByEmail}
>
Пригласить
</BaseButton>
</div>
</div>
</div>
</div>
</>
)}
{modalType === "create-ticket-project" && (
<>
<div className="title-project">
<div className="create-task-head">
<div className="create-task-body__right__owner">
<p>Создание задачи</p>
</div>
</div>
<div className="create-task-body">
<div className="create-task-body__left">
<div className="input-container">
<input
maxLength="100"
className="name-project"
value={valueTicket}
onChange={(e) => setValueTicket(e.target.value)}
placeholder="Название задачи"
/>
</div>
<CKEditor
editor={ClassicEditor}
data={descriptionTicket}
config={{
toolbar: [
"heading",
"|",
"bold",
"italic",
"link",
"bulletedList",
"numberedList"
],
removePlugins: ["BlockQuote"],
placeholder: "Описание задачи",
link: {
addTargetToExternalLinks: true
}
}}
onChange={(event, editor) => {
const data = editor.getData();
setDescriptionTicket(data);
}}
/>
</div>
<div className="create-task-body__right">
<div className="create-task-body__right__tags">
<div className="tags__selected">
<div className="tags__selected__items">
{taskTags.map((tag) => {
return (
<div
className="selected-tag"
key={tag.id}
style={{ background: tag.color }}
>
<p>{tag.slug}</p>
<img
src={crossWhite}
className="delete"
alt="delete"
onClick={() =>
setTaskTags((prevState) =>
prevState.filter(
(prevTag) => prevTag.id !== tag.id
)
)
}
/>
</div>
);
})}
</div>
<div
className="tags__selected__name"
onClick={() => setSelectTagsOpen(!selectTagsOpen)}
>
Выберите тег
<img
className={
selectTagsOpen ? "arrow arrow--open" : "arrow"
}
src={arrowDown}
alt="arrow"
/>
</div>
</div>
{selectTagsOpen && (
<div className="tags__dropDown">
<img
src={close}
className="close"
onClick={() => setSelectTagsOpen(false)}
/>
{correctProjectTags.map((tag) => {
return (
<div
className="tag__item"
key={tag.id}
onClick={() =>
setTaskTags((prevState) => [...prevState, tag])
}
>
<p>{tag.slug}</p>
<span style={{ background: tag.color }} />
</div>
);
})}
{Boolean(!correctProjectTags.length) && (
<p className="no-tags">Нет тегов</p>
)}
</div>
)}
</div>
<div className="select__priority">
<div
className="select__priority__name"
onClick={() => setSelectPriority(!selectPriority)}
>
{selectedPriority
? `Приоритет: ${selectedPriority.name}`
: "Выберите приоритет"}
<img
className={selectPriority ? "arrow arrow--open" : "arrow"}
src={arrowDown}
alt="arrow"
/>
</div>
{selectPriority && (
<div className="select__priority__dropDown">
{priority.map((item) => {
return (
<div
className="dropdown__item"
key={item.key}
onClick={() => {
setSelectPriority(false);
setSelectedPriority(item);
}}
>
{item.name}
</div>
);
})}
</div>
)}
</div>
<div
onClick={() =>
setSelectExecutorTaskOpen(!selectExecutorTaskOpen)
}
className={
selectExecutorTaskOpen
? "select__executor select__executor--open"
: "select__executor"
}
>
<div className="selected__executor">
{selectedExecutorTask.user_id ? (
<>
<img
className="avatar"
src={urlForLocal(selectedExecutorTask.user.avatar)}
alt="avatar"
/>
<span>{removeLast(selectedExecutorTask.user.fio)}</span>
</>
) : (
<span>{selectedExecutorTask}</span>
)}
</div>
<img className="arrow" src={arrowDown} alt="arrow" />
{selectExecutorTaskOpen && (
<div className="select__executor__dropDown">
{correctProjectUsers.length ? (
correctProjectUsers.map((person) => {
return (
<div
onClick={() => setSelectedExecutorTask(person)}
className="executor"
key={person.user_id}
>
<img
className="avatar"
src={
person.user?.avatar
? urlForLocal(person.user.avatar)
: avatarMok
}
alt="avatar"
/>
<span>{removeLast(person.user.fio)}</span>
</div>
);
})
) : (
<span>Нет пользователей</span>
)}
</div>
)}
</div>
<div className="create-task-body__right__dead-line">
<p></p>
<p onClick={() => setDatePickerOpen(!datePickerOpen)}>
{deadLineDate
? getCorrectDate(deadLineDate)
: "Срок исполнения"}
</p>
<DatePicker
className="datePicker"
open={datePickerOpen}
locale="ru"
selected={startDate}
onChange={(date) => {
setDatePickerOpen(false);
setStartDate(date);
setDeadLineDate(date);
}}
/>
</div>
</div>
</div>
{loader ? (
<Loader style={"green"} />
) : (
<BaseButton styles={"button-add"} onClick={createTicket}>
Создать задачу
</BaseButton>
)}
</div>
</>
)}
{modalType === "edit-project" && (
<div>
<div className="title-project">
<h4>Введите новое название</h4>
<div className="input-container">
<input
className="name-project"
value={projectName}
onChange={(e) => setProjectName(e.target.value)}
/>
</div>
</div>
<BaseButton styles={"button-add"} onClick={editProject}>
Сохранить
</BaseButton>
</div>
)}
{modalType === "create-project" && (
<div>
<div className="title-project">
<h4>{titleProject}</h4>
<div className="input-container">
<input
maxLength="30"
className="name-project"
value={nameProject}
onChange={(e) => setNameProject(e.target.value)}
/>
</div>
<BaseButton styles={"button-add"} onClick={createProject}>
Создать
</BaseButton>
</div>
</div>
)}
{modalType === "addSubtask" && (
<div>
<div className="title-project subtask">
<h4>
Вы добавляете подзадачу{" "}
<p>в колонку(id) задачи "{defautlInput}"</p>
</h4>
<p className="title-project__decs">Введите текст</p>
<div>
<textarea className="title-project__textarea"></textarea>
</div>
</div>
<BaseButton styles={"button-add"} onClick={(e) => e.preventDefault()}>
Добавить
</BaseButton>
</div>
)}
{modalType === "create-column" && (
<div>
<div className="title-project">
<h4>Введите название колонки</h4>
<div className="input-container">
<input
maxLength="100"
className="name-project"
value={valueColumn}
onChange={(e) => setValueColumn(e.target.value)}
/>
</div>
</div>
<BaseButton styles={"button-add"} onClick={createTab}>
Добавить
</BaseButton>
</div>
)}
{modalType === "edit-column" && (
<div>
<div className="title-project">
<div>
<h4>Название колонки</h4>
<div className="input-container">
<input
className="name-project"
value={columnName}
onChange={(e) => dispatch(setColumnName(e.target.value))}
/>
</div>
</div>
<div>
<h4>Приоритет колонки</h4>
<div
className={
selectColumnPriorityOpen
? "select-priority select-priority--open"
: "select-priority"
}
onClick={() =>
setSelectColumnPriorityOpen(!selectColumnPriorityOpen)
}
>
<span>{selectColumnPriority}</span>
<img src={arrowDown} alt="arrow" />
{selectColumnPriorityOpen && (
<div className="select-priority__dropDown">
{projectBoard.columns.map((column, index) => {
return (
<span
key={column.id}
onClick={() => {
setSelectColumnPriority(index + 1);
dispatch(setColumnPriority(index + 1));
}}
>
{index + 1}
</span>
);
})}
</div>
)}
</div>
</div>
</div>
<BaseButton styles={"button-add"} onClick={changeColumnParams}>
Сохранить
</BaseButton>
</div>
)}
<span
className="exit"
onClick={() => {
setActive(false);
setEmailError("");
setEmailWorker("");
}}
></span>
</ModalLayout>
);
};
export default TrackerModal;