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

View File

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

View File

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

View File

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

View File

@ -64,13 +64,13 @@ export const Profile = () => {
img: reportsIcon, img: reportsIcon,
title: "Мои вакансии", title: "Мои вакансии",
description: description:
"<span>Ваши открытые вакансии, которыми вы можете управлять" "Ваши открытые вакансии, <br/><span>которыми вы можете управлять</span>"
}, },
{ {
path: "profile/employees", path: "profile/employees",
img: summaryIcon, img: summaryIcon,
title: "Данные персонала", title: "Данные персонала",
description: "Наши специалисты <br/><span>уже работающие у вас</span>" description: "Наши специалисты, <br/><span>уже работающие у вас</span>"
}, },
{ {
path: "profile/tracker", path: "profile/tracker",
@ -116,9 +116,9 @@ export const Profile = () => {
/> />
<p className="summary__name"> <p className="summary__name">
<span> <span>
{profileInfo?.fio || profileInfo?.username}, {profileInfo?.fio || profileInfo?.username}
{user === "developer" && {user === "developer" &&
` ${profileInfo?.specification} разработчик`} `, ${profileInfo?.specification} разработчик`}
</span> </span>
</p> </p>
</div> </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 React, { useEffect, useState } from "react";
import { useDispatch, useSelector } from "react-redux"; import { useDispatch, useSelector } from "react-redux";
import { Link } from "react-router-dom";
import { import {
getProjects, getProjects,
@ -9,7 +15,8 @@ import {
setToggleTab setToggleTab
} from "@redux/projectsTrackerSlice"; } from "@redux/projectsTrackerSlice";
import { caseOfNum } from "@utils/helper"; import { getCorrectDate } from "@utils/calendarHelper";
import { caseOfNum, urlForLocal } from "@utils/helper";
import { apiRequest } from "@api/request"; import { apiRequest } from "@api/request";
@ -26,9 +33,10 @@ import ProjectTicket from "@components/ProjectTicket/ProjectTicket";
import addProjectImg from "assets/icons/addProjectImg.svg"; import addProjectImg from "assets/icons/addProjectImg.svg";
import archiveTrackerProjects from "assets/icons/archiveTrackerProjects.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 arrowViewReport from "assets/icons/arrows/arrowViewReport.svg";
import filterIcon from "assets/icons/filterIcon.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 project from "assets/icons/trackerProject.svg";
import tasks from "assets/icons/trackerTasks.svg"; import tasks from "assets/icons/trackerTasks.svg";
import archive from "assets/images/archiveIcon.png"; import archive from "assets/images/archiveIcon.png";
@ -43,15 +51,110 @@ export const Tracker = () => {
const dispatch = useDispatch(); const dispatch = useDispatch();
const projects = useSelector(getProjects); const projects = useSelector(getProjects);
const tab = useSelector(getToggleTab); const tab = useSelector(getToggleTab);
const theme = useTheme(getTheme());
const [nodes, setNodes] = useState([]);
const [initialNodes, setInitialNodes] = useState([]);
const [allTasks, setAllTasks] = useState([]); const [allTasks, setAllTasks] = useState([]);
const [filteredAllTasks, setFilteredAllTasks] = useState([]); const [filteredAllTasks, setFilteredAllTasks] = useState([]);
const [loader, setLoader] = useState(false); const [loader, setLoader] = useState(false);
const [filterCompleteTasks, setFilterCompleteTasks] = useState([]); const [filterCompleteTasks, setFilterCompleteTasks] = useState([]);
const [allCompletedTasks, setAllCompletedTasks] = useState([]); const [allCompletedTasks, setAllCompletedTasks] = useState([]);
const [search, setSearch] = useState("");
const [modalCreateProject, setModalCreateProject] = useState(false); 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(() => { useEffect(() => {
setLoader(true); setLoader(true);
apiRequest( apiRequest(
@ -84,6 +187,8 @@ export const Tracker = () => {
: []; : [];
setAllTasks(allTasks); setAllTasks(allTasks);
setFilteredAllTasks(allTasks); setFilteredAllTasks(allTasks);
setNodes(allTasks);
setInitialNodes(allTasks);
setAllCompletedTasks(completedTasks); setAllCompletedTasks(completedTasks);
setFilterCompleteTasks(completedTasks); setFilterCompleteTasks(completedTasks);
}) })
@ -225,8 +330,16 @@ export const Tracker = () => {
setModalCreateProject(true); setModalCreateProject(true);
}} }}
> >
<p className="create-project-btn__text">
Добавить новый проект
</p>
<div className="create-project-btn__content">
<img src={addProjectImg} alt="#"></img> <img src={addProjectImg} alt="#"></img>
<p className="create-project-btn__text">Добавить проект</p> <p>
Ставьте задачи, следите за прогрессом, ведите учёт
рабочего времени
</p>
</div>
</BaseButton> </BaseButton>
</> </>
)} )}
@ -238,53 +351,122 @@ export const Tracker = () => {
: "tracker__tabs__content__projects" : "tracker__tabs__content__projects"
} }
> >
<div className="task-list__head"> {/*<div className="task-list__head">*/}
<div className="task-list__tasks-period"> {/* <div className="task-list__tasks-period">*/}
<div className="month-period"> {/* <div className="month-period">*/}
<p> {/* <p>*/}
{25} - {35} {/* {25} - {35}*/}
</p> {/* </p>*/}
<h3>Сентября,</h3> {/* <h3>Сентября,</h3>*/}
<h3>2023</h3> {/* <h3>2023</h3>*/}
</div> {/* </div>*/}
<div className="buttons-month"> {/* <div className="buttons-month">*/}
<button> {/* <button>*/}
<img src={arrowViewReport} alt="<"></img> {/* <img src={arrowViewReport} alt="<"></img>*/}
</button> {/* </button>*/}
<button> {/* <button>*/}
<img src={arrowViewReport} alt=">"></img> {/* <img src={arrowViewReport} alt=">"></img>*/}
</button> {/* </button>*/}
</div> {/* </div>*/}
</div> {/* </div>*/}
<div className="task-list__head__search"> {/* <div className="task-list__head__search">*/}
<img src={search} alt="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 <input
type="text" type="text"
placeholder="Найти задачу" placeholder="Поиск по задачам"
onChange={(event) => filterAllTask(event)} value={search}
onChange={handleSearch}
/> />
</div> </div>
<CompactTable
<div className="task-list__filters"> columns={COLUMNS}
<BaseButton styles={"task-list__filters-filter"}> data={data}
<img src={filterIcon} alt="#" /> theme={theme}
<p>Фильтр</p> sort={sort}
</BaseButton> pagination={pagination}
<BaseButton styles={"task-list__filters-clear"}>
<p> Очистить фильтр</p>
</BaseButton>
</div>
</div>
{loader && <Loader style="green" />}
<AllTaskTableTracker
loader={loader}
filteredAllTasks={filteredAllTasks}
projects={projects}
/> />
<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">
<div className="task-list__time-compited"> <div className="task-list__time-compited">

View File

@ -162,21 +162,35 @@
} }
.create-project-btn { .create-project-btn {
width: 300px; width: 22%;
height: 83px; height: 170px;
border-radius: 12px; border-radius: 12px;
background: #ecf8e5; background: #ecf8e5;
color: #000000; color: #000000;
display: flex; display: flex;
align-items: center; flex-direction: column;
justify-content: center; justify-content: flex-start;
transition: 0.4s; transition: 0.4s;
padding: 10px 10px;
&__text { &__text {
text-align: left; color: #1458dd;
font-weight: 400; text-align: center;
font-weight: 700;
font-size: 18px; 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 { &: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;
}
}
}
} }