Create new component TrackerTagList

This commit is contained in:
MaxOvs19 2024-04-09 21:09:06 +03:00
parent 75cf13d945
commit 10ffb1f7f1
5 changed files with 419 additions and 383 deletions

View File

@ -0,0 +1,129 @@
.tasks {
&__head {
&__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: 190px;
width: 100%;
@media (max-width: 915px) {
margin-right: 0;
width: 100%;
max-width: none;
}
@media (max-width: 650px) {
border-color: gray;
}
&-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;
}
@media (max-width: 915px) {
width: 100%;
max-width: none;
justify-content: start;
p {
font-size: 16px;
max-width: none;
}
}
}
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;
left: 0;
background: white;
border-radius: 8px;
z-index: 5;
padding: 10px 10px;
display: flex;
flex-direction: column;
row-gap: 7px;
width: 100%;
.executor-dropdown__person {
display: flex;
justify-content: space-between;
align-items: center;
p {
max-width: 155px;
overflow: hidden;
white-space: nowrap;
text-overflow: ellipsis;
@media (max-width: 915px) {
max-width: none;
}
}
img {
width: 20px;
height: 20px;
}
&:hover {
p {
font-weight: 600;
}
}
}
}
}
}
}

View File

@ -0,0 +1,273 @@
import React, { useEffect, useState } from "react";
import { HexColorPicker } from "react-colorful";
import { useDispatch } from "react-redux";
import { useParams } from "react-router-dom";
import {
addNewTagToProject,
deleteTagProject,
setProjectBoardFetch
} from "@redux/projectsTrackerSlice";
import { apiRequest } from "@api/request";
import { useNotification } from "@hooks/useNotification";
import arrowDown from "assets/icons/arrows/selectArrow.png";
import close from "assets/icons/close.png";
import edit from "assets/icons/edit.svg";
const TrackerTagList = ({ projectBoard }) => {
const dispatch = useDispatch();
const projectId = useParams();
const { showNotification } = useNotification();
const [tagInfo, setTagInfo] = useState({ description: "", name: "" });
const [color, setColor] = useState("#aabbcc");
const [tags, setTags] = useState({
open: false,
add: false,
edit: false
});
function deleteTag(tagId) {
apiRequest("/mark/detach", {
method: "DELETE",
data: {
mark_id: tagId,
entity_type: 1,
entity_id: projectId.id
}
}).then(() => {
dispatch(deleteTagProject(tagId));
showNotification({
show: true,
text: "Тег удален",
type: "success"
});
});
}
function addNewTag() {
apiRequest("/mark/create", {
method: "POST",
data: {
title: tagInfo.description,
slug: tagInfo.name,
color: color,
status: 1
}
}).then((data) => {
apiRequest("/mark/attach", {
method: "POST",
data: {
mark_id: data.id,
entity_type: 1,
entity_id: projectId.id
}
}).then((data) => {
dispatch(addNewTagToProject(data.mark));
setTags((prevState) => ({
...prevState,
add: false
}));
showNotification({
show: true,
text: "Тег успешно создан",
type: "success"
});
});
});
}
function editTag() {
apiRequest("/mark/update", {
method: "PUT",
data: {
mark_id: tagInfo.editMarkId,
title: tagInfo.description,
slug: tagInfo.name,
color: color
}
}).then(() => {
dispatch(setProjectBoardFetch(projectId.id));
setTags((prevState) => ({
...prevState,
edit: false
}));
setTagInfo({ description: "", name: "" });
setColor("#aabbcc");
showNotification({
show: true,
text: "Тег успешно изменён",
type: "success"
});
});
}
const initListeners = () => {
document.addEventListener("click", closeByClickingOut);
};
const closeByClickingOut = (event) => {
const path = event.path || (event.composedPath && event.composedPath());
if (
event &&
!path.find(
(div) =>
div.classList &&
(div.classList.contains("tasks__head__tags") ||
div.classList.contains("tags__list"))
)
) {
setTags({ open: false, add: false, edit: false });
setTagInfo({ description: "", name: "" });
setColor("#aabbcc");
}
};
useEffect(() => {
initListeners();
}, []);
return (
<div className="tasks__head__tags">
<div
className="tags__add"
onClick={() => {
setTags((prevState) => ({
...prevState,
open: !tags.open
}));
}}
>
<p>Список тегов</p>
<img className={tags.open ? "open" : ""} src={arrowDown} alt="arrow" />
</div>
{tags.open && (
<div className="tags__list">
{!tags.add && !tags.edit && (
<div
className="add-new-tag"
onClick={() =>
setTags((prevState) => ({
...prevState,
add: true
}))
}
>
<p>Добавить новый тег</p>
<span>+</span>
</div>
)}
{!tags.add && !tags.edit && (
<div className="tags__list__created">
{projectBoard.mark.map((tag) => {
return (
<div className="tag-item" key={tag.id}>
<div className="tag-item__info">
<span
className="tag-item__color"
style={{ background: tag.color }}
/>
<div>
<span className="tag-item__info__name">{tag.slug}</span>
<p className="tag-item__description">{tag.title}</p>
</div>
</div>
<div className="tag-item__images">
<img
src={edit}
alt="edit"
onClick={() => {
setTags((prevState) => ({
...prevState,
edit: true
}));
setTagInfo({
description: tag.title,
name: tag.slug,
editMarkId: tag.id
});
setColor(tag.color);
}}
/>
<img
onClick={() => deleteTag(tag.id)}
className="delete"
src={close}
alt="delete"
/>
</div>
</div>
);
})}
</div>
)}
{(tags.add || tags.edit) && (
<div className="form-tag">
<input
className="form-tag__input"
placeholder="Описание метки"
maxLength="25"
value={tagInfo.description}
onChange={(e) =>
setTagInfo((prevState) => ({
...prevState,
description: e.target.value
}))
}
/>
<input
className="form-tag__input"
placeholder="Тег"
value={tagInfo.name}
maxLength="10"
onChange={(e) =>
setTagInfo((prevState) => ({
...prevState,
name: e.target.value
}))
}
/>
<HexColorPicker color={color} onChange={setColor} />
<button
onClick={() => {
tags.add ? addNewTag() : editTag();
}}
className={
tagInfo.name && tagInfo.description
? "form-tag__btn"
: "form-tag__btn disable"
}
>
{tags.add ? "Добавить" : "Изменить"}
</button>
{(tags.add || tags.edit) && (
<button
className={"form-tag__btn"}
onClick={() => {
setTags((prevState) => ({
...prevState,
add: false,
edit: false
}));
setTagInfo({
description: "",
name: ""
});
setColor("#aabbcc");
}}
>
Отмена
</button>
)}
</div>
)}
</div>
)}
</div>
);
};
export default TrackerTagList;

View File

@ -1,15 +1,11 @@
import moment from "moment";
import React, { useEffect, useRef, useState } from "react";
import { HexColorPicker } from "react-colorful";
import { useDispatch, useSelector } from "react-redux";
import { Link, useParams } from "react-router-dom";
import {
activeLoader,
addNewTagToProject,
deleteTagProject,
filterCreatedByMe,
filteredExecutorTasks,
filteredParticipateTasks,
getBoarderLoader,
getProjectBoard,
@ -41,11 +37,10 @@ import { ProfileBreadcrumbs } from "@components/ProfileBreadcrumbs/ProfileBreadc
import { ProfileHeader } from "@components/ProfileHeader/ProfileHeader";
import TrackerSelectColumn from "@components/TrackerSelectColumn/TrackerSelectColumn";
import TrackerSelectExecutor from "@components/TrackerSelectExecutor/TrackerSelectExecutor";
import TrackerTagList from "@components/TrackerTagList/TrackerTagList";
import arrow from "assets/icons/arrows/arrowRight.png";
import arrowDown from "assets/icons/arrows/selectArrow.png";
import category from "assets/icons/category.svg";
import close from "assets/icons/close.png";
import commentsBoard from "assets/icons/commentsBoard.svg";
import del from "assets/icons/delete.svg";
import edit from "assets/icons/edit.svg";
@ -71,15 +66,8 @@ export const ProjectTracker = () => {
const [modalActiveTicket, setModalActiveTicket] = useState(false);
const [selectedTicket, setSelectedTicket] = useState({});
const [personListOpen, setPersonListOpen] = useState(false);
const [tags, setTags] = useState({
open: false,
add: false,
edit: false
});
const [acceptModalOpen, setAcceptModalOpen] = useState(false);
const [currentColumnDelete, setCurrentColumnDelete] = useState(null);
const [color, setColor] = useState("#aabbcc");
const [tagInfo, setTagInfo] = useState({ description: "", name: "" });
const [checkBoxParticipateTasks, setCheckBoxParticipateTasks] =
useState(false);
const [filteredNoTasks, setFilteredNoTasks] = useState(false);
@ -294,81 +282,6 @@ export const ProjectTracker = () => {
dispatch(setProjectBoardFetch(projectId.id));
}
function addNewTag() {
apiRequest("/mark/create", {
method: "POST",
data: {
title: tagInfo.description,
slug: tagInfo.name,
color: color,
status: 1
}
}).then((data) => {
apiRequest("/mark/attach", {
method: "POST",
data: {
mark_id: data.id,
entity_type: 1,
entity_id: projectId.id
}
}).then((data) => {
dispatch(addNewTagToProject(data.mark));
setTags((prevState) => ({
...prevState,
add: false
}));
showNotification({
show: true,
text: "Тег успешно создан",
type: "success"
});
});
});
}
function editTag() {
apiRequest("/mark/update", {
method: "PUT",
data: {
mark_id: tagInfo.editMarkId,
title: tagInfo.description,
slug: tagInfo.name,
color: color
}
}).then(() => {
dispatch(setProjectBoardFetch(projectId.id));
setTags((prevState) => ({
...prevState,
edit: false
}));
setTagInfo({ description: "", name: "" });
setColor("#aabbcc");
showNotification({
show: true,
text: "Тег успешно изменён",
type: "success"
});
});
}
function deleteTag(tagId) {
apiRequest("/mark/detach", {
method: "DELETE",
data: {
mark_id: tagId,
entity_type: 1,
entity_id: projectId.id
}
}).then(() => {
dispatch(deleteTagProject(tagId));
showNotification({
show: true,
text: "Тег удален",
type: "success"
});
});
}
const initListeners = () => {
document.addEventListener("click", closeByClickingOut);
};
@ -388,20 +301,6 @@ export const ProjectTracker = () => {
setPersonListOpen(false);
}
if (
event &&
!path.find(
(div) =>
div.classList &&
(div.classList.contains("tasks__head__tags") ||
div.classList.contains("tags__list"))
)
) {
setTags({ open: false, add: false, edit: false });
setTagInfo({ description: "", name: "" });
setColor("#aabbcc");
}
if (
event &&
!path.find(
@ -567,150 +466,8 @@ export const ProjectTracker = () => {
setSelectedExecutor={setSelectedExecutor}
/>
<div className="tasks__head__tags">
<div
className="tags__add"
onClick={() => {
setTags((prevState) => ({
...prevState,
open: !tags.open
}));
}}
>
<p>Список тегов</p>
<img
className={tags.open ? "open" : ""}
src={arrowDown}
alt="arrow"
/>
</div>
{tags.open && (
<div className="tags__list">
{!tags.add && !tags.edit && (
<div
className="add-new-tag"
onClick={() =>
setTags((prevState) => ({
...prevState,
add: true
}))
}
>
<p>Добавить новый тег</p>
<span>+</span>
</div>
)}
{!tags.add && !tags.edit && (
<div className="tags__list__created">
{projectBoard.mark.map((tag) => {
return (
<div className="tag-item" key={tag.id}>
<div className="tag-item__info">
<span
className="tag-item__color"
style={{ background: tag.color }}
/>
<div>
<span className="tag-item__info__name">
{tag.slug}
</span>
<p className="tag-item__description">
{tag.title}
</p>
</div>
</div>
<div className="tag-item__images">
<img
src={edit}
alt="edit"
onClick={() => {
setTags((prevState) => ({
...prevState,
edit: true
}));
setTagInfo({
description: tag.title,
name: tag.slug,
editMarkId: tag.id
});
setColor(tag.color);
}}
/>
<img
onClick={() => deleteTag(tag.id)}
className="delete"
src={close}
alt="delete"
/>
</div>
</div>
);
})}
</div>
)}
{(tags.add || tags.edit) && (
<div className="form-tag">
<input
className="form-tag__input"
placeholder="Описание метки"
maxLength="25"
value={tagInfo.description}
onChange={(e) =>
setTagInfo((prevState) => ({
...prevState,
description: e.target.value
}))
}
/>
<input
className="form-tag__input"
placeholder="Тег"
value={tagInfo.name}
maxLength="10"
onChange={(e) =>
setTagInfo((prevState) => ({
...prevState,
name: e.target.value
}))
}
/>
<HexColorPicker color={color} onChange={setColor} />
<button
onClick={() => {
tags.add ? addNewTag() : editTag();
}}
className={
tagInfo.name && tagInfo.description
? "form-tag__btn"
: "form-tag__btn disable"
}
>
{tags.add ? "Добавить" : "Изменить"}
</button>
{(tags.add || tags.edit) && (
<button
className={"form-tag__btn"}
onClick={() => {
setTags((prevState) => ({
...prevState,
add: false,
edit: false
}));
setTagInfo({
description: "",
name: ""
});
setColor("#aabbcc");
}}
>
Отмена
</button>
)}
</div>
)}
</div>
)}
</div>
<TrackerTagList projectBoard={projectBoard} />
<Link to="/profile/tracker" className="tasks__head__back">
<p>К списку проектов</p>
<img src={arrow} alt="arrow" />

View File

@ -361,132 +361,6 @@
}
}
&__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: 190px;
width: 100%;
@media (max-width: 915px) {
margin-right: 0;
width: 100%;
max-width: none;
}
@media (max-width: 650px) {
border-color: gray;
}
&-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;
}
@media (max-width: 915px) {
width: 100%;
max-width: none;
justify-content: start;
p {
font-size: 16px;
max-width: none;
}
}
}
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;
left: 0;
background: white;
border-radius: 8px;
z-index: 5;
padding: 10px 10px;
display: flex;
flex-direction: column;
row-gap: 7px;
width: 100%;
.executor-dropdown__person {
display: flex;
justify-content: space-between;
align-items: center;
p {
max-width: 155px;
overflow: hidden;
white-space: nowrap;
text-overflow: ellipsis;
@media (max-width: 915px) {
max-width: none;
}
}
img {
width: 20px;
height: 20px;
}
&:hover {
p {
font-weight: 600;
}
}
}
}
}
&__back {
cursor: pointer;
display: flex;
@ -1958,7 +1832,7 @@
.table {
&__search {
display: flex;
background: #F0F2F5;
background: #f0f2f5;
border-radius: 5px;
width: 100%;
padding: 14px 12px;
@ -1976,11 +1850,11 @@
border: none;
outline: none;
font-size: 16px;
color: #9BABC5;
color: #9babc5;
width: 100%;
&::placeholder {
color: #9BABC5;
color: #9babc5;
}
}
}
@ -1995,12 +1869,12 @@
width: 32px;
border-radius: 5px;
height: 32px;
color: #2E3A59;
color: #2e3a59;
}
.switch {
border: none;
background: #F0F2F5;
background: #f0f2f5;
font-weight: 600;
}
@ -2016,12 +1890,12 @@
background: white;
.page {
border: 1px solid #E8ECF8;
border: 1px solid #e8ecf8;
background: none;
&--active {
border: none;
background: #9DA65D;
background: #9da65d;
color: white;
}
}
@ -2029,17 +1903,20 @@
}
table {
grid-template-columns: minmax(0px, 2fr) minmax(0px, 1fr) minmax(0px, 1fr) minmax(0px, 1fr);
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;
border-bottom: 1px solid #f5f6f8;
color: #2e3a59;
padding: 0 7.5px 15px;
}
td {
padding: 22px 7.5px;
color: #2E3A59;
color: #2e3a59;
border-top: none;
p {