17 Commits

28 changed files with 744 additions and 337 deletions

View File

@ -0,0 +1,3 @@
<svg width="14" height="15" viewBox="0 0 14 15" fill="none" xmlns="http://www.w3.org/2000/svg">
<path d="M6.62109 12.928H2.62109C2.44428 12.928 2.27471 12.8577 2.14969 12.7327C2.02467 12.6077 1.95443 12.4381 1.95443 12.2613V2.92798C1.95443 2.75117 2.02467 2.5816 2.14969 2.45657C2.27471 2.33155 2.44428 2.26131 2.62109 2.26131H5.95443V4.26131C5.95443 4.79174 6.16514 5.30045 6.54021 5.67553C6.91529 6.0506 7.42399 6.26131 7.95443 6.26131H9.95443V9.59465C9.95443 9.77146 10.0247 9.94103 10.1497 10.066C10.2747 10.1911 10.4443 10.2613 10.6211 10.2613C10.7979 10.2613 10.9675 10.1911 11.0925 10.066C11.2175 9.94103 11.2878 9.77146 11.2878 9.59465V5.59465C11.2878 5.59465 11.2878 5.59465 11.2878 5.55465C11.2808 5.4934 11.2674 5.43307 11.2478 5.37465V5.31465C11.2157 5.2461 11.1729 5.18309 11.1211 5.12798L7.12109 1.12798C7.06598 1.07612 7.00297 1.03337 6.93443 1.00131C6.91237 0.997451 6.88982 0.997451 6.86776 1.00131C6.80294 0.966647 6.73327 0.941927 6.66109 0.927979H2.62109C2.09066 0.927979 1.58195 1.13869 1.20688 1.51377C0.831807 1.88884 0.621094 2.39755 0.621094 2.92798V12.2613C0.621094 12.7917 0.831807 13.3005 1.20688 13.6755C1.58195 14.0506 2.09066 14.2613 2.62109 14.2613H6.62109C6.7979 14.2613 6.96747 14.1911 7.0925 14.0661C7.21752 13.941 7.28776 13.7715 7.28776 13.5946C7.28776 13.4178 7.21752 13.2483 7.0925 13.1232C6.96747 12.9982 6.7979 12.928 6.62109 12.928ZM7.28776 3.20131L9.01443 4.92798H7.95443C7.77762 4.92798 7.60805 4.85774 7.48302 4.73272C7.358 4.60769 7.28776 4.43812 7.28776 4.26131V3.20131ZM3.95443 8.92798H7.95443C8.13124 8.92798 8.30081 8.85774 8.42583 8.73272C8.55086 8.60769 8.62109 8.43812 8.62109 8.26131C8.62109 8.0845 8.55086 7.91493 8.42583 7.78991C8.30081 7.66488 8.13124 7.59465 7.95443 7.59465H3.95443C3.77762 7.59465 3.60805 7.66488 3.48302 7.78991C3.358 7.91493 3.28776 8.0845 3.28776 8.26131C3.28776 8.43812 3.358 8.60769 3.48302 8.73272C3.60805 8.85774 3.77762 8.92798 3.95443 8.92798ZM6.62109 10.2613H3.95443C3.77762 10.2613 3.60805 10.3315 3.48302 10.4566C3.358 10.5816 3.28776 10.7512 3.28776 10.928C3.28776 11.1048 3.358 11.2744 3.48302 11.3994C3.60805 11.5244 3.77762 11.5946 3.95443 11.5946H6.62109C6.7979 11.5946 6.96747 11.5244 7.0925 11.3994C7.21752 11.2744 7.28776 11.1048 7.28776 10.928C7.28776 10.7512 7.21752 10.5816 7.0925 10.4566C6.96747 10.3315 6.7979 10.2613 6.62109 10.2613ZM3.95443 6.26131H4.62109C4.7979 6.26131 4.96747 6.19107 5.0925 6.06605C5.21752 5.94103 5.28776 5.77146 5.28776 5.59465C5.28776 5.41783 5.21752 5.24827 5.0925 5.12324C4.96747 4.99822 4.7979 4.92798 4.62109 4.92798H3.95443C3.77762 4.92798 3.60805 4.99822 3.48302 5.12324C3.358 5.24827 3.28776 5.41783 3.28776 5.59465C3.28776 5.77146 3.358 5.94103 3.48302 6.06605C3.60805 6.19107 3.77762 6.26131 3.95443 6.26131ZM13.0944 10.4546C13.0325 10.3922 12.9587 10.3426 12.8775 10.3087C12.7962 10.2749 12.7091 10.2574 12.6211 10.2574C12.5331 10.2574 12.4459 10.2749 12.3647 10.3087C12.2835 10.3426 12.2097 10.3922 12.1478 10.4546L9.95443 12.6546L9.09443 11.788C9.03227 11.7258 8.95848 11.6765 8.87726 11.6429C8.79605 11.6092 8.709 11.5919 8.62109 11.5919C8.53319 11.5919 8.44614 11.6092 8.36493 11.6429C8.28371 11.6765 8.20992 11.7258 8.14776 11.788C8.0856 11.8501 8.03629 11.9239 8.00265 12.0051C7.96901 12.0864 7.9517 12.1734 7.9517 12.2613C7.9517 12.3492 7.96901 12.4363 8.00265 12.5175C8.03629 12.5987 8.0856 12.6725 8.14776 12.7346L9.48109 14.068C9.54307 14.1305 9.6168 14.1801 9.69804 14.2139C9.77928 14.2478 9.86642 14.2652 9.95443 14.2652C10.0424 14.2652 10.1296 14.2478 10.2108 14.2139C10.2921 14.1801 10.3658 14.1305 10.4278 14.068L13.0944 11.4013C13.1569 11.3393 13.2065 11.2656 13.2404 11.1844C13.2742 11.1031 13.2916 11.016 13.2916 10.928C13.2916 10.84 13.2742 10.7528 13.2404 10.6716C13.2065 10.5904 13.1569 10.5166 13.0944 10.4546Z" fill="#52B709"/>
</svg>

After

Width:  |  Height:  |  Size: 3.7 KiB

File diff suppressed because one or more lines are too long

After

Width:  |  Height:  |  Size: 1.1 MiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 3.2 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 2.7 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 4.7 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 3.8 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 42 KiB

View File

@ -24,19 +24,35 @@ export const Footer = () => {
<div className="footer__bottom">
<div className="footer__social">
<div className="footer__social__icons">
<a href="https://www.vk.com/">
<a
href="https://www.vk.com/"
target="_blank"
rel="noopener noreferrer"
>
<img src={vk} alt="vk" width={24} />
</a>
<a href="https://www.telegram.org/">
<a
href="https://www.telegram.org/"
target="_blank"
rel="noopener noreferrer"
>
<img src={tg} alt="tg" width={24} />
</a>
</div>
<a href="mailto:office@itguild.info">office@itguild.info</a>
<a
href="mailto:office@itguild.info"
target="_blank"
rel="noopener noreferrer"
>
office@itguild.info
</a>
</div>
<div className="footer__info">
<div className="footer__mail">
{/* <img src={email} alt="email" /> */}
<a href="#">Присоединиться к команде</a>
<a href="#" target="_blank" rel="noopener noreferrer">
Присоединиться к команде
</a>
</div>
<p>
© {new Date().getFullYear()} - Outstaffing. Все права защищены

View File

@ -0,0 +1,22 @@
import React from "react";
import { Link } from "react-router-dom";
import empty from "assets/images/emptyPage.svg";
import "./emptyBlock.scss";
export const EmptyBlock = () => {
return (
<>
<div className="empty-block">
<h4 className="empty-block__title">
Данная страница находится в разработке
</h4>
<Link className="empty-block__back" to="/profile">
На главную
</Link>
</div>
<img src={empty} alt="img" className="empty-block__img" />
</>
);
};

View File

@ -0,0 +1,53 @@
.empty-block {
position: relative;
border-radius: 8px;
border: 0.5px solid;
border-image-source: linear-gradient(137.79deg, #FFFFFF 9.15%, #F4F4F4 76.22%);
background: linear-gradient(110.06deg, rgba(255, 255, 255, 0.34) 0%, rgba(239, 239, 239, 0.34) 99.25%);
display: flex;
box-shadow: 10px 9px 14.3px 0px #00000008;
align-items: center;
justify-content: center;
margin-top: 22px;
padding: 95px 0 40px;
flex-direction: column;
z-index: 2;
backdrop-filter: blur(5px);
&__img {
position: absolute;
z-index: 1;
top: 30px;
transform: translate(-50%);
left: 50%;
}
&__title {
color: #000000;
font-weight: 700;
font-size: 22px;
line-height: 32px;
margin-bottom: 0;
}
&__back {
max-width: 150px;
width: 100%;
background: #52B709;
border-radius: 44px;
color: #FFFFFF;
font-size: 14px;
font-weight: 700;
margin-top: 16px;
display: flex;
align-items: center;
justify-content: center;
height: 40px;
transition: all 0.3s ease;
&:hover {
color: white;
scale: 1.1;
}
}
}

View File

@ -122,15 +122,6 @@ export const ModalTiсket = ({
setShowModalToReport(!showModalToReport);
};
const closeModal = () => {
setActive(false);
const currentUrl = window.location.pathname;
const newUrl = currentUrl.replace(/\/task\/\d+$/, "");
window.history.replaceState({}, "", newUrl);
document.body.style.overflow = "auto";
console.log(task);
};
const [isExpanded, setIsExpanded] = useState(false);
const toggleModalSize = () => {
@ -310,6 +301,17 @@ export const ModalTiсket = ({
});
}
const closeModal = () => {
if (timerStart) {
stopTaskTimer();
}
setActive(false);
const currentUrl = window.location.pathname;
const newUrl = currentUrl.replace(/\/task\/\d+$/, "");
window.history.replaceState({}, "", newUrl);
document.body.style.overflow = "auto";
};
function taskExecutor(person) {
apiRequest("/task/update-task", {
method: "PUT",
@ -380,82 +382,111 @@ export const ModalTiсket = ({
}
useEffect(() => {
initListeners();
apiRequest(
`/comment/get-by-entity?entity_type=2&entity_id=${task.id}`
).then((res) => {
const comments = res.reduce((acc, cur) => {
if (!cur.parent_id) {
acc.push({ ...cur, subComments: [] });
} else {
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) => {
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
if (active) {
setStartDate(task.dead_line ? new Date(task.dead_line) : new Date());
setTaskPriority(task.execution_priority);
setMembers(task.taskUsers);
setTaskTags(task.mark);
setExecutorId(task.executor_id);
setDeadLine(task.dead_line);
setExecutor(task.executor);
setInputsValue({
title: task.title,
description: task.description,
comment: ""
});
initListeners();
apiRequest(
`/comment/get-by-entity?entity_type=2&entity_id=${task.id}`
).then((res) => {
const comments = res.reduce((acc, cur) => {
if (!cur.parent_id) {
acc.push({ ...cur, subComments: [] });
} else {
acc.forEach((item) => {
if (item.id === cur.parent_id) item.subComments.push(cur);
});
updateTimerHours = Math.floor(timerSeconds / 60 / 60);
updateTimerMinute = Math.floor((timerSeconds / 60) % 60);
updateTimerSec = timerSeconds % 60;
if (!time.stopped_at) {
setTimerStart(true);
startTimer();
setTimerInfo(time);
}
});
}
);
}
return acc;
}, []);
setComments(comments);
});
apiRequest(`/file/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) => {
if (Array.isArray(res)) {
setTaskFiles(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 (
localStorage.getItem("role_status") !== "18" &&
Boolean(
if (!time.stopped_at) {
setTimerStart(true);
startTimer();
setTimerInfo(time);
}
});
} else {
setCurrentTimerCount({
hours: 0,
minute: 0,
seconds: 0
});
}
});
apiRequest(`/file/get-by-entity?entity_type=2&entity_id=${task.id}`).then(
(res) => {
if (Array.isArray(res)) {
setTaskFiles(res);
} else {
setTaskFiles([]);
}
}
);
if (
localStorage.getItem("role_status") !== "18" &&
Array.isArray(correctProjectUsers) &&
!correctProjectUsers.find(
(item) => item.user_id === profileInfo.id_user
)
)
) {
setCorrectProjectUsers((prevState) => [
...prevState,
{
user: {
avatar: profileInfo.photo,
fio: profileInfo.fio
},
user_id: profileInfo.id_user
}
]);
) {
setCorrectProjectUsers((prevState) => [
...prevState,
{
user: {
avatar: profileInfo.photo,
fio: profileInfo.fio
},
user_id: profileInfo.id_user
}
]);
}
}
}, []);
}, [active]);
useEffect(() => {
let tagIds = taskTags.map((tag) => tag.id);
setCorrectProjectTags(
projectMarks.reduce((acc, cur) => {
if (!tagIds.includes(cur.id)) acc.push(cur);
return acc;
}, [])
);
if (Array.isArray(taskTags)) {
const tagIds = taskTags.map((tag) => tag.id);
setCorrectProjectTags(
projectMarks.reduce((acc, cur) => {
if (!tagIds.includes(cur.id)) acc.push(cur);
return acc;
}, [])
);
}
}, [taskTags]);
async function handleUpload(event) {
@ -534,13 +565,15 @@ export const ModalTiсket = ({
}
useEffect(() => {
let ids = members.map((user) => user.user_id);
setUsers(
projectUsers.reduce((acc, cur) => {
if (!ids.includes(cur.user_id)) acc.push(cur);
return acc;
}, [])
);
if (Array.isArray(members)) {
const ids = members.map((user) => user.user_id);
setUsers(
projectUsers.reduce((acc, cur) => {
if (!ids.includes(cur.user_id)) acc.push(cur);
return acc;
}, [])
);
}
}, [members]);
function copyTicketLink() {
@ -684,6 +717,7 @@ export const ModalTiсket = ({
editor={ClassicEditor}
data={inputsValue.description}
config={{
toolbar: ["link"],
removePlugins: [
"CKFinderUploadAdapter",
"CKFinder",
@ -695,7 +729,10 @@ export const ModalTiсket = ({
"ImageUpload",
"MediaEmbed",
"BlockQuote"
]
],
link: {
addTargetToExternalLinks: true
}
}}
onChange={(event, editor) => {
const data = editor.getData();
@ -713,7 +750,7 @@ export const ModalTiсket = ({
)}
{/*<img src={taskImg} className="image-task"></img>*/}
</div>
{Boolean(taskFiles.length) && (
{Boolean(taskFiles?.length) && (
<div className="task__files">
{taskFiles.map((file) => {
return (
@ -873,7 +910,7 @@ export const ModalTiсket = ({
)}
</div>
)}
{Boolean(members.length) && (
{Boolean(members?.length) && (
<div className="members">
<h5>Участники:</h5>
<div className="members__list">
@ -1005,23 +1042,24 @@ export const ModalTiсket = ({
<div className="workers_box-tag">
<div className="tags">
<div className="tags__selected">
{taskTags.map((tag) => {
return (
<div
className="tags__selected__item"
key={tag.id}
style={{ background: tag.color }}
>
<p>{tag.slug}</p>
<img
src={crossWhite}
className="delete"
alt="delete"
onClick={() => deleteTagFromTask(tag.id)}
/>
</div>
);
})}
{Array.isArray(taskTags) &&
taskTags.map((tag) => {
return (
<div
className="tags__selected__item"
key={tag.id}
style={{ background: tag.color }}
>
<p>{tag.slug}</p>
<img
src={crossWhite}
className="delete"
alt="delete"
onClick={() => deleteTagFromTask(tag.id)}
/>
</div>
);
})}
</div>
<div
className="tags__select"

View File

@ -625,7 +625,10 @@ export const TrackerModal = ({
"numberedList"
],
removePlugins: ["BlockQuote"],
placeholder: "Описание задачи"
placeholder: "Описание задачи",
link: {
addTargetToExternalLinks: true
}
}}
onChange={(event, editor) => {
const data = editor.getData();

View File

@ -62,10 +62,14 @@ export const SideBar = () => {
<Link to={"/forms"}>Формы</Link>
</li>
<li>
<a href="#">Школа</a>
<a href="#" target="_blank" rel="noopener noreferrer">
Школа
</a>
</li>
<li>
<a href="#">Контакты</a>
<a href="#" target="_blank" rel="noopener noreferrer">
Контакты
</a>
</li>
<li>
<Link to={"/blog"}>Блог</Link>

View File

@ -1,4 +1,4 @@
import React, { useEffect, useState } from "react";
import React, { forwardRef, useEffect, useState } from "react";
import { useDispatch } from "react-redux";
import { movePositionProjectTask } from "@redux/projectsTrackerSlice";
@ -14,173 +14,181 @@ import avatarMok from "assets/images/avatarMok.webp";
import "./trackerCardTask.scss";
const TrackerCardTask = ({
task,
projectBoard,
titleColor,
column,
openTicket,
startWrapperIndexTest,
setWrapperHover
}) => {
const dispatch = useDispatch();
const [taskHover, setTaskHover] = useState({});
const TrackerCardTask = forwardRef(
(
{
task,
projectBoard,
titleColor,
column,
openTicket,
startWrapperIndexTest,
setWrapperHover
},
ref
) => {
const dispatch = useDispatch();
const [taskHover, setTaskHover] = useState({});
const priority = {
2: "Высокий",
1: "Средний",
0: "Низкий"
};
const priority = {
2: "Высокий",
1: "Средний",
0: "Низкий"
};
const priorityClass = {
2: "high",
1: "middle",
0: "low"
};
const priorityClass = {
2: "high",
1: "middle",
0: "low"
};
function dragDropTaskHandler(e, task, column) {
e.preventDefault();
if (task.id === startWrapperIndexTest.current.task.id) {
return;
function dragDropTaskHandler(e, task, column) {
e.preventDefault();
if (task.id === startWrapperIndexTest.current.task.id) {
return;
}
const finishTask = column.tasks.indexOf(task);
dispatch(
movePositionProjectTask({
startTask: startWrapperIndexTest.current.task,
finishTask: task,
finishIndex: finishTask
})
);
}
const finishTask = column.tasks.indexOf(task);
function dragOverTaskHandler(e, task) {
e.preventDefault();
if (startWrapperIndexTest.current.task.id === task.id) {
return;
}
setTaskHover((prevState) => ({ [prevState]: false, [task.id]: true }));
}
dispatch(
movePositionProjectTask({
startTask: startWrapperIndexTest.current.task,
finishTask: task,
finishIndex: finishTask
})
function dragStartHandler(e, task, columnId) {
startWrapperIndexTest.current = { task: task, index: columnId };
}
function dragLeaveTaskHandler() {
setTaskHover((prevState) => ({ [prevState]: false }));
}
function dragEndTaskHandler() {
setTaskHover((prevState) => ({ [prevState]: false }));
setWrapperHover((prevState) => ({
[prevState]: false
}));
}
useEffect(() => {
const tasksHover = {};
const columnHover = {};
if (Object.keys(projectBoard).length) {
projectBoard.columns.forEach((column) => {
columnHover[column.id] = false;
column.tasks.forEach((task) => (tasksHover[task.id] = false));
});
}
setWrapperHover(columnHover);
setTaskHover(tasksHover);
}, [projectBoard]);
return (
<div
ref={ref}
key={task.id}
className={`tasks__board__item ${
taskHover[task.id] ? "task__hover" : ""
}`}
draggable={true}
onDragStart={(e) => dragStartHandler(e, task, column.id)}
onDragOver={(e) => dragOverTaskHandler(e, task)}
onDragLeave={(e) => dragLeaveTaskHandler(e)}
onDragEnd={() => dragEndTaskHandler()}
onDrop={(e) => dragDropTaskHandler(e, task, column)}
onClick={() => openTicket(task)}
>
<div className="tasks__board__item__title">
<p className="task__board__item__title">{task.title}</p>
</div>
<p
dangerouslySetInnerHTML={{
__html: task.description
}}
className="tasks__board__item__description"
></p>
{Boolean(task.mark.length) && (
<div className="tasks__board__item__tags">
{task.mark.map((tag) => {
return (
<div
className="tag-item"
key={tag.id}
style={{ background: tag.color }}
>
<p>{tag.slug}</p>
</div>
);
})}
</div>
)}
<div className="tasks__board__item__container">
{typeof task.execution_priority === "number" && (
<div className="tasks__board__item__priority">
<p></p>
<span className={priorityClass[task.execution_priority]}>
{priority[task.execution_priority]}
</span>
</div>
)}
{task.dead_line && (
<div className="tasks__board__item__dead-line">
<p></p>
<span style={{ color: titleColor }}>
{getCorrectDate(task.dead_line)}
</span>
</div>
)}
</div>
<div className="tasks__board__item__info">
<div className="tasks__board__item__executor">
<img
src={
task.executor?.avatar
? urlForLocal(task.executor?.avatar)
: avatarMok
}
alt="avatar"
/>
<span>
{removeLast(task.executor?.fio) || "Исполнитель не назначен"}
</span>
</div>
<div className="tasks__board__item__info__tags">
<div className="tasks__board__item__info__more">
<img src={commentsBoard} alt="commentsImg" />
<span>{task.comment_count}</span>
</div>
<div className="tasks__board__item__info__more">
<img src={filesBoard} alt="filesImg" />
<span>{task.file_count}</span>
</div>
</div>
</div>
<TrackerSelectColumn
columns={projectBoard.columns.filter((item) => item.id !== column.id)}
currentColumn={column}
task={task}
/>
</div>
);
}
);
function dragOverTaskHandler(e, task) {
e.preventDefault();
if (startWrapperIndexTest.current.task.id === task.id) {
return;
}
setTaskHover((prevState) => ({ [prevState]: false, [task.id]: true }));
}
function dragStartHandler(e, task, columnId) {
startWrapperIndexTest.current = { task: task, index: columnId };
}
function dragLeaveTaskHandler() {
setTaskHover((prevState) => ({ [prevState]: false }));
}
function dragEndTaskHandler() {
setTaskHover((prevState) => ({ [prevState]: false }));
setWrapperHover((prevState) => ({
[prevState]: false
}));
}
useEffect(() => {
const tasksHover = {};
const columnHover = {};
if (Object.keys(projectBoard).length) {
projectBoard.columns.forEach((column) => {
columnHover[column.id] = false;
column.tasks.forEach((task) => (tasksHover[task.id] = false));
});
}
setWrapperHover(columnHover);
setTaskHover(tasksHover);
}, [projectBoard]);
return (
<div
key={task.id}
className={`tasks__board__item ${
taskHover[task.id] ? "task__hover" : ""
}`}
draggable={true}
onDragStart={(e) => dragStartHandler(e, task, column.id)}
onDragOver={(e) => dragOverTaskHandler(e, task)}
onDragLeave={(e) => dragLeaveTaskHandler(e)}
onDragEnd={() => dragEndTaskHandler()}
onDrop={(e) => dragDropTaskHandler(e, task, column)}
onClick={(e) => openTicket(e, task)}
>
<div className="tasks__board__item__title">
<p className="task__board__item__title">{task.title}</p>
</div>
<p
dangerouslySetInnerHTML={{
__html: task.description
}}
className="tasks__board__item__description"
></p>
{Boolean(task.mark.length) && (
<div className="tasks__board__item__tags">
{task.mark.map((tag) => {
return (
<div
className="tag-item"
key={tag.id}
style={{ background: tag.color }}
>
<p>{tag.slug}</p>
</div>
);
})}
</div>
)}
<div className="tasks__board__item__container">
{typeof task.execution_priority === "number" && (
<div className="tasks__board__item__priority">
<p></p>
<span className={priorityClass[task.execution_priority]}>
{priority[task.execution_priority]}
</span>
</div>
)}
{task.dead_line && (
<div className="tasks__board__item__dead-line">
<p></p>
<span style={{ color: titleColor }}>
{getCorrectDate(task.dead_line)}
</span>
</div>
)}
</div>
<div className="tasks__board__item__info">
<div className="tasks__board__item__executor">
<img
src={
task.executor?.avatar
? urlForLocal(task.executor?.avatar)
: avatarMok
}
alt="avatar"
/>
<span>
{removeLast(task.executor?.fio) || "Исполнитель не назначен"}
</span>
</div>
<div className="tasks__board__item__info__tags">
<div className="tasks__board__item__info__more">
<img src={commentsBoard} alt="commentsImg" />
<span>{task.comment_count}</span>
</div>
<div className="tasks__board__item__info__more">
<img src={filesBoard} alt="filesImg" />
<span>{task.file_count}</span>
</div>
</div>
</div>
<TrackerSelectColumn
columns={projectBoard.columns.filter((item) => item.id !== column.id)}
currentColumn={column}
task={task}
/>
</div>
);
};
TrackerCardTask.displayName = "TrackerCardTask";
export default TrackerCardTask;

View File

@ -121,6 +121,7 @@ export const TrackerTaskComment = ({
editor={ClassicEditor}
data={commentsEditText}
config={{
toolbar: ["link"],
removePlugins: [
"CKFinderUploadAdapter",
"CKFinder",
@ -132,7 +133,10 @@ export const TrackerTaskComment = ({
"ImageUpload",
"MediaEmbed",
"BlockQuote"
]
],
link: {
addTargetToExternalLinks: true
}
}}
onChange={(event, editor) => {
const data = editor.getData();

View File

@ -11,7 +11,7 @@ export const VacancyTab = ({ title, active, count, setActive }) => {
onClick={setActive}
>
<p className="vacancy__tab__title">{title}</p>
<span className="vacancy__tabtab__count">{count}</span>
<span className="vacancy__tab__count">{count}</span>
</div>
);
};

View File

@ -90,7 +90,11 @@ export const FormPage = () => {
Заявка на собеседование через телеграм
</div>
<div className="form-page__telegram-icon">
<a href="https://t.me/st0kir" target="_blank" rel="noreferrer">
<a
href="https://t.me/st0kir"
target="_blank"
rel="noopener noreferrer"
>
<SVG src={telegramIcon} />
</a>
</div>

View File

@ -13,6 +13,10 @@ import clue from "assets/icons/landingClue.svg";
import tracker from "assets/icons/landingTracker.svg";
import codeBg from "assets/images/landing/backgroundCode.webp";
import cat from "assets/images/landing/landingCat.webp";
import reportingSystem from "assets/images/landing/reportingSystem.webp";
import searchIT from "assets/images/landing/searchIT.webp";
import systemControlGit from "assets/images/landing/systemControlGit.webp";
import taskManagement from "assets/images/landing/taskManagement.webp";
import "./landing.scss";
@ -29,22 +33,22 @@ export const Landing = () => {
{
name: "<span>Найти</span> работу <br/> в IT",
path: "/stack",
img: cat
img: searchIT
},
{
name: "<span>Система</span> контроля версий GIT",
path: "/stack",
img: cat
img: systemControlGit
},
{
name: "<span>Управление</span> задачами",
path: "/landing-tracker",
img: cat
img: taskManagement
},
{
name: "<span>Система</span> для отчётности",
path: "/stack",
img: cat
img: reportingSystem
},
{
name: "Все наши предложения",

View File

@ -12,6 +12,7 @@ import ellipseGreen from "assets/images/landingTracker/ellipseGreen.svg";
import cat from "assets/images/landingTracker/landingCat.webp";
import flag from "assets/images/landingTracker/projectsFlag.webp";
import questionMark from "assets/images/landingTracker/questionMark.svg";
import target from "assets/images/landingTracker/target.webp";
import trackerCup from "assets/images/landingTracker/trackerCup.webp";
import trackerPreview from "assets/images/landingTracker/trackerPreview.webp";
import trackerSign from "assets/images/landingTracker/trackerSign.webp";
@ -173,9 +174,9 @@ export const LandingTracker = () => {
);
})}
</div>
{/* <div className="steps__portfolio">
<img src={portfolio} alt="portfolio" />
</div> */}
<div className="goals__target">
<img src={target} alt="target" />
</div>
</div>
</section>

View File

@ -446,6 +446,11 @@
}
}
}
&__target {
position: absolute;
bottom: 0;
}
}
}

View File

@ -6,6 +6,7 @@ import { useTheme } from "@table-library/react-table-library/theme";
import React, { useEffect, useState } from "react";
import { Link, Navigate } from "react-router-dom";
import { LEVELS, SKILLS } from "@utils/constants";
import { urlForLocal } from "@utils/helper";
import { apiRequest } from "@api/request";
@ -17,6 +18,7 @@ import { ProfileBreadcrumbs } from "@components/ProfileBreadcrumbs/ProfileBreadc
import { ProfileHeader } from "@components/ProfileHeader/ProfileHeader";
import rightArrow from "assets/icons/arrows/arrowRight.svg";
import report from "assets/icons/report.svg";
// import PartnerPersonCard from "@components/PartnerPersonCard/PartnerPersonCard";
// import { useDispatch } from "react-redux";
@ -44,9 +46,21 @@ export const PartnerCategories = () => {
const theme = useTheme(getTheme());
const [nodes, setNodes] = useState([]);
const [initialNodes, setInitialNodes] = useState([]);
const [activeTab, setActiveTab] = useState("Все");
const [search, setSearch] = useState("");
const tabs = [
{
name: "Фронтенд",
value: 2
},
{
name: "Бэкенд",
value: 1
}
];
const COLUMNS = [
{
label: "",
@ -59,19 +73,28 @@ export const PartnerCategories = () => {
)
},
{
label: "ФИО",
renderCell: (item) => <p>{item?.employee.fio}</p>,
sort: { sortKey: "NAME" }
label: "Данные",
renderCell: (item) => (
<div className="table__info">
<p>{item?.employee.fio}</p>
<span>
{LEVELS[item?.resume.userCard.level]} /{" "}
{SKILLS[item?.resume.userCard.position_id]}
</span>
</div>
)
// sort: { sortKey: "NAME" }
},
{
label: "Резюме",
label: "Участвует в проекте",
renderCell: (item) => (
<Link className="table__link" to={`/candidate/${item.user_id}`}>
Резюме
<div className="img__wrapper">
<img src={rightArrow} alt="arrow" />
</div>
</Link>
<div className="table__project">
{item.resume.userCard.at_project ? (
<div>item.resume.userCard.at_project</div>
) : (
<span>Нет проектов</span>
)}
</div>
)
},
{
@ -81,12 +104,17 @@ export const PartnerCategories = () => {
className="table__link"
to={`/profile/employees/report/${item.user_id}`}
>
<img src={report} alt="report" />
Подробный отчет
<div className="img__wrapper">
<img src={rightArrow} alt="arrow" />
</div>
</Link>
)
},
{
label: <span className="table__action">Действие</span>,
renderCell: () => <div className="table__more"></div>
}
];
@ -115,9 +143,23 @@ export const PartnerCategories = () => {
useEffect(() => {
setLoader(true);
apiRequest("/project/my-employee").then((el) => {
setLoader(false);
setNodes(el?.managerEmployees);
setInitialNodes(el.managerEmployees);
const fetchResumes = async () => {
const promises = el.managerEmployees.map(async (manager) => {
const resume = await apiRequest(`/resume?userId=${manager.user_id}`);
return { ...manager, resume }; // Возвращаем объект с добавленным ключом resume
});
try {
const updatedManagers = await Promise.all(promises);
setInitialNodes(updatedManagers);
setNodes(updatedManagers);
setLoader(false);
} catch (error) {
console.error("Ошибка при получении резюме:", error);
}
};
fetchResumes();
});
}, []);
@ -266,6 +308,44 @@ export const PartnerCategories = () => {
onChange={handleSearch}
/>
</label>
<div className="table__tabs">
<div
onClick={() => {
setActiveTab("Все");
setNodes(initialNodes);
}}
className={
activeTab === "Все"
? "table__tab table__tab--active"
: "table__tab"
}
>
Все
</div>
{tabs.map((tab) => {
return (
<div
onClick={() => {
setActiveTab(tab.name);
setNodes(
initialNodes.filter(
(item) =>
item.resume.userCard.position_id === tab.value
)
);
}}
className={
activeTab === tab.name
? "table__tab table__tab--active"
: "table__tab"
}
key={tab.value}
>
{tab.name}
</div>
);
})}
</div>
<CompactTable
columns={COLUMNS}
data={data}

View File

@ -178,10 +178,56 @@
}
}
&__tabs {
display: flex;
margin: 0 auto 30px 18px;
padding: 4px 8px;
gap: 10px;
align-items: center;
font-size: 16px;
background: rgba(240, 242, 245, 1);
border-radius: 5px;
}
&__tab {
padding: 8px 12px;
cursor: pointer;
color: rgba(46, 58, 89, 1);
font-size: 15px;
&--active {
background: rgba(255, 255, 255, 1);
font-size: 16px;
border-radius: 5px;
border: 0;
}
&:nth-child(2) {
border-right: 1px solid rgba(224, 226, 229, 1);
border-left: 1px solid rgba(224, 226, 229, 1);
}
}
&__avatar {
max-width: 45px;
width: 40px;
height: 40px;
margin-left: 18px;
}
&__info {
p {
color: rgba(46, 58, 89, 1);
font-size: 14px;
font-weight: 500;
line-height: 17.5px;
}
span {
color: rgba(155, 171, 197, 1);
font-size: 14px;
line-height: 17.5px;
}
}
&__link {
display: flex;
column-gap: 10px;
@ -199,11 +245,56 @@
justify-content: center;
img {
width: 14px;
width: 8px;
}
}
}
&__project {
color: rgba(46, 58, 89, 1);
font-size: 14px;
font-weight: 500;
line-height: 17.5px;
}
&__action {
color: rgba(155, 171, 197, 1);
font-size: 14px;
font-weight: 400;
}
&__more {
position: relative;
height: 30px;
cursor: pointer;
width: 10px;
display: flex;
align-items: center;
justify-content: space-evenly;
flex-direction: column;
&:before {
content: '';
display: flex;
top: 2px;
width: 4px;
height: 4px;
background: rgba(155, 171, 197, 1);
border-radius: 50px;
position: relative;
}
&:after {
content: '';
display: flex;
bottom: 2px;
width: 4px;
height: 4px;
background: rgba(155, 171, 197, 1);
border-radius: 50px;
position: relative;
}
}
&__pagination {
width: 100%;
display: flex;
@ -251,6 +342,7 @@
}
table {
--data-table-library_grid-template-columns: minmax(0px, 0.5fr) minmax(0px, 1.5fr) minmax(0px, 1fr) minmax(0px, 1fr) minmax(0px, 1fr) !important;
th {
border-top: 0;
border-bottom: 1px solid #EDEDED;

View File

@ -1,7 +1,9 @@
import React from "react";
import { Footer } from "@components/Common/Footer/Footer";
import { EmptyBlock } from "@components/EmptyBlock/EmptyBlock";
import { Navigation } from "@components/Navigation/Navigation";
import { ProfileBreadcrumbs } from "@components/ProfileBreadcrumbs/ProfileBreadcrumbs";
import { ProfileHeader } from "@components/ProfileHeader/ProfileHeader";
import "./payouts.scss";
@ -11,7 +13,16 @@ export const Payouts = () => {
<div className="payouts">
<ProfileHeader />
<Navigation />
<div className="container"></div>
<div className="container">
<ProfileBreadcrumbs
links={[
{ name: "Главная", link: "/profile" },
{ name: "Выплаты и финансы ", link: "/profile/payouts" }
]}
/>
<h3 className="payouts__title">Выплаты и финансы </h3>
<EmptyBlock />
</div>
<Footer />
</div>
);

View File

@ -9,5 +9,13 @@
display: flex;
flex-direction: column;
flex: 1;
margin-top: 23px;
}
&__title {
font-weight: 700;
font-size: 22px;
line-height: 32px;
color: #000000;
}
}

View File

@ -1,7 +1,7 @@
import moment from "moment";
import React, { useEffect, useRef, useState } from "react";
import { useDispatch, useSelector } from "react-redux";
import { Link, useParams } from "react-router-dom";
import { Link, useLocation, useParams } from "react-router-dom";
import {
activeLoader,
@ -51,6 +51,9 @@ import avatarMok from "assets/images/avatarMok.webp";
export const ProjectTracker = () => {
const dispatch = useDispatch();
const projectId = useParams();
const taskParams = useParams();
const taskRefs = useRef([]);
const hasRunEffect = useRef(false);
const [openColumnSelect, setOpenColumnSelect] = useState({});
const [selectedTab, setSelectedTab] = useState(0);
@ -78,6 +81,19 @@ export const ProjectTracker = () => {
initListeners();
}, []);
useEffect(() => {
if (projectBoard.columns && taskParams.taskId && !hasRunEffect.current) {
for (const column of projectBoard.columns) {
const task = column.tasks.find((task) => task.id == taskParams.taskId);
if (task) {
openTicket(task);
hasRunEffect.current = true;
return;
}
}
}
}, [projectBoard]);
useEffect(() => {
let columnsTasksEmpty = true;
if (Object.keys(projectBoard).length) {
@ -145,21 +161,32 @@ export const ProjectTracker = () => {
setPriorityTask(length);
}
function openTicket(e, task) {
const updateUrlWithTaskId = (taskId) => {
const currentUrl = window.location.pathname;
const taskUrlSegment = `/task/`;
if (currentUrl.includes(taskUrlSegment)) {
// Если URL содержит '/task/', заменяем старый ID на новый
const baseUrl = currentUrl.substring(
0,
currentUrl.indexOf(taskUrlSegment) + taskUrlSegment.length
);
const newUrl = `${baseUrl}${taskId}`;
window.history.pushState({}, "", newUrl);
} else {
// Если URL не содержит '/task/', добавляем '/task/${taskId}'
const newUrl = `${currentUrl}${taskUrlSegment}${taskId}`;
window.history.pushState({}, "", newUrl);
}
};
function openTicket(task) {
setSelectedTicket(task);
setModalActiveTicket(true);
const currentUrl = window.location.pathname;
const newUrl = `${currentUrl}/task/${task.id}`;
window.history.pushState({}, "", newUrl);
updateUrlWithTaskId(task.id);
document.body.style.overflow = "hidden";
}
useEffect(() => {
const currentUrl = window.location.pathname;
const newUrl = currentUrl.replace(/\/task\/\d+$/, "");
window.history.replaceState({}, "", newUrl);
}, []);
function deleteColumn(column) {
const priorityColumns = [];
apiRequest("/project-column/update-column", {
@ -421,19 +448,16 @@ export const ProjectTracker = () => {
</Link>
</div>
</div>
{Boolean(modalActiveTicket) && (
<ModalTicket
active={modalActiveTicket}
setActive={setModalActiveTicket}
task={selectedTicket}
projectId={projectBoard.id}
projectName={projectBoard.name}
projectUsers={projectBoard.projectUsers}
projectOwnerId={projectBoard.owner_id}
projectMarks={projectBoard.mark}
/>
)}
<ModalTicket
active={modalActiveTicket}
setActive={setModalActiveTicket}
task={selectedTicket}
projectId={projectBoard.id}
projectName={projectBoard.name}
projectUsers={projectBoard.projectUsers}
projectOwnerId={projectBoard.owner_id}
projectMarks={projectBoard.mark}
/>
<div className="tasks__container">
{Boolean(projectBoard?.columns) &&
@ -535,6 +559,9 @@ export const ProjectTracker = () => {
startWrapperIndexTest={startWrapperIndexTest}
task={task}
titleColor={titleColor}
ref={(el) => {
taskRefs.current[task.id] = el;
}}
/>
);
})}

View File

@ -648,9 +648,15 @@ export const Stack = () => {
onChange={handleChange}
/>
<p>
Соглашаюсь с <a href="">Пользовательским соглашением</a> и
Соглашаюсь с{" "}
<a href="" target="_blank" rel="noopener noreferrer">
Пользовательским соглашением
</a>{" "}
и
<br />
<a href="">Политикой обработки данных</a>
<a href="" target="_blank" rel="noopener noreferrer">
Политикой обработки данных
</a>
</p>
</div>

View File

@ -58,6 +58,25 @@ export const Summary = () => {
});
}, []);
const addSkill = (skill) => {
const isSkillFound = selectedSkills.some(
(item) => item.skill_id == skill.id
);
if (!isSkillFound) {
setSelectedSkills((prevValue) => [
...prevValue,
{ skill: skill, skill_id: skill.id }
]);
}
};
const deleteSkill = (skill) => {
setSelectedSkills((prevValue) =>
prevValue.filter((item) => item.skill_id !== skill.skill_id)
);
};
function setSkills() {
apiRequest("/resume/edit-skills", {
method: "PUT",
@ -157,13 +176,7 @@ export const Summary = () => {
<img
src={deleteIcon}
alt="deleteIcon"
onClick={() =>
setSelectedSkills((prevValue) =>
prevValue.filter(
(item) => item.skill_id !== skill.skill_id
)
)
}
onClick={() => deleteSkill(skill)}
/>
</span>
);
@ -184,12 +197,7 @@ export const Summary = () => {
{skillsList.map((skill) => {
return (
<p
onClick={() =>
setSelectedSkills((prevValue) => [
...prevValue,
{ skill: skill, skill_id: skill.id }
])
}
onClick={() => addSkill(skill)}
key={skill.id}
className="select-skills__item"
>
@ -205,7 +213,7 @@ export const Summary = () => {
<div className="skills__section__items__wrapper">
{selectedSkills &&
selectedSkills.map((skill, index) => (
<span key={skill.id} className="skill_item">
<span key={skill.skill_id} className="skill_item">
{skill.skill.name}
{selectedSkills.length > index + 1 && ","}
</span>
@ -284,7 +292,7 @@ export const Summary = () => {
<a
href={itemGit.link}
target="_blank"
rel="noreferrer"
rel="noopener noreferrer"
key={itemGit.id}
className="summary__section-git-item git-item"
>
@ -305,7 +313,7 @@ export const Summary = () => {
className="git-item__link"
href={itemGit.link}
target="_blank"
rel="noreferrer"
rel="noopener noreferrer"
>
<img src={rightArrow} alt="arrowRight" />
</a>

View File

@ -24,6 +24,7 @@ export const DeveloperPage = () => {
return (
<Routes>
<Route exact path="/tracker/task/:id" element={<TicketFullScreen />} />
<Route
exact
path="/tracker/project/:id/task/:taskId"