add comments to tasks

This commit is contained in:
Николай Полтщук 2023-06-01 03:21:41 +03:00
parent c55a697f6b
commit 4e0a44e5a4
4 changed files with 290 additions and 160 deletions

View File

@ -0,0 +1,125 @@
import React, { useState } from "react";
import TrackerTaskSubComment from "../TrackerTaskComment/TrackerTaskComment";
import { apiRequest } from "../../api/request";
import {urlForLocal} from "../../helper";
import {getCorrectDate} from "../Calendar/calendarHelper";
import edit from "../../images/edit.svg";
import del from "../../images/delete.svg";
import accept from "../../images/accept.png";
export const TrackerTaskComment = ({
taskId,
comment,
commentDelete,
addSubComment,
subCommentDelete
}) => {
const [commentsEditOpen, setCommentsEditOpen] = useState(false)
const [commentsEditText, setCommentsEditText] = useState(comment.text)
const [subCommentsCreateOpen, setSubCommentsCreateOpen] = useState(false)
const [subCommentsCreateText, setSubCommentsCreateText] = useState('')
function editComment() {
if (commentsEditText === comment.text) return
apiRequest("/comment/update", {
method: "PUT",
data: {
comment_id: comment.id,
text: commentsEditText
}
}).then(() => {
})
}
function deleteComment() {
apiRequest("/comment/update", {
method: "PUT",
data: {
comment_id: comment.id,
status: 0
}
}).then(() => {
if (comment.parent_id) {
subCommentDelete(comment)
} else {
commentDelete(comment)
}
})
}
function createSubComment() {
setSubCommentsCreateOpen(false)
if(!subCommentsCreateText) return
apiRequest("/comment/create", {
method: "POST",
data: {
text: subCommentsCreateText,
entity_type: 2,
entity_id: taskId,
parent_id: comment.id
}
}).then((res) => {
let newSubComment = res
newSubComment.created_at = new Date()
setSubCommentsCreateText('')
addSubComment(comment.id, newSubComment)
})
}
return (
<div className={[!comment.parent_id && comment.subComments.length ? 'comments__list__item__main': '',
'comments__list__item',
comment.parent_id ? 'comments__list__item__subComment' : ''].join(' ')}>
<div className='comments__list__item__info'>
<div className='comments__list__item__fio'>
<img src={urlForLocal(comment.user.avatar)} alt='avatar' />
<p>{comment.user.fio}</p>
</div>
<div className='comments__list__item__date'>
<span>{getCorrectDate(comment.created_at)}</span>
<div className={commentsEditOpen ? 'edit edit__open' : 'edit'} >
<img src={edit} alt='edit' onClick={() => {
if (commentsEditOpen) {
editComment()
}
setCommentsEditOpen(!commentsEditOpen)
}} />
</div>
<img src={del} alt='delete' onClick={() => deleteComment()} />
</div>
</div>
{commentsEditOpen ?
<input className='comments__list__item__text' value={commentsEditText} onChange={(e) => {
setCommentsEditText(e.target.value)
}} /> :
<p className='comments__list__item__text'>{commentsEditText}</p>}
{!comment.parent_id &&
<>
{
subCommentsCreateOpen ?
<div className='comments__list__item__answer__new'>
<input value={subCommentsCreateText} onChange={(e) => {
setSubCommentsCreateText(e.target.value)
}}/>
<img src={accept} alt='accept'
onClick={() => {
createSubComment()
}}
/>
</div>
:
<span onClick={() => {
setSubCommentsCreateOpen(true)
}} className='comments__list__item__answer'>Ответить</span>
}
</>
}
{Boolean(comment.subComments?.length) && comment.subComments.map((subComment) => {
return <TrackerTaskSubComment key={subComment.id} taskId={taskId} comment={subComment} subCommentDelete={subCommentDelete}/>
})
}
</div>
)
}
export default TrackerTaskComment

View File

@ -1,6 +1,7 @@
import React, {useEffect, useState} from "react"; import React, {useEffect, useState} from "react";
import { Link } from "react-router-dom"; import { Link } from "react-router-dom";
import TrackerModal from "../TrackerModal/TrackerModal"; import TrackerModal from "../TrackerModal/TrackerModal";
import TrackerTaskComment from "../../../components/TrackerTaskComment/TrackerTaskComment";
import { apiRequest } from "../../../api/request"; import { apiRequest } from "../../../api/request";
import { useDispatch } from "react-redux"; import { useDispatch } from "react-redux";
import { import {
@ -8,8 +9,6 @@ import {
setProjectBoardFetch, setProjectBoardFetch,
} from "../../../redux/projectsTrackerSlice"; } from "../../../redux/projectsTrackerSlice";
import {getCorrectDate} from '../../../components/Calendar/calendarHelper'
import category from "../../../images/category.png"; import category from "../../../images/category.png";
import watch from "../../../images/watch.png"; import watch from "../../../images/watch.png";
import file from "../../../images/fileModal.svg"; import file from "../../../images/fileModal.svg";
@ -25,7 +24,6 @@ import close from "../../../images/closeProjectPersons.svg";
import "./ModalTicket.scss"; import "./ModalTicket.scss";
import {urlForLocal, getCorrectRequestDate} from "../../../helper"; import {urlForLocal, getCorrectRequestDate} from "../../../helper";
import accept from "../../../images/accept.png";
export const ModalTiсket = ({ export const ModalTiсket = ({
active, active,
@ -40,9 +38,6 @@ export const ModalTiсket = ({
const [editOpen, setEditOpen] = useState(false); const [editOpen, setEditOpen] = useState(false);
const [inputsValue, setInputsValue] = useState({title: task.title, description: task.description, comment: ''}); const [inputsValue, setInputsValue] = useState({title: task.title, description: task.description, comment: ''});
const [comments, setComments] = useState([]); const [comments, setComments] = useState([]);
const [commentsEditOpen, setCommentsEditOpen] = useState({})
const [commentsEditText, setCommentsEditText] = useState({})
const [subCommentsCreateOpen, setSubCommentsCreateOpen] = useState({})
const [dropListOpen, setDropListOpen] = useState(false) const [dropListOpen, setDropListOpen] = useState(false)
const [dropListMembersOpen, setDropListMembersOpen] = useState(false) const [dropListMembersOpen, setDropListMembersOpen] = useState(false)
const [executor, setExecutor] = useState(task.executor) const [executor, setExecutor] = useState(task.executor)
@ -94,49 +89,48 @@ export const ModalTiсket = ({
}).then((res) => { }).then((res) => {
let newComment = res let newComment = res
newComment.created_at = new Date() newComment.created_at = new Date()
newComment.subComments = []
setInputsValue((prevValue) => ({...prevValue, comment: ''})) setInputsValue((prevValue) => ({...prevValue, comment: ''}))
setComments((prevValue) => ([...prevValue, newComment])) setComments((prevValue) => ([...prevValue, newComment]))
setCommentsEditOpen((prevValue) => ({...prevValue, [res.id]: false}))
setCommentsEditText((prevValue) => ({...prevValue, [res.id]: res.text}))
}) })
} }
function deleteComment(commentId) {
apiRequest("/comment/update", { function commentDelete(comment) {
method: "PUT", setComments((prevValue) => prevValue.filter((item) => item.id !== comment.id))
data: { if (comment.subComments.length) {
comment_id: commentId, comment.subComments.forEach((subComment) => {
status: 0 apiRequest("/comment/update", {
method: "PUT",
data: {
comment_id: subComment.id,
status: 0
}
}).then(() => {
})
})
}
}
function addSubComment(commentId, subComment) {
const addSubComment = comments
addSubComment.forEach((comment) => {
if (comment.id === commentId) {
comment.subComments.push(subComment)
} }
}).then((res) => {
setComments((prevValue) => prevValue.filter((item) => item.id !== commentId))
}) })
setComments(addSubComment)
} }
function editComment(commentId) { function subCommentDelete(subComment) {
const deleteSubComment = comments
apiRequest("/comment/update", { deleteSubComment.forEach((comment, index) => {
method: "PUT", if (comment.id === subComment.parent_id) {
data: { deleteSubComment[index].subComments = comment.subComments.filter((item) => item.id !== subComment.id)
comment_id: commentId,
text: commentsEditText[commentId]
} }
}).then((res) => {
// createSubComment()
}) })
setComments([...deleteSubComment])
} }
// function createSubComment() {
// apiRequest("/comment/create", {
// method: "POST",
// data: {
// text: '12312312',
// entity_type: 2,
// entity_id: task.id,
// parent_id: 36
// }
// }).then((res) => console.log(res))
// }
function startTaskTimer() { function startTaskTimer() {
apiRequest("/timer/create", { apiRequest("/timer/create", {
method: "POST", method: "POST",
@ -159,7 +153,7 @@ export const ModalTiсket = ({
timer_id: timerInfo.id, timer_id: timerInfo.id,
stopped_at: getCorrectRequestDate(new Date()) stopped_at: getCorrectRequestDate(new Date())
} }
}).then((res) => { }).then(() => {
setTimerStart(false) setTimerStart(false)
clearInterval(timerId) clearInterval(timerId)
}) })
@ -185,7 +179,7 @@ export const ModalTiсket = ({
task_id: task.id, task_id: task.id,
executor_id: 0 executor_id: 0
}, },
}).then((res) => { }).then(() => {
setExecutor(null) setExecutor(null)
}); });
} }
@ -210,19 +204,24 @@ export const ModalTiсket = ({
task_id: task.id, task_id: task.id,
user_id: person.user_id user_id: person.user_id
}, },
}).then((res) => { }).then(() => {
setMembers(members.filter((item) => item.user_id !== person.user_id)) setMembers(members.filter((item) => item.user_id !== person.user_id))
}); });
} }
useEffect(() => { useEffect(() => {
apiRequest(`/comment/get-by-entity?entity_type=2&entity_id=${task.id}`).then((res) => { apiRequest(`/comment/get-by-entity?entity_type=2&entity_id=${task.id}`).then((res) => {
setComments(res) const comments = res.reduce((acc, cur) => {
res.forEach((item) => { if (!cur.parent_id) {
setCommentsEditOpen((prevValue) => ({...prevValue, [item.id]: false})) acc.push({...cur, subComments: []})
setCommentsEditText((prevValue) => ({...prevValue, [item.id]: item.text})) } else {
setSubCommentsCreateOpen((prevValue) => ({...prevValue, [item.id]: false})) acc.forEach((item) => {
}) if (item.id === cur.parent_id) item.subComments.push(cur)
})
}
return acc
}, [])
setComments(comments)
}) })
apiRequest(`/timer/get-by-entity?entity_type=2&entity_id=${task.id}`).then((res) => { apiRequest(`/timer/get-by-entity?entity_type=2&entity_id=${task.id}`).then((res) => {
let timerSeconds = 0 let timerSeconds = 0
@ -347,45 +346,15 @@ export const ModalTiсket = ({
</div> </div>
<div className='comments__list'> <div className='comments__list'>
{comments.map((comment) => { {comments.map((comment) => {
return <div className='comments__list__item' key={comment.id}> return <TrackerTaskComment
<div className='comments__list__item__info'> key={comment.id}
<div className='comments__list__item__fio'> taskId={task.id}
<img src={urlForLocal(comment.user.avatar)} alt='avatar' /> comment={comment}
<p>{comment.user.fio}</p> commentDelete={commentDelete}
</div> addSubComment={addSubComment}
<div className='comments__list__item__date'> subCommentDelete={subCommentDelete}
<span>{getCorrectDate(comment.created_at)}</span> />
<div className={commentsEditOpen[comment.id] ? 'edit edit__open' : 'edit'} > })
<img src={edit} alt='edit' onClick={() => {
if (commentsEditOpen[comment.id]) {
editComment(comment.id)
}
setCommentsEditOpen((prevValue) => ({...prevValue, [comment.id]: !prevValue[comment.id]}))
}} />
</div>
<img src={del} alt='delete' onClick={() => deleteComment(comment.id)} />
</div>
</div>
{commentsEditOpen[comment.id] ? <input className='comments__list__item__text' value={commentsEditText[comment.id]} onChange={(e) => {
setCommentsEditText((prevValue) => ({...prevValue, [comment.id]: e.target.value}))
}} /> : <p className='comments__list__item__text'>{commentsEditText[comment.id]}</p>}
{subCommentsCreateOpen[comment.id] ?
<div className='comments__list__item__answer__new'>
<input />
<img src={accept} alt='accept'
onClick={() => {
setSubCommentsCreateOpen((prevValue) => ({...prevValue, [comment.id]: !prevValue[comment.id]}))
}}
/>
</div>
:
<span onClick={() => {
setSubCommentsCreateOpen((prevValue) => ({...prevValue, [comment.id]: !prevValue[comment.id]}))
}} className='comments__list__item__answer'>Ответить</span>
}
</div>
})
} }
</div> </div>
</div> </div>

View File

@ -81,6 +81,22 @@
flex-direction: column; flex-direction: column;
max-height: 420px; max-height: 420px;
overflow: auto; overflow: auto;
&::-webkit-scrollbar {
width: 4px;
border-radius: 10px;
}
&::-webkit-scrollbar-thumb {
background: #cbd9f9;
border-radius: 10px;
}
&::-webkit-scrollbar-track {
background: #c5c0c6;
border-radius: 10px;
}
&__item { &__item {
padding: 10px 20px; padding: 10px 20px;
display: flex; display: flex;
@ -89,6 +105,41 @@
border-radius: 44px; border-radius: 44px;
font-size: 14px; font-size: 14px;
width: 100%; width: 100%;
position: relative;
&__subComment {
&:before {
content: '';
background: #E4E4E6;
height: 1px;
width: 7px;
position: absolute;
top: 36%;
left: 2.5%;
}
}
&__main {
&:after {
content: '';
position: absolute;
background-image: url("../../../images/mainTaskCommentImg.png");
width: 10px;
height: 8px;
top: 50px;
left: 25px;
}
&:before {
content: '';
position: absolute;
background: #E4E4E6;
width: 1px;
height: calc(100% - 120px);
left: 29px;
top: 65px;
}
}
&__fio { &__fio {
display: flex; display: flex;
@ -163,6 +214,7 @@
padding: 3px 5px; padding: 3px 5px;
display: flex; display: flex;
align-items: center; align-items: center;
margin-top: 5px;
img { img {
width: 20px; width: 20px;

View File

@ -5,6 +5,7 @@ import { ProfileBreadcrumbs } from "../../ProfileBreadcrumbs/ProfileBreadcrumbs"
import { Footer } from "../../Footer/Footer"; import { Footer } from "../../Footer/Footer";
import { Link, useParams, useNavigate } from "react-router-dom"; import { Link, useParams, useNavigate } from "react-router-dom";
import TrackerModal from "../TrackerModal/TrackerModal"; import TrackerModal from "../TrackerModal/TrackerModal";
import TrackerTaskComment from "../../../components/TrackerTaskComment/TrackerTaskComment";
import { Navigation } from "../../Navigation/Navigation"; import { Navigation } from "../../Navigation/Navigation";
import {Loader} from "../../Loader/Loader"; import {Loader} from "../../Loader/Loader";
@ -33,12 +34,10 @@ import link from "../../../images/link.svg";
import archive2 from "../../../images/archive.svg"; import archive2 from "../../../images/archive.svg";
import del from "../../../images/delete.svg"; import del from "../../../images/delete.svg";
import edit from "../../../images/edit.svg"; import edit from "../../../images/edit.svg";
import accept from "../../../images/accept.png"
import "./ticketFullScreen.scss"; import "./ticketFullScreen.scss";
import close from "../../../images/closeProjectPersons.svg"; import close from "../../../images/closeProjectPersons.svg";
import {getCorrectRequestDate, urlForLocal} from "../../../helper"; import {getCorrectRequestDate, urlForLocal} from "../../../helper";
import {getCorrectDate} from "../../Calendar/calendarHelper";
export const TicketFullScreen = ({}) => { export const TicketFullScreen = ({}) => {
const [modalAddWorker, setModalAddWorker] = useState(false); const [modalAddWorker, setModalAddWorker] = useState(false);
@ -52,9 +51,6 @@ export const TicketFullScreen = ({}) => {
const [inputsValue, setInputsValue] = useState({}); const [inputsValue, setInputsValue] = useState({});
const [loader, setLoader] = useState(true); const [loader, setLoader] = useState(true);
const [comments, setComments] = useState([]); const [comments, setComments] = useState([]);
const [commentsEditOpen, setCommentsEditOpen] = useState({})
const [subCommentsCreateOpen, setSubCommentsCreateOpen] = useState({})
const [commentsEditText, setCommentsEditText] = useState({})
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({})
@ -64,12 +60,17 @@ export const TicketFullScreen = ({}) => {
setTaskInfo(taskInfo); setTaskInfo(taskInfo);
setInputsValue({title: taskInfo.title, description: taskInfo.description, comment: ''}) setInputsValue({title: taskInfo.title, description: taskInfo.description, comment: ''})
apiRequest(`/comment/get-by-entity?entity_type=2&entity_id=${taskInfo.id}`).then((res) => { apiRequest(`/comment/get-by-entity?entity_type=2&entity_id=${taskInfo.id}`).then((res) => {
setComments(res) const comments = res.reduce((acc, cur) => {
res.forEach((item) => { if (!cur.parent_id) {
setCommentsEditOpen((prevValue) => ({...prevValue, [item.id]: false})) acc.push({...cur, subComments: []})
setSubCommentsCreateOpen((prevValue) => ({...prevValue, [item.id]: false})) } else {
setCommentsEditText((prevValue) => ({...prevValue, [item.id]: item.text})) acc.forEach((item) => {
}) if (item.id === cur.parent_id) item.subComments.push(cur)
})
}
return acc
}, [])
setComments(comments)
}) })
taskInfo.timers.forEach((time) => { taskInfo.timers.forEach((time) => {
if (!time.stopped_at) { if (!time.stopped_at) {
@ -102,7 +103,7 @@ export const TicketFullScreen = ({}) => {
title: inputsValue.title, title: inputsValue.title,
description: inputsValue.description description: inputsValue.description
}, },
}).then((res) => { }).then(() => {
}); });
} }
@ -117,33 +118,9 @@ export const TicketFullScreen = ({}) => {
}).then((res) => { }).then((res) => {
let newComment = res let newComment = res
newComment.created_at = new Date() newComment.created_at = new Date()
newComment.subComments = []
setInputsValue((prevValue) => ({...prevValue, comment: ''})) setInputsValue((prevValue) => ({...prevValue, comment: ''}))
setComments((prevValue) => ([...prevValue, newComment])) 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) => {
}) })
} }
@ -168,7 +145,7 @@ export const TicketFullScreen = ({}) => {
timer_id: timerInfo.id, timer_id: timerInfo.id,
stopped_at: getCorrectRequestDate(new Date()) stopped_at: getCorrectRequestDate(new Date())
} }
}).then((res) => setTimerStart(false)) }).then(() => setTimerStart(false))
} }
function deletePerson(userId) { function deletePerson(userId) {
@ -183,6 +160,42 @@ export const TicketFullScreen = ({}) => {
}); });
} }
function commentDelete(comment) {
setComments((prevValue) => prevValue.filter((item) => item.id !== comment.id))
if (comment.subComments.length) {
comment.subComments.forEach((subComment) => {
apiRequest("/comment/update", {
method: "PUT",
data: {
comment_id: subComment.id,
status: 0
}
}).then(() => {
})
})
}
}
function addSubComment(commentId, subComment) {
const addSubComment = comments
addSubComment.forEach((comment) => {
if (comment.id === commentId) {
comment.subComments.push(subComment)
}
})
setComments(addSubComment)
}
function subCommentDelete(subComment) {
const deleteSubComment = comments
deleteSubComment.forEach((comment, index) => {
if (comment.id === subComment.parent_id) {
deleteSubComment[index].subComments = comment.subComments.filter((item) => item.id !== subComment.id)
}
})
setComments([...deleteSubComment])
}
const toggleTabs = (index) => { const toggleTabs = (index) => {
dispatch(setToggleTab(index)); dispatch(setToggleTab(index));
}; };
@ -341,43 +354,14 @@ export const TicketFullScreen = ({}) => {
</div> </div>
<div className='comments__list'> <div className='comments__list'>
{comments.map((comment) => { {comments.map((comment) => {
return <div className='comments__list__item' key={comment.id}> return <TrackerTaskComment
<div className='comments__list__item__info'> key={comment.id}
<div className='comments__list__item__fio'> taskId={taskInfo.id}
<img src={urlForLocal(comment.user.avatar)} alt='avatar' /> comment={comment}
<p>{comment.user.fio}</p> commentDelete={commentDelete}
</div> addSubComment={addSubComment}
<div className='comments__list__item__date'> subCommentDelete={subCommentDelete}
<span>{getCorrectDate(comment.created_at)}</span> />
<div className={commentsEditOpen[comment.id] ? 'edit edit__open' : 'edit'} >
<img src={edit} alt='edit' onClick={() => {
if (commentsEditOpen[comment.id]) {
editComment(comment.id)
}
setCommentsEditOpen((prevValue) => ({...prevValue, [comment.id]: !prevValue[comment.id]}))
}} />
</div>
<img src={del} alt='delete' onClick={() => deleteComment(comment.id)} />
</div>
</div>
{commentsEditOpen[comment.id] ? <input className='comments__list__item__text' value={commentsEditText[comment.id]} onChange={(e) => {
setCommentsEditText((prevValue) => ({...prevValue, [comment.id]: e.target.value}))
}} /> : <p className='comments__list__item__text'>{commentsEditText[comment.id]}</p>}
{subCommentsCreateOpen[comment.id] ?
<div className='comments__list__item__answer__new'>
<input />
<img src={accept} alt='accept'
onClick={() => {
setSubCommentsCreateOpen((prevValue) => ({...prevValue, [comment.id]: !prevValue[comment.id]}))
}}
/>
</div>
:
<span onClick={() => {
setSubCommentsCreateOpen((prevValue) => ({...prevValue, [comment.id]: !prevValue[comment.id]}))
}} className='comments__list__item__answer'>Ответить</span>
}
</div>
}) })
} }