This commit is contained in:
kirill boyko 2024-04-02 17:48:38 +03:00
commit b19e00d98f
7 changed files with 436 additions and 96 deletions

View File

@ -299,6 +299,17 @@ export const TrackerModal = ({
title: columnName
}
}).then(() => {
const existingColumn = projectBoard.columns.find(
(column) => column.title === columnName
);
if (existingColumn) {
showNotification({
show: true,
text: "Колонка с таким названием уже существует",
type: "error"
});
} else {
setActive(false);
dispatch(editColumnName({ id: columnId, title: columnName }));
showNotification({
@ -306,6 +317,7 @@ export const TrackerModal = ({
text: "Колонка успешно изменена",
type: "success"
});
}
});
}
@ -375,14 +387,29 @@ export const TrackerModal = ({
email: emailWorker,
project_id: projectBoard.id
}
}).then((el) => {
}).then((response) => {
if (response.status === 400) {
showNotification({
show: true,
text: "Участник уже добавлен в проект",
type: "error"
});
} else if (response.status === 404) {
showNotification({
show: true,
text: "Данной почты не существует",
type: "error"
});
} else {
setActive(false);
setEmailWorker("");
dispatch(addPersonToProject(response));
showNotification({
show: true,
text: "Приглашение отправлено",
type: "success"
});
}
});
} else {
setEmailError("Некорректный e-mail адрес");

View File

@ -43,11 +43,11 @@ export const Navigation = () => {
}
],
partner: [
{
path: "/catalog",
active: "candidate",
name: "Каталог"
},
// {
// path: "/catalog",
// active: "candidate",
// name: "Каталог"
// },
{
path: "/requests",
name: "Мои вакансии"

View File

@ -101,8 +101,11 @@ export const ProjectTicket = ({ project, index }) => {
alt="avatar"
className="project__avatar"
/>
<div>
<p>Создатель проекта</p>
<span>{project.owner_info.fio}</span>
</div>
</div>
</Link>
{/* <Link
@ -112,6 +115,11 @@ export const ProjectTicket = ({ project, index }) => {
Посмотреть статистику
</Link> */}
<span className="project-stats" onClick={() => {}}>
Просмотреть <br />
статистику проекта
</span>
<span
className="menu-settings"
onClick={() => {
@ -159,14 +167,14 @@ export const ProjectTicket = ({ project, index }) => {
setAcceptModalOpen(true);
}}
>
<img src={archiveSet}></img>
{/* <img src={archiveSet}></img>
<p>в архив</p>
</div>
<div
onClick={() => {
navigate(`/profile/statistics/${project.id}`);
}}
>
> */}
<img src={archiveSet}></img>
<p>статистика</p>
</div>

View File

@ -2,7 +2,8 @@
display: flex;
flex-direction: column;
position: relative;
width: 300px;
width: 22%;
height: 170px;
background: #f1f1f1;
border-radius: 12px;
@ -29,7 +30,7 @@
text-overflow: ellipsis;
font-size: 18px;
color: #111112;
margin-bottom: 10px;
margin: 0 0 15px 0;
&:hover {
color: black;
@ -44,14 +45,15 @@
p {
color: #6f6f6f;
margin-bottom: 0;
font-size: 12px;
font-weight: 500;
font-size: 9px;
font-weight: 300;
line-height: 17px;
}
span {
color: blue;
font-size: 15px;
font-weight: 400;
}
.count {
@ -80,18 +82,29 @@
}
}
&-stats {
font-size: 12px;
font-weight: 300;
line-height: 17px;
text-decoration: underline;
color: #678eda;
position: absolute;
left: 18px;
bottom: 10px;
}
.menu-settings {
position: absolute;
font-size: 30px;
color: #6f6f6f;
right: 15px;
top: -10px;
bottom: 10px;
}
&__avatar {
width: 25px;
height: 25px;
margin-right: 10px;
width: 30px;
height: 30px;
margin: 0 10px 0 0;
}
&__open-tracker {

View File

@ -37,25 +37,25 @@ export const Profile = () => {
path: "profile/summary",
img: summaryIcon,
title: "Резюме",
description: "Ваше резюме<br/><span>заполнено</span>"
description: "Ваше резюме <br/><span>заполнено</span>"
},
{
path: "profile/tracker",
img: timerIcon,
title: "Трекер времени",
description: "Сколько времени занимает<br/> выполнение задач"
description: "Сколько времени занимает <br/>выполнение задач"
},
// {
// path: "profile/payouts",
// img: paymentIcon,
// title: "Выплаты",
// description: "У вас <span>подтвержден</span><br/> статус самозанятого"
// description: "У вас <span>подтвержден</span> <br/>статус самозанятого"
// },
{
path: "profile/settings",
img: settingIcon,
title: "Настройки профиля",
description: "Перейдите чтобы начать<br/> редактирование"
description: "Перейдите чтобы начать <br/>редактирование"
}
],
partner: [
@ -64,31 +64,31 @@ export const Profile = () => {
img: reportsIcon,
title: "Мои вакансии",
description:
"<span>Ваши открытые вакансии, которыми вы можете управлять"
"Ваши открытые вакансии, <br/><span>которыми вы можете управлять</span>"
},
{
path: "profile/employees",
img: summaryIcon,
title: "Данные персонала",
description: "Наши специалисты <br/><span>уже работающие у вас</span>"
description: "Наши специалисты, <br/><span>уже работающие у вас</span>"
},
{
path: "profile/tracker",
img: timerIcon,
title: "Трекер времени",
description: "Контроль времени и<br/> выполнение задач"
description: "Контроль времени и <br/>выполнение задач"
},
// {
// path: "profile/treaties",
// img: paymentIcon,
// title: "Договоры и отчетность",
// description: "Ключевые условия<br/> договора"
// description: "Ключевые условия <br/>договора"
// },
{
path: "profile/settings",
img: settingIcon,
title: "Настройки профиля",
description: "Перейдите чтобы начать<br/> редактирование"
description: "Перейдите чтобы начать <br/>редактирование"
}
]
});
@ -116,9 +116,9 @@ export const Profile = () => {
/>
<p className="summary__name">
<span>
{profileInfo?.fio || profileInfo?.username},
{profileInfo?.fio || profileInfo?.username}
{user === "developer" &&
` ${profileInfo?.specification} разработчик`}
`, ${profileInfo?.specification} разработчик`}
</span>
</p>
</div>

View File

@ -1,5 +1,11 @@
import { getTheme } from "@table-library/react-table-library/baseline";
import { CompactTable } from "@table-library/react-table-library/compact";
import { usePagination } from "@table-library/react-table-library/pagination";
import { useSort } from "@table-library/react-table-library/sort";
import { useTheme } from "@table-library/react-table-library/theme";
import React, { useEffect, useState } from "react";
import { useDispatch, useSelector } from "react-redux";
import { Link } from "react-router-dom";
import {
getProjects,
@ -9,7 +15,8 @@ import {
setToggleTab
} from "@redux/projectsTrackerSlice";
import { caseOfNum } from "@utils/helper";
import { getCorrectDate } from "@utils/calendarHelper";
import { caseOfNum, urlForLocal } from "@utils/helper";
import { apiRequest } from "@api/request";
@ -26,9 +33,10 @@ import ProjectTicket from "@components/ProjectTicket/ProjectTicket";
import addProjectImg from "assets/icons/addProjectImg.svg";
import archiveTrackerProjects from "assets/icons/archiveTrackerProjects.svg";
import rightArrow from "assets/icons/arrows/arrowRight.svg";
import arrowViewReport from "assets/icons/arrows/arrowViewReport.svg";
import filterIcon from "assets/icons/filterIcon.svg";
import search from "assets/icons/serchIcon.png";
import searchImg from "assets/icons/serchIcon.png";
import project from "assets/icons/trackerProject.svg";
import tasks from "assets/icons/trackerTasks.svg";
import archive from "assets/images/archiveIcon.png";
@ -43,15 +51,110 @@ export const Tracker = () => {
const dispatch = useDispatch();
const projects = useSelector(getProjects);
const tab = useSelector(getToggleTab);
const theme = useTheme(getTheme());
const [nodes, setNodes] = useState([]);
const [initialNodes, setInitialNodes] = useState([]);
const [allTasks, setAllTasks] = useState([]);
const [filteredAllTasks, setFilteredAllTasks] = useState([]);
const [loader, setLoader] = useState(false);
const [filterCompleteTasks, setFilterCompleteTasks] = useState([]);
const [allCompletedTasks, setAllCompletedTasks] = useState([]);
const [search, setSearch] = useState("");
const [modalCreateProject, setModalCreateProject] = useState(false);
const COLUMNS = [
{
label: "Задача",
renderCell: (item) => (
<p dangerouslySetInnerHTML={{ __html: item.title }}></p>
),
sort: { sortKey: "NAME" }
},
{
label: "Создано",
renderCell: (item) => <span>{getCorrectDate(item.created_at)}</span>,
sort: { sortKey: "CREATE" }
},
{
label: "Дедлайн",
renderCell: (item) => (
<span>
{item.dead_line ? getCorrectDate(item.dead_line) : "Без дедлайна"}
</span>
),
sort: { sortKey: "DEADLINE" }
},
{
label: "Потраченное время",
renderCell: (item) => (
<span>
{item.timers.length
? getSpendTime(item.timers)
: "Трекер не был включен"}
</span>
),
sort: { sortKey: "SPEND" }
}
];
let data = { nodes };
const sort = useSort(
data,
{
onChange: onSortChange
},
{
sortFns: {
NAME: (array) => array.sort((a, b) => a.title.localeCompare(b.title)),
CREATE: (array) =>
array.sort((a, b) => new Date(a.created_at) - new Date(b.created_at)),
DEADLINE: (array) =>
array.sort((a, b) => new Date(a.dead_line) - new Date(b.dead_line)),
SPEND: (array) =>
array.sort(
(a, b) =>
getSpendTime(a.timers, true) - getSpendTime(b.timers, true)
)
}
}
);
const pagination = usePagination(data, {
state: {
page: 0,
size: 11
}
});
function getSpendTime(times, seconds) {
let timerSeconds = 0;
times.forEach((time) => {
timerSeconds += time.deltaSeconds;
});
if (seconds) {
return timerSeconds;
}
return `${Math.floor(timerSeconds / 60 / 60)}:${Math.floor(
(timerSeconds / 60) % 60
)}:${timerSeconds % 60}`;
}
function onSortChange(action, state) {
console.log(action, state);
}
const handleSearch = (event) => {
setSearch(event.target.value);
setNodes(
initialNodes.filter((item) =>
item.title.toLowerCase().includes(event.target.value.toLowerCase())
)
);
};
useEffect(() => {
setLoader(true);
apiRequest(
@ -84,6 +187,8 @@ export const Tracker = () => {
: [];
setAllTasks(allTasks);
setFilteredAllTasks(allTasks);
setNodes(allTasks);
setInitialNodes(allTasks);
setAllCompletedTasks(completedTasks);
setFilterCompleteTasks(completedTasks);
})
@ -225,8 +330,16 @@ export const Tracker = () => {
setModalCreateProject(true);
}}
>
<p className="create-project-btn__text">
Добавить новый проект
</p>
<div className="create-project-btn__content">
<img src={addProjectImg} alt="#"></img>
<p className="create-project-btn__text">Добавить проект</p>
<p>
Ставьте задачи, следите за прогрессом, ведите учёт
рабочего времени
</p>
</div>
</BaseButton>
</>
)}
@ -238,53 +351,122 @@ export const Tracker = () => {
: "tracker__tabs__content__projects"
}
>
<div className="task-list__head">
<div className="task-list__tasks-period">
<div className="month-period">
<p>
{25} - {35}
</p>
<h3>Сентября,</h3>
<h3>2023</h3>
</div>
{/*<div className="task-list__head">*/}
{/* <div className="task-list__tasks-period">*/}
{/* <div className="month-period">*/}
{/* <p>*/}
{/* {25} - {35}*/}
{/* </p>*/}
{/* <h3>Сентября,</h3>*/}
{/* <h3>2023</h3>*/}
{/* </div>*/}
<div className="buttons-month">
<button>
<img src={arrowViewReport} alt="<"></img>
</button>
<button>
<img src={arrowViewReport} alt=">"></img>
</button>
</div>
</div>
{/* <div className="buttons-month">*/}
{/* <button>*/}
{/* <img src={arrowViewReport} alt="<"></img>*/}
{/* </button>*/}
{/* <button>*/}
{/* <img src={arrowViewReport} alt=">"></img>*/}
{/* </button>*/}
{/* </div>*/}
{/* </div>*/}
<div className="task-list__head__search">
<img src={search} alt="search" />
{/* <div className="task-list__head__search">*/}
{/* <img src={search} alt="search" />*/}
{/* <input*/}
{/* type="text"*/}
{/* placeholder="Найти задачу"*/}
{/* onChange={(event) => filterAllTask(event)}*/}
{/* />*/}
{/* </div>*/}
{/* <div className="task-list__filters">*/}
{/* <BaseButton styles={"task-list__filters-filter"}>*/}
{/* <img src={filterIcon} alt="#" />*/}
{/* <p>Фильтр</p>*/}
{/* </BaseButton>*/}
{/* <BaseButton styles={"task-list__filters-clear"}>*/}
{/* <p> Очистить фильтр</p>*/}
{/* </BaseButton>*/}
{/* </div>*/}
{/*</div>*/}
{loader ? (
<Loader style="green" />
) : (
<>
<div className="table__search">
<img src={searchImg} alt="search" />
<input
type="text"
placeholder="Найти задачу"
onChange={(event) => filterAllTask(event)}
placeholder="Поиск по задачам"
value={search}
onChange={handleSearch}
/>
</div>
<div className="task-list__filters">
<BaseButton styles={"task-list__filters-filter"}>
<img src={filterIcon} alt="#" />
<p>Фильтр</p>
</BaseButton>
<BaseButton styles={"task-list__filters-clear"}>
<p> Очистить фильтр</p>
</BaseButton>
</div>
</div>
{loader && <Loader style="green" />}
<AllTaskTableTracker
loader={loader}
filteredAllTasks={filteredAllTasks}
projects={projects}
<CompactTable
columns={COLUMNS}
data={data}
theme={theme}
sort={sort}
pagination={pagination}
/>
<div className="table__pagination">
<button
className={
pagination.state.page === 0 ? "switch disable" : "switch"
}
type="button"
disabled={pagination.state.page === 0}
onClick={() =>
pagination.fns.onSetPage(pagination.state.page - 1)
}
>
{"<"}
</button>
<span className="table__pages">
{pagination.state.getPages(data.nodes).map((_, index) => (
<button
key={index}
type="button"
className={
pagination.state.page === index
? "page page--active "
: "page"
}
onClick={() => pagination.fns.onSetPage(index)}
>
{index + 1}
</button>
))}
</span>
<button
className={
pagination.state.page + 1 ===
pagination.state.getPages(data.nodes).length
? "switch disable"
: "switch"
}
type="button"
disabled={
pagination.state.page + 1 ===
pagination.state.getPages(data.nodes).length
}
onClick={() =>
pagination.fns.onSetPage(pagination.state.page + 1)
}
>
{">"}
</button>
</div>
</>
)}
{/*<AllTaskTableTracker*/}
{/* loader={loader}*/}
{/* filteredAllTasks={filteredAllTasks}*/}
{/* projects={projects}*/}
{/*/>*/}
<div className="task-list__time">
<div className="task-list__time-compited">

View File

@ -162,21 +162,35 @@
}
.create-project-btn {
width: 300px;
height: 83px;
width: 22%;
height: 170px;
border-radius: 12px;
background: #ecf8e5;
color: #000000;
display: flex;
align-items: center;
justify-content: center;
flex-direction: column;
justify-content: flex-start;
transition: 0.4s;
padding: 10px 10px;
&__text {
text-align: left;
font-weight: 400;
color: #1458dd;
text-align: center;
font-weight: 700;
font-size: 18px;
margin-left: 15px;
margin: 0 0 25px 0;
}
&__content {
display: flex;
align-items: center;
margin: 0 20px 0 20px;
font-size: 10px;
font-weight: 400;
img {
margin: 0 20px 0 0;
}
}
&:hover {
@ -1937,4 +1951,100 @@
}
}
}
.table {
&__search {
display: flex;
background: #F0F2F5;
border-radius: 5px;
width: 100%;
padding: 14px 12px;
column-gap: 10px;
align-items: center;
margin-bottom: 20px;
img {
width: 20px;
height: 20px;
}
input {
background: none;
border: none;
outline: none;
font-size: 16px;
color: #9BABC5;
width: 100%;
&::placeholder {
color: #9BABC5;
}
}
}
&__pagination {
display: flex;
margin: 25px auto 0;
column-gap: 12px;
button {
font-size: 14px;
width: 32px;
border-radius: 5px;
height: 32px;
color: #2E3A59;
}
.switch {
border: none;
background: #F0F2F5;
font-weight: 600;
}
.disable {
opacity: 0.7;
}
}
&__pages {
display: flex;
column-gap: 4px;
color: black;
background: white;
.page {
border: 1px solid #E8ECF8;
background: none;
&--active {
border: none;
background: #9DA65D;
color: white;
}
}
}
}
table {
grid-template-columns: minmax(0px, 2fr) minmax(0px, 1fr) minmax(0px, 1fr) minmax(0px, 1fr);
th {
border-top: none;
border-bottom: 1px solid #F5F6F8;
color: #2E3A59;
padding: 0 7.5px 15px;
}
td {
padding: 22px 7.5px;
color: #2E3A59;
border-top: none;
p {
max-width: 430px;
overflow: hidden;
white-space: nowrap;
text-overflow: ellipsis;
}
}
}
}