This commit is contained in:
Николай Полтщук 2023-05-23 23:02:39 +03:00
parent a443bad839
commit c55376ecb3
8 changed files with 427 additions and 48 deletions

View File

@ -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";
@ -35,6 +37,8 @@ export const ModalTiсket = ({
const [editOpen, setEditOpen] = useState(false);
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", {
@ -62,7 +66,7 @@ export const ModalTiсket = ({
});
}
function editComment() {
function createComment() {
apiRequest("/comment/create", {
method: "POST",
data: {
@ -71,12 +75,46 @@ export const ModalTiсket = ({
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))
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 (
@ -109,7 +147,7 @@ export const ModalTiсket = ({
{editOpen ? <input value={inputsValue.description} onChange={(e) => {
setInputsValue((prevValue) => ({...prevValue, description: e.target.value}))
}}/> :<p>{inputsValue.description}</p>}
<img src={taskImg} className="image-task"></img>
{/*<img src={taskImg} className="image-task"></img>*/}
</div>
<div className="content__communication">
<p className="tasks">
@ -136,7 +174,30 @@ export const ModalTiсket = ({
<input placeholder="Оставить комментарий" value={inputsValue.comment} onChange={(e) => {
setInputsValue((prevValue) => ({...prevValue, comment: e.target.value}))
}} />
<img src={send} onClick={editComment}></img>
<img src={send} onClick={createComment}></img>
</div>
<div className='comments__list'>
{comments.map((comment) => {
return <div className='comments__list__item' key={comment.id}>
<div className='comments__list__item__info'>
<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>
{commentsEditOpen[comment.id] ? <input value={commentsEditText[comment.id]} onChange={(e) => {
setCommentsEditText((prevValue) => ({...prevValue, [comment.id]: e.target.value}))
}} /> : <p>{commentsEditText[comment.id]}</p>}
</div>
})
}
</div>
</div>
</div>

View File

@ -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 {

View File

@ -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,30 +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 [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, comment: ''})
apiRequest(`/comment/get-by-entity?entity_type=2&entity_id=${taskInfo.id}`).then((res) => setComments(res))
apiRequest(`/project/get-project?project_id=${taskInfo.project_id}`).then(
(project) => {
setProjectInfo(project);
setLoader(false)
}
);
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)
});
}, []);
@ -81,7 +96,7 @@ export const TicketFullScreen = ({}) => {
});
}
function editComment() {
function createComment() {
apiRequest("/comment/create", {
method: "POST",
data: {
@ -90,10 +105,50 @@ export const TicketFullScreen = ({}) => {
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));
};
@ -145,7 +200,7 @@ export const TicketFullScreen = ({}) => {
<div className="tracker__tabs__content content-tabs">
<div className="tasks__head">
<div className="tasks__head__wrapper">
<h4>Проект : {projectInfo.name}</h4>
<h4>Проект : {projectBoard.name}</h4>
<TrackerModal
active={modalAddWorker}
@ -153,19 +208,45 @@ export const TicketFullScreen = ({}) => {
></TrackerModal>
<div className="tasks__head__persons">
<img src={avatarTest} alt="avatar" />
<img src={avatarTest} alt="avatar" />
<span className="countPersons">+9</span>
{/*<img src={avatarTest} alt="avatar" />*/}
{/*<img src={avatarTest} alt="avatar" />*/}
<span className="countPersons">{projectBoard.projectUsers?.length}</span>
<span
className="addPerson"
onClick={() => {
dispatch(modalToggle("addWorker"));
setModalAddWorker(true);
setPersonListOpen(true)
}}
>
+
</span>
<p>добавить участника в проект</p>
<p>добавить участника</p>
{personListOpen &&
<div className='persons__list'>
<img className='persons__list__close' src={close} alt='close' onClick={() => setPersonListOpen(false)} />
<div className='persons__list__count'><span>{projectBoard.projectUsers?.length}</span>участник</div>
<div className='persons__list__info'>В проекте - <span>{projectBoard.name}</span></div>
<div className='persons__list__items'>
{projectBoard.projectUsers?.map((person) => {
return <div className='persons__list__item' key={person.user_id}>
<img className='avatar' src={urlForLocal(person.user.avatar)} alt='avatar' />
<span>{person.user.fio}</span>
<img className='delete' src={close} alt='delete' onClick={() => deletePerson(person.user_id)}/>
</div>
})
}
</div>
<div className='persons__list__add'
onClick={() => {
dispatch(modalToggle("addWorker"));
setModalAddWorker(true);
setPersonListOpen(false)
}}
>
<span className='addPerson'>+</span>
<p>Добавить участников</p>
</div>
</div>
}
</div>
<div className="tasks__head__select">
<span>Учавствую</span>
@ -195,7 +276,7 @@ export const TicketFullScreen = ({}) => {
{editOpen ? <input value={inputsValue.description} onChange={(e) => {
setInputsValue((prevValue) => ({...prevValue, description: e.target.value}))
}}/> :<p>{inputsValue.description}</p>}
<img src={task} className="image-task"></img>
{/*<img src={task} className="image-task"></img>*/}
</div>
<div className="content__communication">
<p className="tasks">
@ -222,7 +303,30 @@ export const TicketFullScreen = ({}) => {
<input placeholder="Оставить комментарий" value={inputsValue.comment} onChange={(e) => {
setInputsValue((prevValue) => ({...prevValue, comment: e.target.value}))
}} />
<img src={send} onClick={editComment}></img>
<img src={send} onClick={createComment}></img>
</div>
<div className='comments__list'>
{comments.map((comment) => {
return <div className='comments__list__item' key={comment.id}>
<div className='comments__list__item__info'>
<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>
{commentsEditOpen[comment.id] ? <input value={commentsEditText[comment.id]} onChange={(e) => {
setCommentsEditText((prevValue) => ({...prevValue, [comment.id]: e.target.value}))
}} /> : <p>{commentsEditText[comment.id]}</p>}
</div>
})
}
</div>
</div>
</div>

View File

@ -12,7 +12,8 @@ import {
editProjectName,
editColumnName,
getColumnName,
getColumnId
getColumnId,
addPersonToProject
} from "../../../redux/projectsTrackerSlice";
import arrowDown from "../../../images/selectArrow.png"
@ -136,12 +137,13 @@ 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)
})
}
@ -149,10 +151,14 @@ export const TrackerModal = ({
useEffect(() => {
modalType === "addWorker" ? apiRequest('/project/my-employee').then((el) => {
let persons = el.managerEmployees
projectBoard.projectUsers.forEach(person => persons.splice(persons.indexOf(person), 1))
setWorkers(persons)
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
}, []))
}) : ''
}, [modalType])
}, [active])
return (
<div
@ -179,18 +185,21 @@ export const TrackerModal = ({
<img className='arrow' src={arrowDown} alt='arrow' />
{Boolean(selectWorkersOpen) &&
<div className='select__worker__dropDown'>
{workers.map((worker) => {
if ((workers.length === 1 || 0) && worker === selectedWorker) {
return <p>Пользователей нет</p>
}
{Boolean(workers.length) ?
workers.map((worker) => {
if (worker === selectedWorker) {
return
}
return <div className='worker' key={worker.id} onClick={() => setSelectedWorker(worker)}>
return <div className='worker' key={worker.id} onClick={() =>
{
setSelectedWorker(worker)
}
}>
<p>{worker.employee.fio}</p>
<img src={urlForLocal(worker.employee.avatar)} alt='avatar'/>
</div>
})
}) :
<div>Нет пользователей</div>
}
</div>
}

View File

@ -0,0 +1,3 @@
<svg width="9" height="8" viewBox="0 0 9 8" fill="none" xmlns="http://www.w3.org/2000/svg">
<path d="M0.912203 -5.93063e-05C1.078 -0.00256001 1.17953 0.0699604 1.2738 0.164737C2.15005 1.04323 3.0278 1.92023 3.90504 2.79773C3.9859 2.87858 4.06642 2.87867 4.14661 2.79798C5.0061 1.93824 5.8656 1.07849 6.72534 0.219002C6.95365 -0.00906182 7.16121 -0.00781147 7.39403 0.221003C7.54257 0.367044 7.69161 0.512585 7.83915 0.659376C8.05321 0.872686 8.05446 1.0945 7.83915 1.31031C6.96516 2.18556 6.09116 3.0603 5.21416 3.9323C5.14239 4.00382 5.13689 4.03958 5.21266 4.11485C6.08516 4.97934 6.9534 5.84833 7.8229 6.71583C8.06171 6.95415 8.06096 7.1607 7.82115 7.40077C7.67385 7.54806 7.52756 7.6961 7.37952 7.84265C7.16821 8.05145 6.94815 8.05145 6.73784 7.8414C5.86284 6.9674 4.98735 6.09415 4.1161 5.21641C4.04008 5.13989 4.00582 5.14739 3.93455 5.21941C3.07131 6.08765 2.20457 6.9524 1.33832 7.81789C1.09625 8.05971 0.893197 8.06021 0.65338 7.82089C0.51184 7.6796 0.3703 7.53831 0.22926 7.39677C-0.00555557 7.16145 -0.00705598 6.9529 0.225259 6.71933C1.0915 5.84909 1.95725 4.97784 2.82674 4.1106C2.89976 4.03783 2.90326 4.00232 2.82924 3.92855C1.94299 3.04805 1.06074 2.1638 0.177496 1.2803C-0.0575702 1.04523 -0.0590706 0.835926 0.172495 0.604111C0.328538 0.447817 0.485583 0.292773 0.640876 0.135979C0.723149 0.0524555 0.820177 0.00394182 0.912203 -5.93063e-05Z" fill="#263238"/>
</svg>

After

Width:  |  Height:  |  Size: 1.4 KiB

View File

@ -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 (
<div className="tracker">
<ProfileHeader />
@ -226,13 +242,39 @@ export const ProjectTracker = () => {
<span
className="addPerson"
onClick={() => {
dispatch(modalToggle("addWorker"));
setModalAdd(true);
setPersonListOpen(true)
}}
>
+
</span>
<p>добавить участника</p>
{personListOpen &&
<div className='persons__list'>
<img className='persons__list__close' src={close} alt='close' onClick={() => setPersonListOpen(false)} />
<div className='persons__list__count'><span>{projectBoard.projectUsers?.length}</span>участник</div>
<div className='persons__list__info'>В проекте - <span>{projectBoard.name}</span></div>
<div className='persons__list__items'>
{projectBoard.projectUsers?.map((person) => {
return <div className='persons__list__item' key={person.user_id}>
<img className='avatar' src={urlForLocal(person.user.avatar)} alt='avatar' />
<span>{person.user.fio}</span>
<img className='delete' src={close} alt='delete' onClick={() => deletePerson(person.user_id)}/>
</div>
})
}
</div>
<div className='persons__list__add'
onClick={() => {
dispatch(modalToggle("addWorker"));
setModalAdd(true);
setPersonListOpen(false)
}}
>
<span className='addPerson'>+</span>
<p>Добавить участников</p>
</div>
</div>
}
</div>
<div className="tasks__head__select">
<span>Участвую</span>

View File

@ -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 {

View File

@ -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;