Merge pull request #110 from apuc/tracker-tasks

Tracker tasks
This commit is contained in:
NikoM1k 2023-06-29 03:42:49 +03:00 committed by GitHub
commit 6751a655ee
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23
10 changed files with 520 additions and 133 deletions

View File

@ -1 +1,4 @@
<svg xmlns="http://www.w3.org/2000/svg" xmlns:xlink="http://www.w3.org/1999/xlink" width="16" height="16" viewBox="0 0 16 16"><g><g><image width="16" height="16" xlink:href="data:image/png;base64,iVBORw0KGgoAAAANSUhEUgAAABAAAAAQCAYAAAAf8/9hAAAAfUlEQVQ4jeXSMQrCUBCE4S9tII1HsU7hyXIQD+EZPEDwLIJi9Yg2+1QQMftaB/5iB3bYgeWlTaDBA1OQ9vYouCcpsWvB0HDBgJtI+9btl3euAcdGSg3YNXJ9rwDbYO38rFB1CNbOHwH/fEEnrw4XOGGWf6I5dvUY5Z9oRP8AEMJY2tGN6tUAAAAASUVORK5CYII="/></g></g></svg> <?xml version="1.0" encoding="utf-8"?><!-- Uploaded to: SVG Repo, www.svgrepo.com, Generator: SVG Repo Mixer Tools -->
<svg fill="#000000" width="800px" height="800px" viewBox="0 0 1920 1920" xmlns="http://www.w3.org/2000/svg">
<path d="M1411.824 0c31.17 0 56.47 25.3 56.47 56.471v56.47h169.412c93.403 0 169.412 76.01 169.412 169.412V1920H113V282.353c0-93.402 76.009-169.412 169.412-169.412h169.41v-56.47c0-31.172 25.3-56.47 56.472-56.47s56.47 25.298 56.47 56.47v56.47h790.589v-56.47c0-31.172 25.299-56.47 56.47-56.47Zm282.352 564.705H225.942v1242.353h1468.234V564.705Zm-1016.47 677.648v338.824H338.882v-338.824h338.824Zm451.765 0v338.824H790.647v-338.824h338.824Zm451.764 0v338.824h-338.823v-338.824h338.823Zm-1016.47 112.941H451.824v112.941h112.941v-112.941Zm451.764 0H903.588v112.941h112.941v-112.941Zm451.765 0h-112.941v112.941h112.941v-112.941ZM677.706 790.588v338.824H338.882V790.588h338.824Zm451.765 0v338.824H790.647V790.588h338.824Zm451.764 0v338.824h-338.823V790.588h338.823ZM564.765 903.53H451.824v112.941h112.941V903.53Zm451.764 0H903.588v112.941h112.941V903.53Zm451.765 0h-112.941v112.941h112.941V903.53ZM451.823 225.882H282.412c-31.06 0-56.47 25.3-56.47 56.471v169.412h1468.234V282.353c0-31.172-25.411-56.47-56.47-56.47h-169.412v56.47c0 31.172-25.3 56.471-56.47 56.471-31.172 0-56.471-25.299-56.471-56.47v-56.472H564.765v56.471c0 31.172-25.3 56.471-56.471 56.471-31.171 0-56.471-25.299-56.471-56.47v-56.472Z" fill-rule="evenodd"/>
</svg>

Before

Width:  |  Height:  |  Size: 457 B

After

Width:  |  Height:  |  Size: 1.4 KiB

View File

@ -160,12 +160,17 @@
a { a {
color: black; color: black;
display: flex;
align-items: center;
justify-content: center;
font-weight: 500;
font-size: 12px;
} }
img { img {
width: 16px; width: 16px;
height: 16px; height: 16px;
margin: 0 10px 0 0; margin: 0 5px 0 0;
@media (max-width: 968px) { @media (max-width: 968px) {
margin-right: 2px; margin-right: 2px;

View File

@ -25,6 +25,7 @@ import file from "assets/icons/fileModal.svg";
import link from "assets/icons/link.svg"; import link from "assets/icons/link.svg";
import send from "assets/icons/send.svg"; import send from "assets/icons/send.svg";
import watch from "assets/icons/watch.svg"; import watch from "assets/icons/watch.svg";
import avatarMok from "assets/images/avatarMok.png";
import "./modalTicket.scss"; import "./modalTicket.scss";
@ -69,7 +70,7 @@ export const ModalTiсket = ({
task_id: task.id, task_id: task.id,
status: 0, status: 0,
}, },
}).then((res) => { }).then(() => {
setActive(false); setActive(false);
dispatch(setProjectBoardFetch(projectId)); dispatch(setProjectBoardFetch(projectId));
}); });
@ -334,9 +335,9 @@ export const ModalTiсket = ({
); );
}, [members]); }, [members]);
function copyProjectLink() { function copyTicketLink() {
navigator.clipboard.writeText( navigator.clipboard.writeText(
`https://itguild.info/tracker/project/${projectId}` `https://itguild.info/tracker/task/${task.id}`
); );
} }
@ -469,7 +470,12 @@ export const ModalTiсket = ({
{executor ? ( {executor ? (
<div className="executor"> <div className="executor">
<p>Исполнитель: {executor.fio}</p> <p>Исполнитель: {executor.fio}</p>
<img src={urlForLocal(executor.avatar)} alt="avatar" /> <img
src={
executor?.avatar ? urlForLocal(executor.avatar) : avatarMok
}
alt="avatar"
/>
<img <img
src={close} src={close}
className="delete" className="delete"
@ -500,7 +506,14 @@ export const ModalTiсket = ({
onClick={() => taskExecutor(person)} onClick={() => taskExecutor(person)}
> >
<span>{person.user.fio}</span> <span>{person.user.fio}</span>
<img src={urlForLocal(person.user.avatar)} /> <img
src={
person.user?.avatar
? urlForLocal(person.user.avatar)
: avatarMok
}
alt="avatar"
/>
</div> </div>
); );
})} })}
@ -517,7 +530,14 @@ export const ModalTiсket = ({
return ( return (
<div className="worker" key={member.user_id}> <div className="worker" key={member.user_id}>
<p>{member.fio}</p> <p>{member.fio}</p>
<img src={urlForLocal(member.avatar)} /> <img
src={
member?.avatar
? urlForLocal(member.avatar)
: avatarMok
}
alt="avatar"
/>
<img <img
src={close} src={close}
className="delete" className="delete"
@ -554,7 +574,14 @@ export const ModalTiсket = ({
onClick={() => addMember(person)} onClick={() => addMember(person)}
> >
<span>{person.user.fio}</span> <span>{person.user.fio}</span>
<img src={urlForLocal(person.user.avatar)} /> <img
src={
person.user?.avatar
? urlForLocal(person.user.avatar)
: avatarMok
}
alt="avatar"
/>
</div> </div>
); );
}) })
@ -620,7 +647,7 @@ export const ModalTiсket = ({
</div> </div>
<div> <div>
<img src={link}></img> <img src={link}></img>
<p onClick={copyProjectLink}>ссылка на проект</p> <p onClick={copyTicketLink}>ссылка на задачу</p>
</div> </div>
<div onClick={deleteTask}> <div onClick={deleteTask}>
<img src={archive}></img> <img src={archive}></img>

View File

@ -129,6 +129,10 @@
width: 100%; width: 100%;
position: relative; position: relative;
.ck-editor {
margin: 10px 0;
}
&__subComment { &__subComment {
&:before { &:before {
content: ""; content: "";
@ -251,6 +255,11 @@
} }
} }
} }
.comment__edit--open {
&:after {
display: none;
}
}
} }
} }
@ -508,7 +517,6 @@
&__creator { &__creator {
font-size: 14px; font-size: 14px;
line-height: 32px;
font-weight: 500; font-weight: 500;
color: #2d4a17; color: #2d4a17;
max-width: 200px; max-width: 200px;

View File

@ -1,3 +1,5 @@
import ClassicEditor from "@ckeditor/ckeditor5-build-classic";
import { CKEditor } from "@ckeditor/ckeditor5-react";
import React, { useEffect, useState } from "react"; import React, { useEffect, useState } from "react";
import { useDispatch, useSelector } from "react-redux"; import { useDispatch, useSelector } from "react-redux";
import { Link, useNavigate, useParams } from "react-router-dom"; import { Link, useNavigate, useParams } from "react-router-dom";
@ -5,7 +7,6 @@ import { Link, useNavigate, useParams } from "react-router-dom";
import { import {
deletePersonOnProject, deletePersonOnProject,
getBoarderLoader, getBoarderLoader,
getProjectBoard,
modalToggle, modalToggle,
setProjectBoardFetch, setProjectBoardFetch,
setToggleTab, setToggleTab,
@ -27,17 +28,17 @@ import TrackerTaskComment from "@components/TrackerTaskComment/TrackerTaskCommen
import archive from "assets/icons/archive.svg"; import archive from "assets/icons/archive.svg";
import archive2 from "assets/icons/archive.svg"; import archive2 from "assets/icons/archive.svg";
import arrow from "assets/icons/arrows/arrowCalendar.png"; import arrow from "assets/icons/arrows/arrowCalendar.png";
import arrow2 from "assets/icons/arrows/arrowStart.png"; import arrowStart from "assets/icons/arrows/arrowStart.png";
import close from "assets/icons/close.png"; import close from "assets/icons/close.png";
import del from "assets/icons/delete.svg"; import del from "assets/icons/delete.svg";
import edit from "assets/icons/edit.svg"; import edit from "assets/icons/edit.svg";
import file from "assets/icons/fileModal.svg"; import file from "assets/icons/fileModal.svg";
import link from "assets/icons/link.svg"; import link from "assets/icons/link.svg";
import plus from "assets/icons/plus.svg";
import send from "assets/icons/send.svg"; import send from "assets/icons/send.svg";
import project from "assets/icons/trackerProject.svg"; import project from "assets/icons/trackerProject.svg";
import tasks from "assets/icons/trackerTasks.svg"; import tasks from "assets/icons/trackerTasks.svg";
import watch from "assets/icons/watch.svg"; import watch from "assets/icons/watch.svg";
import avatarMok from "assets/images/avatarMok.png";
import "./ticketFullScreen.scss"; import "./ticketFullScreen.scss";
@ -46,8 +47,8 @@ export const TicketFullScreen = () => {
const ticketId = useParams(); const ticketId = useParams();
const dispatch = useDispatch(); const dispatch = useDispatch();
const navigate = useNavigate(); const navigate = useNavigate();
const projectBoard = useSelector(getProjectBoard);
const boardLoader = useSelector(getBoarderLoader); const boardLoader = useSelector(getBoarderLoader);
const [projectInfo, setProjectInfo] = useState({});
const [taskInfo, setTaskInfo] = useState({}); const [taskInfo, setTaskInfo] = useState({});
const [editOpen, setEditOpen] = useState(false); const [editOpen, setEditOpen] = useState(false);
const [inputsValue, setInputsValue] = useState({}); const [inputsValue, setInputsValue] = useState({});
@ -56,6 +57,16 @@ export const TicketFullScreen = () => {
const [personListOpen, setPersonListOpen] = useState(false); const [personListOpen, setPersonListOpen] = useState(false);
const [timerStart, setTimerStart] = useState(false); const [timerStart, setTimerStart] = useState(false);
const [timerInfo, setTimerInfo] = useState({}); const [timerInfo, setTimerInfo] = useState({});
const [currentTimerCount, setCurrentTimerCount] = useState({
hours: 0,
minute: 0,
seconds: 0,
});
const [timerId, setTimerId] = useState(null);
const [dropListOpen, setDropListOpen] = useState(false);
const [correctProjectUsers, setCorrectProjectUsers] = useState([]);
const [dropListMembersOpen, setDropListMembersOpen] = useState(false);
const [users, setUsers] = useState([]);
useEffect(() => { useEffect(() => {
apiRequest(`/task/get-task?task_id=${ticketId.id}`).then((taskInfo) => { apiRequest(`/task/get-task?task_id=${ticketId.id}`).then((taskInfo) => {
@ -80,13 +91,34 @@ export const TicketFullScreen = () => {
}, []); }, []);
setComments(comments); setComments(comments);
}); });
taskInfo.timers.forEach((time) => { apiRequest(
`/timer/get-by-entity?entity_type=2&entity_id=${taskInfo.id}`
).then((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 (!time.stopped_at) { if (!time.stopped_at) {
setTimerStart(true); setTimerStart(true);
startTimer();
setTimerInfo(time); setTimerInfo(time);
} }
}); });
dispatch(setProjectBoardFetch(taskInfo.project_id)); });
apiRequest(
`/project/get-project?project_id=${taskInfo.project_id}&expand=columns`
).then((res) => {
setProjectInfo(res);
setCorrectProjectUsers(res.projectUsers);
});
setLoader(boardLoader); setLoader(boardLoader);
}); });
}, []); }, []);
@ -142,6 +174,7 @@ export const TicketFullScreen = () => {
}).then((res) => { }).then((res) => {
setTimerStart(true); setTimerStart(true);
setTimerInfo(res); setTimerInfo(res);
startTimer();
}); });
} }
@ -152,14 +185,17 @@ export const TicketFullScreen = () => {
timer_id: timerInfo.id, timer_id: timerInfo.id,
stopped_at: getCorrectRequestDate(new Date()), stopped_at: getCorrectRequestDate(new Date()),
}, },
}).then(() => setTimerStart(false)); }).then(() => {
setTimerStart(false);
clearInterval(timerId);
});
} }
function deletePerson(userId) { function deletePerson(userId) {
apiRequest("/project/del-user", { apiRequest("/project/del-user", {
method: "DELETE", method: "DELETE",
data: { data: {
project_id: projectBoard.id, project_id: projectInfo.id,
user_id: userId, user_id: userId,
}, },
}).then(() => { }).then(() => {
@ -216,6 +252,119 @@ export const TicketFullScreen = () => {
); );
} }
function startTimer() {
setTimerId(
setInterval(() => {
run();
}, 1000)
);
}
useEffect(() => {
if (taskInfo.taskUsers && projectInfo.projectUsers) {
let ids = taskInfo.taskUsers.map((user) => user.user_id);
setUsers(
projectInfo.projectUsers.reduce((acc, cur) => {
if (!ids.includes(cur.user_id)) acc.push(cur);
return acc;
}, [])
);
}
}, [taskInfo.taskUsers, projectInfo]);
let updateTimerSec = currentTimerCount.seconds,
updateTimerMinute = currentTimerCount.minute,
updateTimerHours = currentTimerCount.hours;
function run() {
updateTimerSec++;
if (updateTimerSec > 60) {
updateTimerMinute++;
updateTimerSec = 0;
}
if (updateTimerMinute === 60) {
updateTimerMinute = 0;
updateTimerHours++;
}
return setCurrentTimerCount({
hours: updateTimerHours,
minute: updateTimerMinute,
seconds: updateTimerSec,
});
}
function correctTimerTime(time) {
if (time < 10) return `0${time}`;
if (time > 10) return time;
}
function deleteTaskExecutor() {
apiRequest("/task/update-task", {
method: "PUT",
data: {
task_id: taskInfo.id,
executor_id: 0,
},
}).then(() => {
setTaskInfo((prevState) => ({
...prevState,
executor_id: null,
executor: null,
}));
});
}
function taskExecutor(person) {
apiRequest("/task/update-task", {
method: "PUT",
data: {
task_id: taskInfo.id,
executor_id: person.user_id,
},
}).then((res) => {
setDropListOpen(false);
setTaskInfo((prevState) => ({
...prevState,
executor_id: res.executor_id,
executor: res.executor,
}));
});
}
function deleteMember(person) {
apiRequest("/task/del-user", {
method: "DELETE",
data: {
task_id: taskInfo.id,
user_id: person.user_id,
},
}).then(() => {
setTaskInfo((prevState) => ({
...prevState,
taskUsers: taskInfo.taskUsers.filter(
(item) => item.user_id !== person.user_id
),
}));
});
}
function addMember(person) {
apiRequest("/task/add-user-to-task", {
method: "POST",
data: {
task_id: taskInfo.id,
user_id: person.user_id,
},
}).then((res) => {
setDropListMembersOpen(false);
setTaskInfo((prevValue) => ({
...prevValue,
taskUsers: [...prevValue.taskUsers, res],
}));
});
}
return ( return (
<section className="ticket-full-screen"> <section className="ticket-full-screen">
<ProfileHeader /> <ProfileHeader />
@ -265,7 +414,7 @@ export const TicketFullScreen = () => {
<div className="tracker__tabs__content content-tabs"> <div className="tracker__tabs__content content-tabs">
<div className="tasks__head"> <div className="tasks__head">
<div className="tasks__head__wrapper"> <div className="tasks__head__wrapper">
<h5>Проект : {projectBoard.name}</h5> <h5>Проект : {projectInfo.name}</h5>
<TrackerModal <TrackerModal
active={modalAddWorker} active={modalAddWorker}
@ -273,9 +422,25 @@ export const TicketFullScreen = () => {
></TrackerModal> ></TrackerModal>
<div className="tasks__head__persons"> <div className="tasks__head__persons">
<span className="countPersons"> {projectInfo.projectUsers?.length > 3 && (
{projectBoard.projectUsers?.length} <span className="countPersons">+1...</span>
</span> )}
<div className="projectPersons">
{projectInfo.projectUsers?.length &&
projectInfo.projectUsers.slice(0, 3).map((person) => {
return (
<img
key={person.user_id}
src={
person.user?.avatar
? urlForLocal(person.user.avatar)
: avatarMok
}
alt="avatar"
/>
);
})}
</div>
<span <span
className="addPerson" className="addPerson"
onClick={() => { onClick={() => {
@ -294,14 +459,14 @@ export const TicketFullScreen = () => {
onClick={() => setPersonListOpen(false)} onClick={() => setPersonListOpen(false)}
/> />
<div className="persons__list__count"> <div className="persons__list__count">
<span>{projectBoard.projectUsers?.length}</span> <span>{projectInfo.projectUsers?.length}</span>
участник участник
</div> </div>
<div className="persons__list__info"> <div className="persons__list__info">
В проекте - <span>{projectBoard.name}</span> В проекте - <span>{projectInfo.name}</span>
</div> </div>
<div className="persons__list__items"> <div className="persons__list__items">
{projectBoard.projectUsers?.map((person) => { {projectInfo.projectUsers?.map((person) => {
return ( return (
<div <div
className="persons__list__item" className="persons__list__item"
@ -309,7 +474,11 @@ export const TicketFullScreen = () => {
> >
<img <img
className="avatar" className="avatar"
src={urlForLocal(person.user.avatar)} src={
person.user?.avatar
? urlForLocal(person.user.avatar)
: avatarMok
}
alt="avatar" alt="avatar"
/> />
<span>{person.user.fio}</span> <span>{person.user.fio}</span>
@ -365,32 +534,52 @@ export const TicketFullScreen = () => {
)} )}
<div className="content__description"> <div className="content__description">
{editOpen ? ( {editOpen ? (
<input <CKEditor
value={inputsValue.description} editor={ClassicEditor}
onChange={(e) => { data={inputsValue.description}
config={{
removePlugins: [
"CKFinderUploadAdapter",
"CKFinder",
"EasyImage",
"Image",
"ImageCaption",
"ImageStyle",
"ImageToolbar",
"ImageUpload",
"MediaEmbed",
"BlockQuote",
],
}}
onChange={(event, editor) => {
const data = editor.getData();
setInputsValue((prevValue) => ({ setInputsValue((prevValue) => ({
...prevValue, ...prevValue,
description: e.target.value, description: data,
})); }));
}} }}
/> />
) : ( ) : (
<p>{inputsValue.description}</p> <p
dangerouslySetInnerHTML={{
__html: inputsValue.description,
}}
/>
)} )}
</div> </div>
<div className="content__communication"> <div className="content__communication">
<p className="tasks"> {/*<p className="tasks">*/}
<BaseButton {/* <BaseButton*/}
onClick={() => { {/* onClick={() => {*/}
dispatch(modalToggle("addSubtask")); {/* dispatch(modalToggle("addSubtask"));*/}
setModalAddWorker(true); {/* setModalAddWorker(true);*/}
}} {/* }}*/}
styles={"button-green-add"} {/* styles={"button-green-add"}*/}
> {/* >*/}
<img src={plus}></img> {/* <img src={plus}></img>*/}
Добавить под задачу {/* Добавить под задачу*/}
</BaseButton> {/* </BaseButton>*/}
</p> {/*</p>*/}
<p className="file"> <p className="file">
<BaseButton styles={"button-add-file"}> <BaseButton styles={"button-add-file"}>
<img src={file}></img> <img src={file}></img>
@ -430,58 +619,159 @@ export const TicketFullScreen = () => {
</div> </div>
</div> </div>
<div className="workers"> <div className="workers">
<div className="workers_box"> <div className="workers_box task__info">
<p className="workers__creator"> <p className="workers__creator">
Создатель : <span>{taskInfo.user?.fio}</span> Создатель : <br />
{taskInfo.user?.fio}
</p> </p>
<div>
{Boolean(taskInfo.taskUsers?.length) && {taskInfo.executor ? (
taskInfo.taskUsers.map((worker, index) => { <div className="executor">
<p>Исполнитель: {taskInfo.executor.fio}</p>
<img
src={
taskInfo.executor?.avatar
? urlForLocal(taskInfo.executor.avatar)
: avatarMok
}
alt="avatar"
/>
<img
src={close}
className="delete"
onClick={() => deleteTaskExecutor()}
/>
</div>
) : (
<div className="add-worker moreItems ">
<button
className="button-add-worker"
onClick={() => setDropListOpen(true)}
>
+
</button>
<span>Добавить исполнителя</span>
{dropListOpen && (
<div className="dropdownList">
<img
src={close}
className="dropdownList__close"
onClick={() => setDropListOpen(false)}
/>
{correctProjectUsers.map((person) => {
return ( return (
<div className="worker" key={index}> <div
<img src={worker.avatar} alt="worket"></img> className="dropdownList__person"
<p>{worker.name}</p> key={person.user_id}
onClick={() => taskExecutor(person)}
>
<span>{person.user.fio}</span>
<img
src={
person.user?.avatar
? urlForLocal(person.user.avatar)
: avatarMok
}
alt="avatar"
/>
</div> </div>
); );
})} })}
</div> </div>
)}
<div className="add-worker moreItems">
<BaseButton
onClick={() => {
dispatch(modalToggle("addWorker"));
setModalAddWorker(true);
}}
styles={"button-add-worker"}
>
+
</BaseButton>
<span>Добавить исполнителя</span>
</div> </div>
)}
{Boolean(taskInfo.taskUsers.length) && (
<div className="members">
<p>Участники:</p>
<div className="members__list">
{taskInfo.taskUsers.map((member) => {
return (
<div className="worker" key={member.user_id}>
<p>{member.fio}</p>
<img
src={
member?.avatar
? urlForLocal(member.avatar)
: avatarMok
}
alt="avatar"
/>
<img
src={close}
className="delete"
onClick={() => deleteMember(member)}
/>
</div>
);
})}
</div>
</div>
)}
<div className="add-worker moreItems"> <div className="add-worker moreItems">
<BaseButton <button
onClick={() => { className="button-add-worker"
dispatch(modalToggle("addWorker")); onClick={() => setDropListMembersOpen(true)}
setModalAddWorker(true);
}}
styles={"button-add-worker"}
> >
+ +
</BaseButton> </button>
<span>Добавить участников</span> <span>Добавить участников</span>
{dropListMembersOpen && (
<div className="dropdownList">
<img
src={close}
className="dropdownList__close"
onClick={() => setDropListMembersOpen(false)}
/>
{users.length ? (
users.map((person) => {
return (
<div
className="dropdownList__person"
key={person.user_id}
onClick={() => addMember(person)}
>
<span>{person.user.fio}</span>
<img
src={
person.user?.avatar
? urlForLocal(person.user.avatar)
: avatarMok
}
alt="avatar"
/>
</div>
);
})
) : (
<p className="noUsers">Нет пользователей</p>
)}
</div>
)}
</div> </div>
</div> </div>
<div className="workers_box-middle"> <div className="workers_box-middle">
<div className="time"> <div className="time">
<img src={watch} alt="watch"></img> <img src={watch}></img>
<span>Длительность : </span> <span>Длительность : </span>
<p>{"0:00:00"}</p> <p>
{correctTimerTime(currentTimerCount.hours)}:
{correctTimerTime(currentTimerCount.minute)}:
{correctTimerTime(currentTimerCount.seconds)}
</p>
</div> </div>
{timerStart ? ( {timerStart ? (
<button className="stop" onClick={() => stopTaskTimer()}> <button
className={
taskInfo.executor_id ===
Number(localStorage.getItem("id"))
? "stop"
: "stop disable"
}
onClick={() => stopTaskTimer()}
>
Остановить Остановить
</button> </button>
) : ( ) : (
@ -494,7 +784,8 @@ export const TicketFullScreen = () => {
} }
onClick={() => startTaskTimer()} onClick={() => startTaskTimer()}
> >
Начать делать <img src={arrow2} alt="arrow"></img> Начать делать
<img src={arrowStart}></img>
</button> </button>
)} )}
</div> </div>

View File

@ -1,3 +1,5 @@
import ClassicEditor from "@ckeditor/ckeditor5-build-classic";
import { CKEditor } from "@ckeditor/ckeditor5-react";
import React, { useEffect, useState } from "react"; import React, { useEffect, useState } from "react";
import { useDispatch, useSelector } from "react-redux"; import { useDispatch, useSelector } from "react-redux";
@ -25,6 +27,7 @@ import BaseButton from "@components/Common/BaseButton/BaseButton";
import ModalLayout from "@components/Common/ModalLayout/ModalLayout"; import ModalLayout from "@components/Common/ModalLayout/ModalLayout";
import arrowDown from "assets/icons/arrows/selectArrow.png"; import arrowDown from "assets/icons/arrows/selectArrow.png";
import avatarMok from "assets/images/avatarMok.png";
import "./trackerModal.scss"; import "./trackerModal.scss";
@ -50,7 +53,7 @@ export const TrackerModal = ({
const [valueColumn, setValueColumn] = useState(""); const [valueColumn, setValueColumn] = useState("");
const [nameProject, setNameProject] = useState(""); const [nameProject, setNameProject] = useState("");
const [valueTiket, setValueTiket] = useState(""); const [valueTiket, setValueTiket] = useState("");
const [descriptionTicket, setDescriptionTicket] = useState(""); const [descriptionTicket, setDescriptionTicket] = useState("Описание задачи");
const [workers, setWorkers] = useState([]); const [workers, setWorkers] = useState([]);
const [selectWorkersOpen, setSelectWorkersOpen] = useState(false); const [selectWorkersOpen, setSelectWorkersOpen] = useState(false);
const [selectedWorker, setSelectedWorker] = useState(null); const [selectedWorker, setSelectedWorker] = useState(null);
@ -114,13 +117,13 @@ export const TrackerModal = ({
dispatch(setProjectBoardFetch(projectBoard.id)); dispatch(setProjectBoardFetch(projectBoard.id));
setActive(false); setActive(false);
setValueTiket(""); setValueTiket("");
setDescriptionTicket(""); setDescriptionTicket("Описание задачи");
setSelectedExecutorTask("Выберите исполнителя задачи"); setSelectedExecutorTask("Выберите исполнителя задачи");
}); });
} else { } else {
setActive(false); setActive(false);
setValueTiket(""); setValueTiket("");
setDescriptionTicket(""); setDescriptionTicket("Описание задачи");
dispatch(setProjectBoardFetch(projectBoard.id)); dispatch(setProjectBoardFetch(projectBoard.id));
} }
}); });
@ -337,14 +340,27 @@ export const TrackerModal = ({
placeholder="Название задачи" placeholder="Название задачи"
/> />
</div> </div>
<div className="input-container"> <CKEditor
<input editor={ClassicEditor}
className="name-project" data={descriptionTicket}
value={descriptionTicket} config={{
onChange={(e) => setDescriptionTicket(e.target.value)} toolbar: [
placeholder="Описание задачи" "heading",
"|",
"bold",
"italic",
"link",
"bulletedList",
"numberedList",
"blockQuote",
],
removePlugins: ["BlockQuote"],
}}
onChange={(event, editor) => {
const data = editor.getData();
setDescriptionTicket(data);
}}
/> />
</div>
<div <div
onClick={() => setSelectExecutorTaskOpen(!selectExecutorTaskOpen)} onClick={() => setSelectExecutorTaskOpen(!selectExecutorTaskOpen)}
className={ className={
@ -381,7 +397,11 @@ export const TrackerModal = ({
<span>{person.user.fio}</span> <span>{person.user.fio}</span>
<img <img
className="avatar" className="avatar"
src={urlForLocal(person.user.avatar)} src={
person.user?.avatar
? urlForLocal(person.user.avatar)
: avatarMok
}
alt="avatar" alt="avatar"
/> />
</div> </div>

View File

@ -31,6 +31,7 @@
align-items: center; align-items: center;
flex-direction: column; flex-direction: column;
margin: 0 0 15px 0; margin: 0 0 15px 0;
row-gap: 5px;
.select-priority { .select-priority {
background-color: white; background-color: white;
@ -78,7 +79,7 @@
} }
.input-container { .input-container {
width: 287px; width: 320px;
height: 35px; height: 35px;
background: #ffffff; background: #ffffff;
border-radius: 8px; border-radius: 8px;
@ -89,8 +90,12 @@
} }
} }
.ck-editor {
max-width: 320px;
}
.select__executor { .select__executor {
width: 287px; width: 320px;
background: white; background: white;
border-radius: 8px; border-radius: 8px;
margin: 5px 0; margin: 5px 0;

View File

@ -1,3 +1,5 @@
import ClassicEditor from "@ckeditor/ckeditor5-build-classic";
import { CKEditor } from "@ckeditor/ckeditor5-react";
import React, { useState } from "react"; import React, { useState } from "react";
import { urlForLocal } from "@utils/helper"; import { urlForLocal } from "@utils/helper";
@ -76,6 +78,7 @@ export const TrackerTaskComment = ({
? "comments__list__item__main" ? "comments__list__item__main"
: "", : "",
"comments__list__item", "comments__list__item",
commentsEditOpen ? "comment__edit--open" : "",
comment.parent_id ? "comments__list__item__subComment" : "", comment.parent_id ? "comments__list__item__subComment" : "",
].join(" ")} ].join(" ")}
> >
@ -106,17 +109,35 @@ export const TrackerTaskComment = ({
</div> </div>
</div> </div>
{commentsEditOpen ? ( {commentsEditOpen ? (
<input <CKEditor
className="comments__list__item__text" editor={ClassicEditor}
value={commentsEditText} data={commentsEditText}
onChange={(e) => { config={{
setCommentsEditText(e.target.value); removePlugins: [
"CKFinderUploadAdapter",
"CKFinder",
"EasyImage",
"Image",
"ImageCaption",
"ImageStyle",
"ImageToolbar",
"ImageUpload",
"MediaEmbed",
"BlockQuote",
],
}}
onChange={(event, editor) => {
const data = editor.getData();
setCommentsEditText(data);
}} }}
/> />
) : ( ) : (
<p className="comments__list__item__text">{commentsEditText}</p> <p
dangerouslySetInnerHTML={{ __html: commentsEditText }}
className="comments__list__item__text"
/>
)} )}
{!comment.parent_id && ( {!comment.parent_id && !commentsEditOpen && (
<> <>
{subCommentsCreateOpen ? ( {subCommentsCreateOpen ? (
<div className="comments__list__item__answer__new"> <div className="comments__list__item__answer__new">

View File

@ -322,17 +322,12 @@ export const ProjectTracker = () => {
<p>добавить колонку</p> <p>добавить колонку</p>
</div> </div>
<div className="tasks__head__persons"> <div className="tasks__head__persons">
<span className="countPersons"> {projectBoard.projectUsers?.length > 3 && (
{projectBoard.projectUsers?.length} <span className="countPersons">+1...</span>
</span> )}
<div className="projectPersons"> <div className="projectPersons">
{projectBoard.projectUsers?.length && {projectBoard.projectUsers?.length &&
projectBoard.projectUsers projectBoard.projectUsers.slice(0, 3).map((person) => {
.slice(
0,
projectBoard.length > 3 ? 3 : projectBoard.length
)
.map((person) => {
return ( return (
<img <img
key={person.user_id} key={person.user_id}
@ -379,7 +374,11 @@ export const ProjectTracker = () => {
> >
<img <img
className="avatar" className="avatar"
src={urlForLocal(person.user.avatar)} src={
person.user?.avatar
? urlForLocal(person.user.avatar)
: avatarMok
}
alt="avatar" alt="avatar"
/> />
<span>{person.user.fio}</span> <span>{person.user.fio}</span>
@ -462,7 +461,13 @@ export const ProjectTracker = () => {
<span <span
className="add" className="add"
onClick={() => onClick={() =>
selectedTabTask(column.id, column.tasks.length) selectedTabTask(
column.id,
projectBoard?.columns
? projectBoard?.columns[0].tasks.at(-1)
.priority + 1
: 1
)
} }
> >
+ +

View File

@ -295,7 +295,7 @@
color: #252c32; color: #252c32;
border: 1px solid #dde2e4; border: 1px solid #dde2e4;
background: white; background: white;
left: -5px; left: -6px;
} }
.addPerson { .addPerson {
@ -329,9 +329,11 @@
&__close { &__close {
cursor: pointer; cursor: pointer;
position: absolute; position: absolute;
right: 20px; right: 25px;
top: 15px; top: 25px;
margin-left: auto; margin-left: auto;
width: 35px;
height: 35px;
} }
&__count { &__count {
@ -356,7 +358,7 @@
line-height: 22px; line-height: 22px;
color: #263238; color: #263238;
font-weight: 500; font-weight: 500;
margin: 13px 0 32px; margin: 13px 0 10px;
span { span {
width: auto; width: auto;