Merge pull request #111 from apuc/tracker-tasks

Tracker tasks
This commit is contained in:
NikoM1k 2023-07-04 15:15:43 +03:00 committed by GitHub
commit 43443997a5
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23
11 changed files with 336 additions and 21 deletions

View File

@ -7,7 +7,7 @@ import { Link } from "react-router-dom";
import { getProfileInfo } from "@redux/outstaffingSlice"; import { getProfileInfo } from "@redux/outstaffingSlice";
import { setProjectBoardFetch } from "@redux/projectsTrackerSlice"; import { setProjectBoardFetch } from "@redux/projectsTrackerSlice";
import { getCorrectRequestDate, urlForLocal } from "@utils/helper"; import { caseOfNum, getCorrectRequestDate, urlForLocal } from "@utils/helper";
import { apiRequest } from "@api/request"; import { apiRequest } from "@api/request";
@ -429,7 +429,7 @@ export const ModalTiсket = ({
Загрузить файл Загрузить файл
</button> </button>
<span>{0}</span> <span>{0}</span>
Файлов {caseOfNum(0, "files")}
</p> </p>
</div> </div>
<div className="content__input"> <div className="content__input">

View File

@ -78,8 +78,22 @@
font-style: normal; font-style: normal;
font-size: 16px; font-size: 16px;
line-height: 24px; line-height: 24px;
max-width: 340px; padding: 5px;
outline: none; outline: none;
border-radius: 8px;
border: 1px solid rgb(204, 206, 209);
}
.ck-toolbar {
border-radius: 8px 8px 0 0 !important;
}
.ck-content {
font-size: 14px;
min-height: 100px;
border: 1px solid rgb(204, 206, 209) !important;
border-radius: 0 0 8px 8px !important;
box-shadow: none !important;
} }
button { button {
@ -228,7 +242,7 @@
margin-left: 34px; margin-left: 34px;
text-decoration-line: underline; text-decoration-line: underline;
font-weight: 400; font-weight: 400;
font-size: 10px; font-size: 11px;
line-height: 32px; line-height: 32px;
cursor: pointer; cursor: pointer;
@ -381,9 +395,9 @@
margin-right: 18px; margin-right: 18px;
} }
&:focus-within { //&:focus-within {
border: 1px solid #0000004d; // border: 1px solid #0000004d;
} //}
} }
} }
@ -467,6 +481,11 @@
border: none; border: none;
color: white; color: white;
font-size: 17px; font-size: 17px;
transition: all 0.3s ease;
&:hover {
background: #6cc933;
}
} }
} }
@ -479,6 +498,11 @@
color: white; color: white;
background: #1458dd; background: #1458dd;
border-radius: 44px; border-radius: 44px;
transition: all 0.15s ease;
&:hover {
background: #0255ff;
}
img { img {
margin-left: 10px; margin-left: 10px;
@ -499,6 +523,11 @@
color: white; color: white;
background: red; background: red;
border-radius: 44px; border-radius: 44px;
transition: all 0.3s ease;
&:hover {
background: #f5693d;
}
} }
.time { .time {

View File

@ -8,11 +8,10 @@ import {
deletePersonOnProject, deletePersonOnProject,
getBoarderLoader, getBoarderLoader,
modalToggle, modalToggle,
setProjectBoardFetch,
setToggleTab, setToggleTab,
} from "@redux/projectsTrackerSlice"; } from "@redux/projectsTrackerSlice";
import { getCorrectRequestDate, urlForLocal } from "@utils/helper"; import { caseOfNum, getCorrectRequestDate, urlForLocal } from "@utils/helper";
import { apiRequest } from "@api/request"; import { apiRequest } from "@api/request";
@ -586,7 +585,7 @@ export const TicketFullScreen = () => {
Загрузить файл Загрузить файл
</BaseButton> </BaseButton>
<span>{0}</span> <span>{0}</span>
Файлов {caseOfNum(0, "files")}
</p> </p>
</div> </div>
<div className="content__input"> <div className="content__input">

View File

@ -94,6 +94,22 @@
max-width: 320px; max-width: 320px;
} }
.ck-editor__editable.ck-rounded-corners {
min-height: 100px;
font-size: 14px;
}
.ck-toolbar {
border: none !important;
border-radius: 8px 8px 0 0 !important;
}
.ck-content {
border: none !important;
border-radius: 0 0 8px 8px !important;
box-shadow: none !important;
}
.select__executor { .select__executor {
width: 320px; width: 320px;
background: white; background: white;
@ -229,6 +245,12 @@
.worker { .worker {
display: flex; display: flex;
justify-content: space-between; justify-content: space-between;
&:hover {
p {
font-weight: 500;
}
}
} }
} }
} }

View File

@ -99,6 +99,8 @@
cursor: pointer; cursor: pointer;
img { img {
width: 20px;
height: 20px;
margin-left: 20px; margin-left: 20px;
margin-right: 20px; margin-right: 20px;
} }

View File

@ -12,6 +12,7 @@ import TrackerTaskSubComment from "@components/TrackerTaskComment/TrackerTaskCom
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 accept from "assets/images/accept.png"; import accept from "assets/images/accept.png";
import avatarMok from "assets/images/avatarMok.png";
export const TrackerTaskComment = ({ export const TrackerTaskComment = ({
taskId, taskId,
@ -84,7 +85,14 @@ export const TrackerTaskComment = ({
> >
<div className="comments__list__item__info"> <div className="comments__list__item__info">
<div className="comments__list__item__fio"> <div className="comments__list__item__fio">
<img src={urlForLocal(comment.user.avatar)} alt="avatar" /> <img
src={
comment.user?.avatar
? urlForLocal(comment.user.avatar)
: avatarMok
}
alt="avatar"
/>
<p>{comment.user.fio}</p> <p>{comment.user.fio}</p>
</div> </div>
<div className="comments__list__item__date"> <div className="comments__list__item__date">

View File

@ -6,6 +6,7 @@ import {
activeLoader, activeLoader,
deletePersonOnProject, deletePersonOnProject,
filterCreatedByMe, filterCreatedByMe,
filteredExecutorTasks,
filteredParticipateTasks, filteredParticipateTasks,
getBoarderLoader, getBoarderLoader,
getProjectBoard, getProjectBoard,
@ -20,6 +21,7 @@ import {
} from "@redux/projectsTrackerSlice"; } from "@redux/projectsTrackerSlice";
import { urlForLocal } from "@utils/helper"; import { urlForLocal } from "@utils/helper";
import { caseOfNum } from "@utils/helper";
import { apiRequest } from "@api/request"; import { apiRequest } from "@api/request";
@ -28,13 +30,13 @@ import { Footer } from "@components/Common/Footer/Footer";
import { Loader } from "@components/Common/Loader/Loader"; import { Loader } from "@components/Common/Loader/Loader";
import ModalTicket from "@components/Modal/Tracker/ModalTicket/ModalTicket"; import ModalTicket from "@components/Modal/Tracker/ModalTicket/ModalTicket";
import TrackerModal from "@components/Modal/Tracker/TrackerModal/TrackerModal"; import TrackerModal from "@components/Modal/Tracker/TrackerModal/TrackerModal";
// import TrackerModal from "@components/Modal/TrackerModal/TrackerModal";
import { Navigation } from "@components/Navigation/Navigation"; import { Navigation } from "@components/Navigation/Navigation";
import { ProfileBreadcrumbs } from "@components/ProfileBreadcrumbs/ProfileBreadcrumbs"; import { ProfileBreadcrumbs } from "@components/ProfileBreadcrumbs/ProfileBreadcrumbs";
import { ProfileHeader } from "@components/ProfileHeader/ProfileHeader"; import { ProfileHeader } from "@components/ProfileHeader/ProfileHeader";
import archive from "assets/icons/archiveTracker.svg"; import archive from "assets/icons/archiveTracker.svg";
import arrow from "assets/icons/arrows/arrowCalendar.png"; import arrow from "assets/icons/arrows/arrowCalendar.png";
import arrowDown from "assets/icons/arrows/selectArrow.png";
import close from "assets/icons/close.png"; import close from "assets/icons/close.png";
import commentsBoard from "assets/icons/commentsBoard.svg"; import commentsBoard from "assets/icons/commentsBoard.svg";
import del from "assets/icons/delete.svg"; import del from "assets/icons/delete.svg";
@ -61,6 +63,8 @@ export const ProjectTracker = () => {
const [checkBoxParticipateTasks, setCheckBoxParticipateTasks] = const [checkBoxParticipateTasks, setCheckBoxParticipateTasks] =
useState(false); useState(false);
const [checkBoxMyTasks, setCheckBoxMyTasks] = useState(false); const [checkBoxMyTasks, setCheckBoxMyTasks] = useState(false);
const [selectedExecutor, setSelectedExecutor] = useState(null);
const [selectExecutorOpen, setSelectedExecutorOpen] = useState(false);
const startWrapperIndexTest = useRef({}); const startWrapperIndexTest = useRef({});
const projectBoard = useSelector(getProjectBoard); const projectBoard = useSelector(getProjectBoard);
const loader = useSelector(getBoarderLoader); const loader = useSelector(getBoarderLoader);
@ -228,6 +232,7 @@ export const ProjectTracker = () => {
dispatch(setProjectBoardFetch(projectId.id)); dispatch(setProjectBoardFetch(projectId.id));
setCheckBoxParticipateTasks(false); setCheckBoxParticipateTasks(false);
setCheckBoxMyTasks(false); setCheckBoxMyTasks(false);
setSelectedExecutor(null);
} }
setCheckBoxParticipateTasks(!checkBoxParticipateTasks); setCheckBoxParticipateTasks(!checkBoxParticipateTasks);
} }
@ -239,10 +244,23 @@ export const ProjectTracker = () => {
dispatch(setProjectBoardFetch(projectId.id)); dispatch(setProjectBoardFetch(projectId.id));
setCheckBoxParticipateTasks(false); setCheckBoxParticipateTasks(false);
setCheckBoxMyTasks(false); setCheckBoxMyTasks(false);
setSelectedExecutor(null);
} }
setCheckBoxMyTasks(!checkBoxMyTasks); setCheckBoxMyTasks(!checkBoxMyTasks);
} }
function executorFilter(user) {
dispatch(filteredExecutorTasks(user.user_id));
setSelectedExecutor(user);
}
function deleteSelectedExecutorFilter() {
setSelectedExecutor(null);
setCheckBoxParticipateTasks(false);
setCheckBoxMyTasks(false);
dispatch(setProjectBoardFetch(projectId.id));
}
return ( return (
<div className="tracker"> <div className="tracker">
<ProfileHeader /> <ProfileHeader />
@ -423,6 +441,63 @@ export const ProjectTracker = () => {
{checkBoxMyTasks && <img src={accept} alt="accept" />} {checkBoxMyTasks && <img src={accept} alt="accept" />}
</div> </div>
</div> </div>
{selectedExecutor ? (
<div className="tasks__head__executorSelected">
<p>{selectedExecutor.user.fio}</p>
<img
className="avatar"
src={
selectedExecutor.user?.avatar
? urlForLocal(selectedExecutor.user.avatar)
: avatarMok
}
alt="avatar"
/>
<img
className="delete"
src={close}
alt="delete"
onClick={deleteSelectedExecutorFilter}
/>
</div>
) : (
<div
className="tasks__head__executor"
onClick={() =>
setSelectedExecutorOpen(!selectExecutorOpen)
}
>
<p>Выберите исполнитель</p>
<img
className={selectExecutorOpen ? "open" : ""}
src={arrowDown}
alt="arrow"
/>
{selectExecutorOpen && (
<div className="tasks__head__executorDropdown">
{projectBoard.projectUsers.map((user) => {
return (
<div
className="executorDropdown__person"
key={user.user_id}
onClick={() => executorFilter(user)}
>
<p>{user.user?.fio}</p>
<img
src={
user.user?.avatar
? urlForLocal(user.user.avatar)
: avatarMok
}
alt="avatar"
/>
</div>
);
})}
</div>
)}
</div>
)}
<Link to="/profile/tracker" className="tasks__head__back"> <Link to="/profile/tracker" className="tasks__head__back">
<p>Вернуться на проекты</p> <p>Вернуться на проекты</p>
<img src={arrow} alt="arrow" /> <img src={arrow} alt="arrow" />
@ -563,11 +638,17 @@ export const ProjectTracker = () => {
<div className="tasks__board__item__info"> <div className="tasks__board__item__info">
<div className="tasks__board__item__info__more"> <div className="tasks__board__item__info__more">
<img src={commentsBoard} alt="commentsImg" /> <img src={commentsBoard} alt="commentsImg" />
<span>{task.comment_count} коментариев</span> <span>
{task.comment_count}{" "}
{caseOfNum(task.comment_count, "comments")}
</span>
</div> </div>
<div className="tasks__board__item__info__more"> <div className="tasks__board__item__info__more">
<img src={filesBoard} alt="filesImg" /> <img src={filesBoard} alt="filesImg" />
<span>{task.files} файлов</span> <span>
{task.files ? task.files : 0}{" "}
{caseOfNum(0, "files")}
</span>
</div> </div>
</div> </div>
</div> </div>

View File

@ -27,6 +27,7 @@ import archive from "assets/icons/archiveTracker.svg";
import search from "assets/icons/serchIcon.png"; import search from "assets/icons/serchIcon.png";
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 avatarMok from "assets/images/avatarMok.png";
import noProjects from "assets/images/noProjects.png"; import noProjects from "assets/images/noProjects.png";
import "./tracker.scss"; import "./tracker.scss";
@ -253,11 +254,19 @@ export const Tracker = () => {
<div className="task" key={task.id}> <div className="task" key={task.id}>
<div className="task__info"> <div className="task__info">
<h5>{task.title}</h5> <h5>{task.title}</h5>
<p>{task.description}</p> <p
dangerouslySetInnerHTML={{
__html: task.description,
}}
/>
</div> </div>
<div className="task__person"> <div className="task__person">
<img <img
src={urlForLocal(task.user.avatar)} src={
task.user?.avatar
? urlForLocal(task.user.avatar)
: avatarMok
}
alt="avatar" alt="avatar"
/> />
<div className="task__project"> <div className="task__project">
@ -301,11 +310,20 @@ export const Tracker = () => {
<div className="archive__completeTask" key={index}> <div className="archive__completeTask" key={index}>
<div className="archive__completeTask__description"> <div className="archive__completeTask__description">
<p>{task.title}</p> <p>{task.title}</p>
<p className="date">{task.description}</p> <p
className="date"
dangerouslySetInnerHTML={{
__html: task.description,
}}
/>
</div> </div>
<div className="archive__completeTask__info"> <div className="archive__completeTask__info">
<img <img
src={urlForLocal(task.user.avatar)} src={
task.user?.avatar
? urlForLocal(task.user.avatar)
: avatarMok
}
alt="avatar" alt="avatar"
/> />
<div className="archive__completeTask__info__project"> <div className="archive__completeTask__info__project">

View File

@ -208,7 +208,8 @@
&__wrapper { &__wrapper {
display: flex; display: flex;
max-width: 1160px; max-width: 1260px;
width: 100%;
margin: 0 auto; margin: 0 auto;
justify-content: space-between; justify-content: space-between;
padding: 0 10px; padding: 0 10px;
@ -252,7 +253,6 @@
&__persons { &__persons {
position: relative; position: relative;
display: flex; display: flex;
cursor: pointer;
align-items: center; align-items: center;
.projectPersons { .projectPersons {
@ -302,8 +302,14 @@
background: #00c5a8; background: #00c5a8;
color: white; color: white;
font-size: 14px; font-size: 14px;
transition: all 0.15s ease;
left: -28px; left: -28px;
z-index: 2; z-index: 2;
cursor: pointer;
&:hover {
background: #10d5bb;
}
} }
p { p {
@ -461,11 +467,114 @@
} }
} }
&__executor {
display: flex;
align-items: center;
justify-content: space-between;
cursor: pointer;
margin-right: 10px;
border-radius: 8px;
border: 1px solid #e3e2e2;
padding: 2px 6px;
position: relative;
max-width: 220px;
width: 100%;
&Selected {
display: flex;
align-items: center;
border-radius: 8px;
max-width: 220px;
width: 100%;
margin-right: 10px;
justify-content: center;
p {
color: #252c32;
font-weight: 400;
font-size: 14px;
line-height: 24px;
max-width: 155px;
overflow: hidden;
white-space: nowrap;
text-overflow: ellipsis;
}
.avatar {
margin: 0 5px;
}
.delete {
cursor: pointer;
}
img {
display: flex;
width: 20px;
height: 20px;
}
}
p {
color: #252c32;
font-weight: 400;
font-size: 14px;
line-height: 24px;
}
img {
transition: all 0.15s ease;
margin-left: 5px;
}
.open {
transform: rotate(180deg);
}
&Dropdown {
position: absolute;
top: 33px;
background: white;
border-radius: 8px;
z-index: 5;
padding: 10px 10px;
display: flex;
flex-direction: column;
row-gap: 7px;
width: 100%;
.executorDropdown__person {
display: flex;
justify-content: space-between;
align-items: center;
p {
max-width: 155px;
overflow: hidden;
white-space: nowrap;
text-overflow: ellipsis;
}
img {
width: 15px;
height: 15px;
}
&:hover {
p {
font-weight: 600;
}
}
}
}
}
&__back { &__back {
cursor: pointer; cursor: pointer;
display: flex; display: flex;
align-items: center; align-items: center;
color: black; color: black;
max-width: 180px;
width: 100%;
p { p {
font-weight: 400; font-weight: 400;
@ -477,6 +586,12 @@
margin-left: 10px; margin-left: 10px;
width: 20px; width: 20px;
} }
&:hover {
p {
font-weight: 500;
}
}
} }
} }
@ -738,12 +853,20 @@
font-weight: 500; font-weight: 500;
font-size: 16px; font-size: 16px;
line-height: 24px; line-height: 24px;
cursor: pointer; max-width: 250px;
overflow: hidden;
white-space: nowrap;
text-overflow: ellipsis;
} }
.add { .add {
color: #6f6f6f; color: #6f6f6f;
font-size: 19px; font-size: 19px;
cursor: pointer;
&:hover {
font-weight: 600;
}
} }
.more { .more {
@ -751,6 +874,11 @@
position: relative; position: relative;
bottom: 4px; bottom: 4px;
font-size: 20px; font-size: 20px;
cursor: pointer;
&:hover {
font-weight: 600;
}
} }
.done { .done {

View File

@ -122,6 +122,13 @@ export const projectsTrackerSlice = createSlice({
); );
}); });
}, },
filteredExecutorTasks: (state, action) => {
state.projectBoard.columns.forEach((column) => {
column.tasks = column.tasks.filter(
(task) => task.executor_id === action.payload
);
});
},
setColumnName: (state, action) => { setColumnName: (state, action) => {
state.columnName = action.payload; state.columnName = action.payload;
}, },
@ -174,6 +181,7 @@ export const {
addPersonToProject, addPersonToProject,
filterCreatedByMe, filterCreatedByMe,
filteredParticipateTasks, filteredParticipateTasks,
filteredExecutorTasks,
movePositionProjectTask, movePositionProjectTask,
} = projectsTrackerSlice.actions; } = projectsTrackerSlice.actions;

View File

@ -61,3 +61,23 @@ export function getCorrectRequestDate(date) {
const sec = String(date.getUTCSeconds()); const sec = String(date.getUTCSeconds());
return `${yyyy}-${mm}-${dd} ${hh}:${min}:${sec}`; return `${yyyy}-${mm}-${dd} ${hh}:${min}:${sec}`;
} }
export function caseOfNum(number, type) {
const comments = ["коментарий", "комментария", " коментариев"];
const files = ["файлов", "файла", "файлов"];
const cases = [2, 0, 1, 1, 1, 2];
if (type === "comments") {
return comments[
number % 100 > 4 && number % 100 < 20
? 2
: cases[number % 10 < 5 ? number % 10 : 5]
];
}
if (type === "files") {
return files[
number % 100 > 4 && number % 100 < 20
? 2
: cases[number % 10 < 5 ? number % 10 : 5]
];
}
}