diff --git a/src/App.js b/src/App.js
index 67b53b2e..8c987891 100644
--- a/src/App.js
+++ b/src/App.js
@@ -6,6 +6,8 @@ import {
Navigate,
} from "react-router-dom";
+import { getNotification } from "@redux/outstaffingSlice";
+
import AuthForPartners from "./pages/AuthForPartners/AuthForPartners";
import AuthForDevelopers from "./pages/AuthForDevelopers/AuthForDevelopers";
import { TrackerIntro } from "./pages/TrackerIntro/TrackerIntro"
@@ -42,12 +44,15 @@ import Blog from "./pages/Blog/Blog";
import { ProjectTracker } from "./pages/ProjectTracker/ProjectTracker";
import { FrequentlyAskedQuestions } from "./pages/FrequentlyAskedQuestions/FrequentlyAskedQuestions";
import { FrequentlyAskedQuestion } from "./pages/FrequentlyAskedQuestion/FrequentlyAskedQuestion";
+import Notification from "@components/Notification/Notification";
+import { useSelector } from "react-redux";
import "./assets/global.scss";
import "./assets/fonts/stylesheet.css";
import "bootstrap/dist/css/bootstrap.min.css";
const App = () => {
+ const notification = useSelector(getNotification)
return (
<>
@@ -130,6 +135,9 @@ const App = () => {
} />
+ {notification.show &&
+
+ }
>
);
};
diff --git a/src/assets/icons/archiveNotification.svg b/src/assets/icons/archiveNotification.svg
new file mode 100644
index 00000000..c52c04a9
--- /dev/null
+++ b/src/assets/icons/archiveNotification.svg
@@ -0,0 +1,3 @@
+
diff --git a/src/assets/icons/copyNotification.svg b/src/assets/icons/copyNotification.svg
new file mode 100644
index 00000000..4d94ea9b
--- /dev/null
+++ b/src/assets/icons/copyNotification.svg
@@ -0,0 +1,5 @@
+
diff --git a/src/assets/icons/errorNotification.svg b/src/assets/icons/errorNotification.svg
new file mode 100644
index 00000000..af935231
--- /dev/null
+++ b/src/assets/icons/errorNotification.svg
@@ -0,0 +1,4 @@
+
diff --git a/src/assets/icons/successNotification.svg b/src/assets/icons/successNotification.svg
new file mode 100644
index 00000000..1e3c3af9
--- /dev/null
+++ b/src/assets/icons/successNotification.svg
@@ -0,0 +1,9 @@
+
\ No newline at end of file
diff --git a/src/components/Modal/AcceptModal/AcceptModal.jsx b/src/components/Modal/AcceptModal/AcceptModal.jsx
new file mode 100644
index 00000000..a2b3750b
--- /dev/null
+++ b/src/components/Modal/AcceptModal/AcceptModal.jsx
@@ -0,0 +1,33 @@
+import React from "react";
+
+import close from "assets/icons/closeProjectPersons.svg";
+
+import "./acceptModal.scss";
+
+export const AcceptModal = ({ closeModal, agreeHandler }) => {
+ return (
+
+
+
+ Вы точно хотите переместить задачу в архив?
+
+
+
+
+
+
+
+
+ );
+};
+
+export default AcceptModal;
diff --git a/src/components/Modal/AcceptModal/acceptModal.scss b/src/components/Modal/AcceptModal/acceptModal.scss
new file mode 100644
index 00000000..5f1abce2
--- /dev/null
+++ b/src/components/Modal/AcceptModal/acceptModal.scss
@@ -0,0 +1,61 @@
+.backDrop {
+ height: 100%;
+ width: 100%;
+ background-color: rgba(0, 0, 0, 0.11);
+ position: fixed;
+ top: 0;
+ left: 0;
+ display: flex;
+ z-index: 11;
+ align-items: center;
+ justify-content: center;
+
+ .acceptModal {
+ border-radius: 20px;
+ background: linear-gradient(180deg, #FFF 0%, #EBEBEB 100%);
+ padding: 50px 34px 25px;
+ display: flex;
+ flex-direction: column;
+ align-items: center;
+ position: relative;
+ row-gap: 25px;
+
+ &__title {
+ max-width: 260px;
+ font-size: 18px;
+ font-weight: 500;
+ text-align: center;
+ margin-bottom: 0;
+ }
+
+ &__buttons {
+ display: flex;
+ column-gap: 20px;
+
+ button {
+ min-width: 90px;
+ height: 37px;
+ border-radius: 44px;
+ border: none;
+ font-size: 14px;
+ font-weight: 500;
+ color: white;
+ }
+
+ .agree {
+ background: #52B709;
+ }
+
+ .cancel {
+ background: #B0BABF;
+ }
+ }
+
+ &__close {
+ position: absolute;
+ top: 15px;
+ right: 22px;
+ cursor: pointer;
+ }
+ }
+}
diff --git a/src/components/Modal/Tracker/ModalTicket/ModalTicket.jsx b/src/components/Modal/Tracker/ModalTicket/ModalTicket.jsx
index 210445e3..ed830c61 100644
--- a/src/components/Modal/Tracker/ModalTicket/ModalTicket.jsx
+++ b/src/components/Modal/Tracker/ModalTicket/ModalTicket.jsx
@@ -20,6 +20,9 @@ import {
import { apiRequest } from "@api/request";
+import { useNotification } from "@hooks/useNotification";
+
+import AcceptModal from "@components/Modal/AcceptModal/AcceptModal";
import TrackerModal from "@components/Modal/Tracker/TrackerModal/TrackerModal";
import TrackerTaskComment from "@components/TrackerTaskComment/TrackerTaskComment";
@@ -82,6 +85,8 @@ export const ModalTiсket = ({
const [correctProjectUsers, setCorrectProjectUsers] = useState(projectUsers);
const [executorId, setExecutorId] = useState(task.executor_id);
const profileInfo = useSelector(getProfileInfo);
+ const [acceptModalOpen, setAcceptModalOpen] = useState(false);
+ const { showNotification } = useNotification();
function deleteTask() {
apiRequest("/task/update-task", {
@@ -93,9 +98,18 @@ export const ModalTiсket = ({
}).then(() => {
setActive(false);
dispatch(setProjectBoardFetch(projectId));
+ showNotification({
+ show: true,
+ text: "Задача успешно была перемещена в архив",
+ type: "archive",
+ });
});
}
+ function archiveTask() {
+ setAcceptModalOpen(true);
+ }
+
function editTask() {
apiRequest("/task/update-task", {
method: "PUT",
@@ -416,6 +430,11 @@ export const ModalTiсket = ({
navigator.clipboard.writeText(
`https://itguild.info/tracker/task/${task.id}`
);
+ showNotification({
+ show: true,
+ text: "Ссылка скопирована в буфер обмена",
+ type: "copy",
+ });
}
function selectDeadLine(date) {
@@ -430,6 +449,10 @@ export const ModalTiсket = ({
});
}
+ function closeAcceptModal() {
+ setAcceptModalOpen(false);
+ }
+
return (
-
+
в архив
@@ -819,8 +842,13 @@ export const ModalTiсket = ({
+ {acceptModalOpen && (
+
+ )}
-
{
const [startDate, setStartDate] = useState(null);
const [uploadedFile, setUploadedFile] = useState(null);
const [taskFiles, setTaskFiles] = useState([]);
+ const [acceptModalOpen, setAcceptModalOpen] = useState(false);
useEffect(() => {
apiRequest(`/task/get-task?task_id=${ticketId.id}`).then((taskInfo) => {
@@ -161,6 +163,10 @@ export const TicketFullScreen = () => {
});
}
+ function archiveTask() {
+ setAcceptModalOpen(true);
+ }
+
function editTask() {
apiRequest("/task/update-task", {
method: "PUT",
@@ -450,6 +456,10 @@ export const TicketFullScreen = () => {
});
}
+ function closeAcceptModal() {
+ setAcceptModalOpen(false);
+ }
+
return (
@@ -976,7 +986,7 @@ export const TicketFullScreen = () => {
ссылка на задачу
-
+
в архив
@@ -990,6 +1000,9 @@ export const TicketFullScreen = () => {
>
)}
+ {acceptModalOpen && (
+
+ )}
);
diff --git a/src/components/Modal/Tracker/TrackerModal/TrackerModal.jsx b/src/components/Modal/Tracker/TrackerModal/TrackerModal.jsx
index a640dc53..32ccdad0 100644
--- a/src/components/Modal/Tracker/TrackerModal/TrackerModal.jsx
+++ b/src/components/Modal/Tracker/TrackerModal/TrackerModal.jsx
@@ -23,6 +23,8 @@ import { urlForLocal } from "@utils/helper";
import { apiRequest } from "@api/request";
+import { useNotification } from "@hooks/useNotification";
+
import BaseButton from "@components/Common/BaseButton/BaseButton";
import ModalLayout from "@components/Common/ModalLayout/ModalLayout";
@@ -67,9 +69,11 @@ export const TrackerModal = ({
const [correctProjectUsers, setCorrectProjectUsers] = useState([]);
const [selectColumnPriorityOpen, setSelectColumnPriorityOpen] =
useState(false);
+ const { showNotification } = useNotification();
function createTab() {
if (!valueColumn) {
+ showNotification({ show: true, text: "Введите название", type: "error" });
return;
}
@@ -91,6 +95,11 @@ export const TrackerModal = ({
function createTiket() {
if (!valueTiket || !descriptionTicket) {
+ showNotification({
+ show: true,
+ text: "Введите название и описание",
+ type: "error",
+ });
return;
}
@@ -106,25 +115,38 @@ export const TrackerModal = ({
priority: priorityTask,
},
}).then((res) => {
- if (selectedExecutorTask.user_id) {
- apiRequest("/task/update-task", {
- method: "PUT",
- data: {
- task_id: res.id,
- executor_id: selectedExecutorTask.user_id,
- },
- }).then(() => {
- dispatch(setProjectBoardFetch(projectBoard.id));
+ if (res.status === 500) {
+ showNotification({
+ show: true,
+ text: "Задача с таким именем уже существует",
+ type: "error",
+ });
+ } else {
+ if (selectedExecutorTask.user_id) {
+ apiRequest("/task/update-task", {
+ method: "PUT",
+ data: {
+ task_id: res.id,
+ executor_id: selectedExecutorTask.user_id,
+ },
+ }).then(() => {
+ dispatch(setProjectBoardFetch(projectBoard.id));
+ setActive(false);
+ setValueTiket("");
+ setDescriptionTicket("Описание задачи");
+ setSelectedExecutorTask("Выберите исполнителя задачи");
+ });
+ } else {
setActive(false);
setValueTiket("");
setDescriptionTicket("Описание задачи");
- setSelectedExecutorTask("Выберите исполнителя задачи");
+ dispatch(setProjectBoardFetch(projectBoard.id));
+ }
+ showNotification({
+ show: true,
+ text: "Задача создана",
+ type: "success",
});
- } else {
- setActive(false);
- setValueTiket("");
- setDescriptionTicket("Описание задачи");
- dispatch(setProjectBoardFetch(projectBoard.id));
}
});
}
@@ -204,10 +226,18 @@ export const TrackerModal = ({
status: 19,
},
}).then((res) => {
- const result = { ...res, columns: [] };
- dispatch(setProject(result));
- setActive(false);
- setNameProject("");
+ if (!Array.isArray(res.name)) {
+ const result = { ...res, columns: [] };
+ dispatch(setProject(result));
+ setActive(false);
+ setNameProject("");
+ } else {
+ showNotification({
+ show: true,
+ text: "Проект с таким именем уже существует",
+ type: "error",
+ });
+ }
});
}
}
diff --git a/src/components/Notification/Notification.jsx b/src/components/Notification/Notification.jsx
new file mode 100644
index 00000000..6ff81d78
--- /dev/null
+++ b/src/components/Notification/Notification.jsx
@@ -0,0 +1,40 @@
+import React from "react";
+import { useDispatch, useSelector } from "react-redux";
+
+import { closeNotification, getNotification } from "@redux/outstaffingSlice";
+
+import archive from "assets/icons/archiveNotification.svg";
+import close from "assets/icons/closeProjectPersons.svg";
+import copy from "assets/icons/copyNotification.svg";
+import error from "assets/icons/errorNotification.svg";
+import success from "assets/icons/successNotification.svg";
+
+import "./notification.scss";
+
+const images = {
+ archive: archive,
+ error: error,
+ copy: copy,
+ success: success,
+};
+
+export const Notification = () => {
+ const dispatch = useDispatch();
+ const notificationInfo = useSelector(getNotification);
+ return (
+
+
+
+
{notificationInfo.text}
+
+
dispatch(closeNotification())}
+ className="notification__close"
+ src={close}
+ alt="close"
+ />
+
+ );
+};
+
+export default Notification;
diff --git a/src/components/Notification/notification.scss b/src/components/Notification/notification.scss
new file mode 100644
index 00000000..cab02ae3
--- /dev/null
+++ b/src/components/Notification/notification.scss
@@ -0,0 +1,35 @@
+.notification {
+ border-radius: 40px;
+ background: linear-gradient(180deg, #FFF 0%, #EBEBEB 100%);
+ padding: 20px 82px 17px 27px;
+ position: fixed;
+ bottom: 25px;
+ right: 25px;
+ z-index: 20;
+
+ &__info {
+ display: flex;
+ column-gap: 10px;
+ align-items: center;
+
+ h2 {
+ max-width: 194px;
+ font-weight: 500;
+ font-size: 16px;
+ margin-bottom: 0;
+ }
+
+ img {
+ max-width: 24px;
+ }
+ }
+
+ &__close {
+ cursor: pointer;
+ position: absolute;
+ top: 15px;
+ right: 25px;
+ width: 15px;
+ height: 15px;
+ }
+}
diff --git a/src/components/ProjectTiket/ProjectTiket.jsx b/src/components/ProjectTiket/ProjectTiket.jsx
index cefb1f1e..7b2f4f59 100644
--- a/src/components/ProjectTiket/ProjectTiket.jsx
+++ b/src/components/ProjectTiket/ProjectTiket.jsx
@@ -6,6 +6,9 @@ import { deleteProject, modalToggle } from "@redux/projectsTrackerSlice";
import { apiRequest } from "@api/request";
+import { useNotification } from "@hooks/useNotification";
+
+import AcceptModal from "@components/Modal/AcceptModal/AcceptModal";
import { ModalSelect } from "@components/Modal/ModalSelect/ModalSelect";
import TrackerModal from "@components/Modal/Tracker/TrackerModal/TrackerModal";
@@ -19,7 +22,9 @@ import "./projectTiket.scss";
export const ProjectTiket = ({ project, index }) => {
const [modalSelect, setModalSelect] = useState(false);
const [modalAdd, setModalAdd] = useState(false);
+ const [acceptModalOpen, setAcceptModalOpen] = useState(false);
const dispatch = useDispatch();
+ const { showNotification } = useNotification();
useEffect(() => {
initListeners();
@@ -49,6 +54,11 @@ export const ProjectTiket = ({ project, index }) => {
},
}).then(() => {
dispatch(deleteProject(project));
+ showNotification({
+ show: true,
+ text: "Проект успешно была перемещена в архив",
+ type: "archive",
+ });
});
}
@@ -58,6 +68,10 @@ export const ProjectTiket = ({ project, index }) => {
);
}
+ function closeAcceptModal() {
+ setAcceptModalOpen(false);
+ }
+
return (
{project.name}
@@ -98,7 +112,12 @@ export const ProjectTiket = ({ project, index }) => {
ссылка на проект
-
+
{
+ setModalSelect(false);
+ setAcceptModalOpen(true);
+ }}
+ >
в архив
@@ -108,6 +127,12 @@ export const ProjectTiket = ({ project, index }) => {
+ {acceptModalOpen && (
+
+ )}
);
};
diff --git a/src/hooks/useNotification.js b/src/hooks/useNotification.js
new file mode 100644
index 00000000..bce98653
--- /dev/null
+++ b/src/hooks/useNotification.js
@@ -0,0 +1,15 @@
+import { useDispatch } from "react-redux";
+
+import { closeNotification, setNotification } from "../redux/outstaffingSlice";
+
+export const useNotification = () => {
+ const dispatch = useDispatch();
+
+ const showNotification = (notification) => {
+ dispatch(setNotification(notification));
+ setTimeout(() => {
+ dispatch(closeNotification());
+ }, 2500);
+ };
+ return { showNotification };
+};
diff --git a/src/pages/ProjectTracker/ProjectTracker.js b/src/pages/ProjectTracker/ProjectTracker.js
index f9094701..0ab9b422 100644
--- a/src/pages/ProjectTracker/ProjectTracker.js
+++ b/src/pages/ProjectTracker/ProjectTracker.js
@@ -25,6 +25,8 @@ import { caseOfNum } from "@utils/helper";
import { apiRequest } from "@api/request";
+import { useNotification } from "@hooks/useNotification";
+
import BaseButton from "@components/Common/BaseButton/BaseButton";
import { Footer } from "@components/Common/Footer/Footer";
import { Loader } from "@components/Common/Loader/Loader";
@@ -72,6 +74,7 @@ export const ProjectTracker = () => {
const startWrapperIndexTest = useRef({});
const projectBoard = useSelector(getProjectBoard);
const loader = useSelector(getBoarderLoader);
+ const { showNotification } = useNotification();
useEffect(() => {
dispatch(activeLoader());
@@ -224,6 +227,7 @@ export const ProjectTracker = () => {
} else {
dispatch(setProjectBoardFetch(projectBoard.id));
}
+ showNotification({ show: true, text: "Колонка удалена", type: "error" });
});
}
diff --git a/src/redux/outstaffingSlice.js b/src/redux/outstaffingSlice.js
index f6f60f69..82f9e84c 100644
--- a/src/redux/outstaffingSlice.js
+++ b/src/redux/outstaffingSlice.js
@@ -1,4 +1,4 @@
-import { createSlice } from "@reduxjs/toolkit";
+import { createAsyncThunk, createSlice } from "@reduxjs/toolkit";
const initialState = {
tags: [],
@@ -14,6 +14,11 @@ const initialState = {
partnerRequestId: null,
partnerRequests: [],
partnerRequestInfo: {},
+ notification: {
+ show: false,
+ text: "",
+ type: "",
+ },
};
export const outstaffingSlice = createSlice({
@@ -62,6 +67,12 @@ export const outstaffingSlice = createSlice({
setPartnerRequestInfo: (state, action) => {
state.partnerRequestInfo = action.payload;
},
+ setNotification: (state, action) => {
+ state.notification = action.payload;
+ },
+ closeNotification: (state) => {
+ state.notification.show = false;
+ },
},
});
@@ -80,12 +91,15 @@ export const {
setPartnerRequestId,
setPartnerRequests,
setPartnerRequestInfo,
+ setNotification,
+ closeNotification,
} = outstaffingSlice.actions;
export const selectProfiles = (state) => state.outstaffing.profiles;
export const selectTags = (state) => state.outstaffing.tags;
export const selectFilteredCandidates = (state) =>
state.outstaffing.filteredCandidates;
+export const getNotification = (state) => state.outstaffing.notification;
export const selectItems = (state) => state.outstaffing.selectedItems;
export const selectCurrentCandidate = (state) =>
state.outstaffing.currentCandidate;