diff --git a/src/components/UI/ModalTicket/ModalTicket.jsx b/src/components/UI/ModalTicket/ModalTicket.jsx index 290ff0bd..1aa706f1 100644 --- a/src/components/UI/ModalTicket/ModalTicket.jsx +++ b/src/components/UI/ModalTicket/ModalTicket.jsx @@ -8,6 +8,8 @@ import { setProjectBoardFetch, } from "../../../redux/projectsTrackerSlice"; +import {getCorrectDate} from '../../../components/Calendar/calendarHelper' + import category from "../../../images/category.png"; import watch from "../../../images/watch.png"; import file from "../../../images/fileModal.svg"; @@ -33,7 +35,10 @@ export const ModalTiсket = ({ const dispatch = useDispatch(); const [addSubtask, setAddSubtask] = useState(false); const [editOpen, setEditOpen] = useState(false); - const [inputsValue, setInputsValue] = useState({title: task.title, description: task.description}) + const [inputsValue, setInputsValue] = useState({title: task.title, description: task.description, comment: ''}); + const [comments, setComments] = useState([]); + const [commentsEditOpen, setCommentsEditOpen] = useState({}) + const [commentsEditText, setCommentsEditText] = useState({}) function deleteTask() { apiRequest("/task/update-task", { @@ -61,6 +66,57 @@ export const ModalTiсket = ({ }); } + function createComment() { + apiRequest("/comment/create", { + method: "POST", + data: { + text: inputsValue.comment, + entity_type: 2, + entity_id: task.id + } + }).then((res) => { + let newComment = res + newComment.created_at = new Date() + setInputsValue((prevValue) => ({...prevValue, comment: ''})) + setComments((prevValue) => ([...prevValue, newComment])) + setCommentsEditOpen((prevValue) => ({...prevValue, [res.id]: false})) + setCommentsEditText((prevValue) => ({...prevValue, [res.id]: res.text})) + }) + } + function deleteComment(commentId) { + apiRequest("/comment/update", { + method: "PUT", + data: { + comment_id: commentId, + status: 0 + } + }).then((res) => { + setComments((prevValue) => prevValue.filter((item) => item.id !== commentId)) + }) + } + + function editComment(commentId) { + + apiRequest("/comment/update", { + method: "PUT", + data: { + comment_id: commentId, + text: commentsEditText[commentId] + } + }).then((res) => { + }) + } + + useEffect(() => { + apiRequest(`/comment/get-by-entity?entity_type=2&entity_id=${task.id}`).then((res) => { + setComments(res) + res.forEach((item) => { + setCommentsEditOpen((prevValue) => ({...prevValue, [item.id]: false})) + setCommentsEditText((prevValue) => ({...prevValue, [item.id]: item.text})) + }) + }) + }, []) + return (
{ setInputsValue((prevValue) => ({...prevValue, description: e.target.value})) }}/> :

{inputsValue.description}

} - + {/**/}

@@ -115,8 +171,33 @@ export const ModalTiсket = ({

- - + { + setInputsValue((prevValue) => ({...prevValue, comment: e.target.value})) + }} /> + +
+
+ {comments.map((comment) => { + return
+
+ {getCorrectDate(comment.created_at)} +
+ edit { + if (commentsEditOpen[comment.id]) { + editComment(comment.id) + } + setCommentsEditOpen((prevValue) => ({...prevValue, [comment.id]: !prevValue[comment.id]})) + }} /> +
+ delete deleteComment(comment.id)} /> +
+ {commentsEditOpen[comment.id] ? { + setCommentsEditText((prevValue) => ({...prevValue, [comment.id]: e.target.value})) + }} /> :

{commentsEditText[comment.id]}

} +
+ }) + + }
diff --git a/src/components/UI/ModalTicket/ModalTicket.scss b/src/components/UI/ModalTicket/ModalTicket.scss index fdd19c48..353a98ab 100644 --- a/src/components/UI/ModalTicket/ModalTicket.scss +++ b/src/components/UI/ModalTicket/ModalTicket.scss @@ -75,6 +75,46 @@ color: #1a1919; margin-bottom: 0; } + + .comments__list { + display: flex; + flex-direction: column; + row-gap: 10px; + &__item { + padding: 10px 20px; + display: flex; + flex-direction: column; + max-width: 438px; + background: #f1f1f1; + border-radius: 44px; + font-size: 14px; + width: 100%; + row-gap: 10px; + + &__info { + display: flex; + justify-content: center; + column-gap: 15px; + + .edit { + width: 25px; + display: flex; + align-items: center; + justify-content: center; + border-radius: 5px; + } + + .edit__open { + background: green; + } + + img { + cursor: pointer; + width: 15px; + } + } + } + } } &__description { diff --git a/src/components/UI/TicketFullScreen/TicketFullScreen.jsx b/src/components/UI/TicketFullScreen/TicketFullScreen.jsx index 46f1c769..dce1ebd8 100644 --- a/src/components/UI/TicketFullScreen/TicketFullScreen.jsx +++ b/src/components/UI/TicketFullScreen/TicketFullScreen.jsx @@ -8,8 +8,15 @@ import TrackerModal from "../TrackerModal/TrackerModal"; import { Navigation } from "../../Navigation/Navigation"; import {Loader} from "../../Loader/Loader"; -import { useDispatch } from "react-redux"; -import {modalToggle, setToggleTab} from "../../../redux/projectsTrackerSlice"; +import {useDispatch, useSelector} from "react-redux"; +import { + deletePersonOnProject, + modalToggle, + setProjectBoardFetch, + setToggleTab, + getProjectBoard, + getBoarderLoader +} from "../../../redux/projectsTrackerSlice"; import { apiRequest } from "../../../api/request"; import project from "../../../images/trackerProject.svg"; @@ -22,7 +29,6 @@ import plus from "../../../images/plus.svg"; import tasks from "../../../images/trackerTasks.svg"; import archive from "../../../images/archiveTracker.svg"; import selectArrow from "../../../images/select.svg"; -import avatarTest from "../../../images/AvatarTest .png"; import arrow from "../../../images/arrowCalendar.png"; import link from "../../../images/link.svg"; import archive2 from "../../../images/archive.svg"; @@ -30,28 +36,39 @@ import del from "../../../images/delete.svg"; import edit from "../../../images/edit.svg"; import "./ticketFullScreen.scss"; +import close from "../../../images/closeProjectPersons.svg"; +import {urlForLocal} from "../../../helper"; +import {getCorrectDate} from "../../Calendar/calendarHelper"; export const TicketFullScreen = ({}) => { const [modalAddWorker, setModalAddWorker] = useState(false); const ticketId = useParams(); const dispatch = useDispatch(); const navigate = useNavigate(); - const [projectInfo, setProjectInfo] = useState({}); + const projectBoard = useSelector(getProjectBoard); + const boardLoader = useSelector(getBoarderLoader); const [taskInfo, setTaskInfo] = useState({}); const [editOpen, setEditOpen] = useState(false); - const [inputsValue, setInputsValue] = useState({}) - const [loader, setLoader] = useState(true) + const [inputsValue, setInputsValue] = useState({}); + const [loader, setLoader] = useState(true); + const [comments, setComments] = useState([]); + const [commentsEditOpen, setCommentsEditOpen] = useState({}) + const [commentsEditText, setCommentsEditText] = useState({}) + const [personListOpen, setPersonListOpen] = useState(false) useEffect(() => { apiRequest(`/task/get-task?task_id=${ticketId.id}`).then((taskInfo) => { setTaskInfo(taskInfo); - setInputsValue({title: taskInfo.title, description: taskInfo.description}) - apiRequest(`/project/get-project?project_id=${taskInfo.project_id}`).then( - (project) => { - setProjectInfo(project); - setLoader(false) - } - ); + setInputsValue({title: taskInfo.title, description: taskInfo.description, comment: ''}) + apiRequest(`/comment/get-by-entity?entity_type=2&entity_id=${taskInfo.id}`).then((res) => { + setComments(res) + res.forEach((item) => { + setCommentsEditOpen((prevValue) => ({...prevValue, [item.id]: false})) + setCommentsEditText((prevValue) => ({...prevValue, [item.id]: item.text})) + }) + }) + dispatch(setProjectBoardFetch(taskInfo.project_id)); + setLoader(boardLoader) }); }, []); @@ -79,6 +96,59 @@ export const TicketFullScreen = ({}) => { }); } + function createComment() { + apiRequest("/comment/create", { + method: "POST", + data: { + text: inputsValue.comment, + entity_type: 2, + entity_id: taskInfo.id + } + }).then((res) => { + let newComment = res + newComment.created_at = new Date() + setInputsValue((prevValue) => ({...prevValue, comment: ''})) + setComments((prevValue) => ([...prevValue, newComment])) + setCommentsEditOpen((prevValue) => ({...prevValue, [res.id]: false})) + setCommentsEditText((prevValue) => ({...prevValue, [res.id]: res.text})) + }) + } + + function deleteComment(commentId) { + apiRequest("/comment/update", { + method: "PUT", + data: { + comment_id: commentId, + status: 0 + } + }).then((res) => { + setComments((prevValue) => prevValue.filter((item) => item.id !== commentId)) + }) + } + + function editComment(commentId) { + apiRequest("/comment/update", { + method: "PUT", + data: { + comment_id: commentId, + text: commentsEditText[commentId] + } + }).then((res) => { + }) + } + + function deletePerson(userId) { + apiRequest("/project/del-user", { + method: "DELETE", + data: { + project_id: projectBoard.id, + user_id: userId + }, + }).then((res) => { + dispatch(deletePersonOnProject(userId)) + }); + } + const toggleTabs = (index) => { dispatch(setToggleTab(index)); }; @@ -130,7 +200,7 @@ export const TicketFullScreen = ({}) => {
-

Проект : {projectInfo.name}

+

Проект : {projectBoard.name}

{ >
- avatar - avatar - +9 + {/*avatar*/} + {/*avatar*/} + {projectBoard.projectUsers?.length} { - dispatch(modalToggle("addWorker")); - setModalAddWorker(true); + setPersonListOpen(true) }} > + -

добавить участника в проект

+

добавить участника

+ {personListOpen && +
+ close setPersonListOpen(false)} /> +
{projectBoard.projectUsers?.length}участник
+
В проекте - “{projectBoard.name}”
+
+ {projectBoard.projectUsers?.map((person) => { + return
+ avatar + {person.user.fio} + delete deletePerson(person.user_id)}/> +
+ }) + } +
+
{ + dispatch(modalToggle("addWorker")); + setModalAddWorker(true); + setPersonListOpen(false) + }} + > + + +

Добавить участников

+
+
+ }
Учавствую @@ -180,7 +276,7 @@ export const TicketFullScreen = ({}) => { {editOpen ? { setInputsValue((prevValue) => ({...prevValue, description: e.target.value})) }}/> :

{inputsValue.description}

} - + {/**/}

@@ -204,8 +300,33 @@ export const TicketFullScreen = ({}) => {

- - + { + setInputsValue((prevValue) => ({...prevValue, comment: e.target.value})) + }} /> + +
+
+ {comments.map((comment) => { + return
+
+ {getCorrectDate(comment.created_at)} +
+ edit { + if (commentsEditOpen[comment.id]) { + editComment(comment.id) + } + setCommentsEditOpen((prevValue) => ({...prevValue, [comment.id]: !prevValue[comment.id]})) + }} /> +
+ delete deleteComment(comment.id)} /> +
+ {commentsEditOpen[comment.id] ? { + setCommentsEditText((prevValue) => ({...prevValue, [comment.id]: e.target.value})) + }} /> :

{commentsEditText[comment.id]}

} +
+ }) + + }
diff --git a/src/components/UI/TrackerModal/TrackerModal.jsx b/src/components/UI/TrackerModal/TrackerModal.jsx index d8361f34..2d23bab6 100644 --- a/src/components/UI/TrackerModal/TrackerModal.jsx +++ b/src/components/UI/TrackerModal/TrackerModal.jsx @@ -12,7 +12,8 @@ import { editProjectName, editColumnName, getColumnName, - getColumnId + getColumnId, + addPersonToProject } from "../../../redux/projectsTrackerSlice"; import arrowDown from "../../../images/selectArrow.png" @@ -136,23 +137,36 @@ export const TrackerModal = ({ apiRequest("/project/add-user", { method: "POST", data: { - user_id: selectedWorker.id, + user_id: selectedWorker.user_id, project_id: projectBoard.id } }).then((el) => { + dispatch(addPersonToProject(el)) setActive(false); - selectedWorker(null) + setSelectedWorker('') + setSelectWorkersOpen(false) }) } useEffect(() => { - modalType === "addWorker" ? apiRequest('/project/my-employee').then((el) => setWorkers(el.managerEmployees)) : '' - }, [modalType]) + modalType === "addWorker" ? 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 + }, [])) + }) : '' + }, [active]) + return (
setActive(false)} + onClick={() => { + setActive(false) + setSelectWorkersOpen(false) + }} >
e.stopPropagation()}> {modalType === "addWorker" && ( @@ -170,18 +184,24 @@ export const TrackerModal = ({

{selectedWorker ? selectedWorker.employee.fio : 'Выберите пользователя'}

arrow {Boolean(selectWorkersOpen) && -
- {workers.map((worker) => { - if (worker === selectedWorker) { - return +
+ {Boolean(workers.length) ? + workers.map((worker) => { + if (worker === selectedWorker) { + return + } + return
+ { + setSelectedWorker(worker) + } + }> +

{worker.employee.fio}

+ avatar +
+ }) : +
Нет пользователей
} - return
setSelectedWorker(worker)}> -

{worker.employee.fio}

- avatar -
- }) - } -
+
}
diff --git a/src/images/closeProjectPersons.svg b/src/images/closeProjectPersons.svg new file mode 100644 index 00000000..baf390e2 --- /dev/null +++ b/src/images/closeProjectPersons.svg @@ -0,0 +1,3 @@ + + + diff --git a/src/pages/ProjectTracker/ProjectTracker.js b/src/pages/ProjectTracker/ProjectTracker.js index 854e5ced..8c19d66e 100644 --- a/src/pages/ProjectTracker/ProjectTracker.js +++ b/src/pages/ProjectTracker/ProjectTracker.js @@ -5,6 +5,7 @@ import { ProfileBreadcrumbs } from "../../components/ProfileBreadcrumbs/ProfileB import { Footer } from "../../components/Footer/Footer"; import { Navigation } from "../../components/Navigation/Navigation"; import { Loader } from "../../components/Loader/Loader"; +import { urlForLocal } from '../../helper' import { useDispatch, useSelector } from "react-redux"; import { apiRequest } from "../../api/request"; @@ -18,6 +19,7 @@ import { activeLoader, setColumnName, setColumnId, + deletePersonOnProject } from "../../redux/projectsTrackerSlice"; import ModalTicket from "../../components/UI/ModalTicket/ModalTicket"; @@ -32,6 +34,7 @@ import filesBoard from "../../images/filesBoard.svg"; import arrow from "../../images/arrowCalendar.png"; import del from "../../images/delete.svg"; import edit from "../../images/edit.svg"; +import close from "../../images/closeProjectPersons.svg" export const ProjectTracker = () => { const dispatch = useDispatch(); @@ -44,6 +47,7 @@ export const ProjectTracker = () => { const [modalAdd, setModalAdd] = useState(false); const [modalActiveTicket, setModalActiveTicket] = useState(false); const [selectedTicket, setSelectedTicket] = useState({}); + const [personListOpen, setPersonListOpen] = useState(false) const startWrapperIndexTest = useRef({}); const projectBoard = useSelector(getProjectBoard); @@ -151,6 +155,18 @@ export const ProjectTracker = () => { }); } + function deletePerson(userId) { + apiRequest("/project/del-user", { + method: "DELETE", + data: { + project_id: projectBoard.id, + user_id: userId + }, + }).then((res) => { + dispatch(deletePersonOnProject(userId)) + }); + } + return (
@@ -226,13 +242,39 @@ export const ProjectTracker = () => { { - dispatch(modalToggle("addWorker")); - setModalAdd(true); + setPersonListOpen(true) }} > +

добавить участника

+ {personListOpen && +
+ close setPersonListOpen(false)} /> +
{projectBoard.projectUsers?.length}участник
+
В проекте - “{projectBoard.name}”
+
+ {projectBoard.projectUsers?.map((person) => { + return
+ avatar + {person.user.fio} + delete deletePerson(person.user_id)}/> +
+ }) + } +
+
{ + dispatch(modalToggle("addWorker")); + setModalAdd(true); + setPersonListOpen(false) + }} + > + + +

Добавить участников

+
+
+ }
Участвую diff --git a/src/pages/Tracker/tracker.scss b/src/pages/Tracker/tracker.scss index f7452790..6a0d7626 100644 --- a/src/pages/Tracker/tracker.scss +++ b/src/pages/Tracker/tracker.scss @@ -309,6 +309,118 @@ line-height: 17px; max-width: 125px; } + + .persons__list { + position: absolute; + z-index: 2; + display: flex; + flex-direction: column; + background: linear-gradient(180deg, #FFFFFF 0%, #EBEBEB 100%); + border-radius: 40px; + padding: 33px 24px 44px 34px; + width: 425px; + cursor: default; + + &__close { + cursor: pointer; + width: 8px; + height: 8px; + margin-left: auto; + } + + &__count { + display: flex; + align-items: end; + color: #1458DD; + font-size: 22px; + margin-top: 10px; + span { + font-size: 44px; + font-weight: 700; + line-height: 40px; + width: auto; + height: auto; + margin-right: 5px; + } + } + + &__info { + display: flex; + font-size: 18px; + line-height: 22px; + color: #263238; + font-weight: 500; + margin: 13px 0 32px; + + span { + width: auto; + height: auto; + color: #1458DD; + font-weight: 600; + font-size: 18px; + line-height: 22px; + } + } + + &__items { + display: flex; + flex-wrap: wrap; + row-gap: 60px; + column-gap: 35px; + margin-bottom: 38px; + } + + &__item { + width: 145px; + display: flex; + justify-content: space-between; + align-items: center; + + .avatar { + width: 22px; + height: 22px; + } + + span { + display: block; + font-weight: 400; + font-size: 12px; + line-height: initial; + color: #807777; + width: auto; + height: auto; + max-width: 85px; + overflow: hidden; + white-space: nowrap; + text-overflow: ellipsis; + } + + .delete { + cursor: pointer; + width: 14px; + height: 14px; + } + } + + &__add { + display: flex; + cursor: pointer; + + span { + background: #8BCC60; + left: 0; + } + + p { + margin-left: 17px; + color: #000000; + font-weight: 400; + font-size: 12px; + line-height: 32px; + position: initial; + } + } + } } &__select { diff --git a/src/redux/projectsTrackerSlice.js b/src/redux/projectsTrackerSlice.js index 3e7333aa..b4fe55fd 100644 --- a/src/redux/projectsTrackerSlice.js +++ b/src/redux/projectsTrackerSlice.js @@ -35,6 +35,12 @@ export const projectsTrackerSlice = createSlice({ } }); }, + deletePersonOnProject: (state,action) => { + state.projectBoard.projectUsers = state.projectBoard.projectUsers.filter((person) => person.user_id !== action.payload) + }, + addPersonToProject: (state, action) => { + state.projectBoard.projectUsers.push(action.payload) + }, activeLoader: (state) => { state.boardLoader = true; }, @@ -100,7 +106,9 @@ export const { activeLoader, editProjectName, editColumnName, - setColumnId + setColumnId, + deletePersonOnProject, + addPersonToProject } = projectsTrackerSlice.actions; export const getProjects = (state) => state.tracker.projects;