fix ModalTicket logic

This commit is contained in:
Никита Губарь 2024-07-25 18:17:39 +03:00
parent 7e64150378
commit ca0a509077
4 changed files with 364 additions and 285 deletions

View File

@ -122,15 +122,6 @@ export const ModalTiсket = ({
setShowModalToReport(!showModalToReport); setShowModalToReport(!showModalToReport);
}; };
const closeModal = () => {
setActive(false);
const currentUrl = window.location.pathname;
const newUrl = currentUrl.replace(/\/task\/\d+$/, "");
window.history.replaceState({}, "", newUrl);
document.body.style.overflow = "auto";
console.log(task);
};
const [isExpanded, setIsExpanded] = useState(false); const [isExpanded, setIsExpanded] = useState(false);
const toggleModalSize = () => { const toggleModalSize = () => {
@ -310,6 +301,17 @@ export const ModalTiсket = ({
}); });
} }
const closeModal = () => {
if (timerStart) {
stopTaskTimer();
}
setActive(false);
const currentUrl = window.location.pathname;
const newUrl = currentUrl.replace(/\/task\/\d+$/, "");
window.history.replaceState({}, "", newUrl);
document.body.style.overflow = "auto";
};
function taskExecutor(person) { function taskExecutor(person) {
apiRequest("/task/update-task", { apiRequest("/task/update-task", {
method: "PUT", method: "PUT",
@ -380,82 +382,111 @@ export const ModalTiсket = ({
} }
useEffect(() => { useEffect(() => {
initListeners(); if (active) {
apiRequest( setStartDate(task.dead_line ? new Date(task.dead_line) : new Date());
`/comment/get-by-entity?entity_type=2&entity_id=${task.id}` setTaskPriority(task.execution_priority);
).then((res) => { setMembers(task.taskUsers);
const comments = res.reduce((acc, cur) => { setTaskTags(task.mark);
if (!cur.parent_id) { setExecutorId(task.executor_id);
acc.push({ ...cur, subComments: [] }); setDeadLine(task.dead_line);
} else { setExecutor(task.executor);
acc.forEach((item) => { setInputsValue({
if (item.id === cur.parent_id) item.subComments.push(cur); title: task.title,
}); description: task.description,
} comment: ""
return acc; });
}, []);
setComments(comments); initListeners();
});
apiRequest(`/timer/get-by-entity?entity_type=2&entity_id=${task.id}`).then( apiRequest(
(res) => { `/comment/get-by-entity?entity_type=2&entity_id=${task.id}`
let timerSeconds = 0; ).then((res) => {
res.length && const comments = res.reduce((acc, cur) => {
res.forEach((time) => { if (!cur.parent_id) {
timerSeconds += time.deltaSeconds; acc.push({ ...cur, subComments: [] });
setCurrentTimerCount({ } else {
hours: Math.floor(timerSeconds / 60 / 60), acc.forEach((item) => {
minute: Math.floor((timerSeconds / 60) % 60), if (item.id === cur.parent_id) item.subComments.push(cur);
seconds: timerSeconds % 60
}); });
updateTimerHours = Math.floor(timerSeconds / 60 / 60); }
updateTimerMinute = Math.floor((timerSeconds / 60) % 60); return acc;
updateTimerSec = timerSeconds % 60; }, []);
if (!time.stopped_at) { setComments(comments);
setTimerStart(true); });
startTimer();
setTimerInfo(time);
}
});
}
);
apiRequest(`/file/get-by-entity?entity_type=2&entity_id=${task.id}`).then( apiRequest(
(res) => { `/timer/get-by-entity?entity_type=2&entity_id=${task.id}`
).then((res) => {
if (Array.isArray(res)) { if (Array.isArray(res)) {
setTaskFiles(res); let timerSeconds = 0;
} res.length &&
} res.forEach((time) => {
); timerSeconds += time.deltaSeconds;
setCurrentTimerCount({
hours: Math.floor(timerSeconds / 60 / 60),
minute: Math.floor((timerSeconds / 60) % 60),
seconds: timerSeconds % 60
});
updateTimerHours = Math.floor(timerSeconds / 60 / 60);
updateTimerMinute = Math.floor((timerSeconds / 60) % 60);
updateTimerSec = timerSeconds % 60;
if ( if (!time.stopped_at) {
localStorage.getItem("role_status") !== "18" && setTimerStart(true);
Boolean( startTimer();
setTimerInfo(time);
}
});
} else {
setCurrentTimerCount({
hours: 0,
minute: 0,
seconds: 0
});
}
});
apiRequest(`/file/get-by-entity?entity_type=2&entity_id=${task.id}`).then(
(res) => {
if (Array.isArray(res)) {
setTaskFiles(res);
} else {
setTaskFiles([]);
}
}
);
if (
localStorage.getItem("role_status") !== "18" &&
Array.isArray(correctProjectUsers) &&
!correctProjectUsers.find( !correctProjectUsers.find(
(item) => item.user_id === profileInfo.id_user (item) => item.user_id === profileInfo.id_user
) )
) ) {
) { setCorrectProjectUsers((prevState) => [
setCorrectProjectUsers((prevState) => [ ...prevState,
...prevState, {
{ user: {
user: { avatar: profileInfo.photo,
avatar: profileInfo.photo, fio: profileInfo.fio
fio: profileInfo.fio },
}, user_id: profileInfo.id_user
user_id: profileInfo.id_user }
} ]);
]); }
} }
}, []); }, [active]);
useEffect(() => { useEffect(() => {
let tagIds = taskTags.map((tag) => tag.id); if (Array.isArray(taskTags)) {
setCorrectProjectTags( const tagIds = taskTags.map((tag) => tag.id);
projectMarks.reduce((acc, cur) => { setCorrectProjectTags(
if (!tagIds.includes(cur.id)) acc.push(cur); projectMarks.reduce((acc, cur) => {
return acc; if (!tagIds.includes(cur.id)) acc.push(cur);
}, []) return acc;
); }, [])
);
}
}, [taskTags]); }, [taskTags]);
async function handleUpload(event) { async function handleUpload(event) {
@ -534,13 +565,15 @@ export const ModalTiсket = ({
} }
useEffect(() => { useEffect(() => {
let ids = members.map((user) => user.user_id); if (Array.isArray(members)) {
setUsers( const ids = members.map((user) => user.user_id);
projectUsers.reduce((acc, cur) => { setUsers(
if (!ids.includes(cur.user_id)) acc.push(cur); projectUsers.reduce((acc, cur) => {
return acc; if (!ids.includes(cur.user_id)) acc.push(cur);
}, []) return acc;
); }, [])
);
}
}, [members]); }, [members]);
function copyTicketLink() { function copyTicketLink() {
@ -717,7 +750,7 @@ export const ModalTiсket = ({
)} )}
{/*<img src={taskImg} className="image-task"></img>*/} {/*<img src={taskImg} className="image-task"></img>*/}
</div> </div>
{Boolean(taskFiles.length) && ( {Boolean(taskFiles?.length) && (
<div className="task__files"> <div className="task__files">
{taskFiles.map((file) => { {taskFiles.map((file) => {
return ( return (
@ -877,7 +910,7 @@ export const ModalTiсket = ({
)} )}
</div> </div>
)} )}
{Boolean(members.length) && ( {Boolean(members?.length) && (
<div className="members"> <div className="members">
<h5>Участники:</h5> <h5>Участники:</h5>
<div className="members__list"> <div className="members__list">
@ -1009,23 +1042,24 @@ export const ModalTiсket = ({
<div className="workers_box-tag"> <div className="workers_box-tag">
<div className="tags"> <div className="tags">
<div className="tags__selected"> <div className="tags__selected">
{taskTags.map((tag) => { {Array.isArray(taskTags) &&
return ( taskTags.map((tag) => {
<div return (
className="tags__selected__item" <div
key={tag.id} className="tags__selected__item"
style={{ background: tag.color }} key={tag.id}
> style={{ background: tag.color }}
<p>{tag.slug}</p> >
<img <p>{tag.slug}</p>
src={crossWhite} <img
className="delete" src={crossWhite}
alt="delete" className="delete"
onClick={() => deleteTagFromTask(tag.id)} alt="delete"
/> onClick={() => deleteTagFromTask(tag.id)}
</div> />
); </div>
})} );
})}
</div> </div>
<div <div
className="tags__select" className="tags__select"

View File

@ -1,4 +1,4 @@
import React, { useEffect, useState } from "react"; import React, { forwardRef, useEffect, useState } from "react";
import { useDispatch } from "react-redux"; import { useDispatch } from "react-redux";
import { movePositionProjectTask } from "@redux/projectsTrackerSlice"; import { movePositionProjectTask } from "@redux/projectsTrackerSlice";
@ -14,173 +14,181 @@ import avatarMok from "assets/images/avatarMok.webp";
import "./trackerCardTask.scss"; import "./trackerCardTask.scss";
const TrackerCardTask = ({ const TrackerCardTask = forwardRef(
task, (
projectBoard, {
titleColor, task,
column, projectBoard,
openTicket, titleColor,
startWrapperIndexTest, column,
setWrapperHover openTicket,
}) => { startWrapperIndexTest,
const dispatch = useDispatch(); setWrapperHover
const [taskHover, setTaskHover] = useState({}); },
ref
) => {
const dispatch = useDispatch();
const [taskHover, setTaskHover] = useState({});
const priority = { const priority = {
2: "Высокий", 2: "Высокий",
1: "Средний", 1: "Средний",
0: "Низкий" 0: "Низкий"
}; };
const priorityClass = { const priorityClass = {
2: "high", 2: "high",
1: "middle", 1: "middle",
0: "low" 0: "low"
}; };
function dragDropTaskHandler(e, task, column) { function dragDropTaskHandler(e, task, column) {
e.preventDefault(); e.preventDefault();
if (task.id === startWrapperIndexTest.current.task.id) { if (task.id === startWrapperIndexTest.current.task.id) {
return; return;
}
const finishTask = column.tasks.indexOf(task);
dispatch(
movePositionProjectTask({
startTask: startWrapperIndexTest.current.task,
finishTask: task,
finishIndex: finishTask
})
);
} }
const finishTask = column.tasks.indexOf(task); function dragOverTaskHandler(e, task) {
e.preventDefault();
if (startWrapperIndexTest.current.task.id === task.id) {
return;
}
setTaskHover((prevState) => ({ [prevState]: false, [task.id]: true }));
}
dispatch( function dragStartHandler(e, task, columnId) {
movePositionProjectTask({ startWrapperIndexTest.current = { task: task, index: columnId };
startTask: startWrapperIndexTest.current.task, }
finishTask: task,
finishIndex: finishTask 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
ref={ref}
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={() => openTicket(task)}
>
<div className="tasks__board__item__title">
<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>
); );
} }
);
function dragOverTaskHandler(e, task) { TrackerCardTask.displayName = "TrackerCardTask";
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">
<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; export default TrackerCardTask;

View File

@ -1,7 +1,7 @@
import moment from "moment"; import moment from "moment";
import React, { useEffect, useRef, useState } from "react"; import React, { useEffect, useRef, useState } from "react";
import { useDispatch, useSelector } from "react-redux"; import { useDispatch, useSelector } from "react-redux";
import { Link, useParams } from "react-router-dom"; import { Link, useLocation, useParams } from "react-router-dom";
import { import {
activeLoader, activeLoader,
@ -51,6 +51,8 @@ import avatarMok from "assets/images/avatarMok.webp";
export const ProjectTracker = () => { export const ProjectTracker = () => {
const dispatch = useDispatch(); const dispatch = useDispatch();
const projectId = useParams(); const projectId = useParams();
const taskParams = useParams();
const taskRefs = useRef([]);
const [openColumnSelect, setOpenColumnSelect] = useState({}); const [openColumnSelect, setOpenColumnSelect] = useState({});
const [selectedTab, setSelectedTab] = useState(0); const [selectedTab, setSelectedTab] = useState(0);
@ -78,6 +80,29 @@ export const ProjectTracker = () => {
initListeners(); initListeners();
}, []); }, []);
useEffect(() => {
// const observer = new MutationObserver(() => {
// if (taskParams.taskId && taskRefs.current[taskParams.taskId]) {
// taskRefs.current[taskParams.taskId].click();
// console.log(taskRefs.current[taskParams.taskId]);
// }
// });
// observer.observe(document.body, { childList: true, subtree: true });
// return () => observer.disconnect();
if (projectBoard.columns && taskParams.taskId) {
for (const column of projectBoard.columns) {
const task = column.tasks.find((task) => task.id == taskParams.taskId);
if (task) {
openTicket(task);
return;
}
}
console.log(projectBoard);
}
}, [projectBoard]);
useEffect(() => { useEffect(() => {
let columnsTasksEmpty = true; let columnsTasksEmpty = true;
if (Object.keys(projectBoard).length) { if (Object.keys(projectBoard).length) {
@ -145,21 +170,32 @@ export const ProjectTracker = () => {
setPriorityTask(length); setPriorityTask(length);
} }
function openTicket(e, task) { const updateUrlWithTaskId = (taskId) => {
const currentUrl = window.location.pathname;
const taskUrlSegment = `/task/`;
if (currentUrl.includes(taskUrlSegment)) {
// Если URL содержит '/task/', заменяем старый ID на новый
const baseUrl = currentUrl.substring(
0,
currentUrl.indexOf(taskUrlSegment) + taskUrlSegment.length
);
const newUrl = `${baseUrl}${taskId}`;
window.history.pushState({}, "", newUrl);
} else {
// Если URL не содержит '/task/', добавляем '/task/${taskId}'
const newUrl = `${currentUrl}${taskUrlSegment}${taskId}`;
window.history.pushState({}, "", newUrl);
}
};
function openTicket(task) {
setSelectedTicket(task); setSelectedTicket(task);
setModalActiveTicket(true); setModalActiveTicket(true);
const currentUrl = window.location.pathname; updateUrlWithTaskId(task.id);
const newUrl = `${currentUrl}/task/${task.id}`;
window.history.pushState({}, "", newUrl);
document.body.style.overflow = "hidden"; document.body.style.overflow = "hidden";
} }
useEffect(() => {
const currentUrl = window.location.pathname;
const newUrl = currentUrl.replace(/\/task\/\d+$/, "");
window.history.replaceState({}, "", newUrl);
}, []);
function deleteColumn(column) { function deleteColumn(column) {
const priorityColumns = []; const priorityColumns = [];
apiRequest("/project-column/update-column", { apiRequest("/project-column/update-column", {
@ -421,19 +457,16 @@ export const ProjectTracker = () => {
</Link> </Link>
</div> </div>
</div> </div>
<ModalTicket
{Boolean(modalActiveTicket) && ( active={modalActiveTicket}
<ModalTicket setActive={setModalActiveTicket}
active={modalActiveTicket} task={selectedTicket}
setActive={setModalActiveTicket} projectId={projectBoard.id}
task={selectedTicket} projectName={projectBoard.name}
projectId={projectBoard.id} projectUsers={projectBoard.projectUsers}
projectName={projectBoard.name} projectOwnerId={projectBoard.owner_id}
projectUsers={projectBoard.projectUsers} projectMarks={projectBoard.mark}
projectOwnerId={projectBoard.owner_id} />
projectMarks={projectBoard.mark}
/>
)}
<div className="tasks__container"> <div className="tasks__container">
{Boolean(projectBoard?.columns) && {Boolean(projectBoard?.columns) &&
@ -535,6 +568,9 @@ export const ProjectTracker = () => {
startWrapperIndexTest={startWrapperIndexTest} startWrapperIndexTest={startWrapperIndexTest}
task={task} task={task}
titleColor={titleColor} titleColor={titleColor}
ref={(el) => {
taskRefs.current[task.id] = el;
}}
/> />
); );
})} })}

View File

@ -24,6 +24,7 @@ export const DeveloperPage = () => {
return ( return (
<Routes> <Routes>
<Route exact path="/tracker/task/:id" element={<TicketFullScreen />} /> <Route exact path="/tracker/task/:id" element={<TicketFullScreen />} />
<Route <Route
exact exact
path="/tracker/project/:id/task/:taskId" path="/tracker/project/:id/task/:taskId"