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..f791222a --- /dev/null +++ b/src/components/Modal/AcceptModal/AcceptModal.jsx @@ -0,0 +1,24 @@ +import React from "react"; + +import close from "assets/icons/closeProjectPersons.svg"; + +import './acceptModal.scss' + +export const AcceptModal = ({closeModal, agreeHandler}) => { + return ( +
+
+

+ Вы точно хотите переместить задачу в архив? +

+
+ + +
+ close +
+
+ ) +} + +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..b1624278 100644 --- a/src/components/Modal/Tracker/ModalTicket/ModalTicket.jsx +++ b/src/components/Modal/Tracker/ModalTicket/ModalTicket.jsx @@ -9,6 +9,7 @@ import { Link } from "react-router-dom"; import { getProfileInfo } from "@redux/outstaffingSlice"; import { setProjectBoardFetch } from "@redux/projectsTrackerSlice"; +import { useNotification } from "@hooks/useNotification"; import { backendImg, @@ -22,6 +23,7 @@ import { apiRequest } from "@api/request"; import TrackerModal from "@components/Modal/Tracker/TrackerModal/TrackerModal"; import TrackerTaskComment from "@components/TrackerTaskComment/TrackerTaskComment"; +import AcceptModal from "@components/Modal/AcceptModal/AcceptModal"; import archive from "assets/icons/archive.svg"; import arrow from "assets/icons/arrows/arrowStart.png"; @@ -82,6 +84,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 +97,14 @@ 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 +425,7 @@ 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 +440,10 @@ export const ModalTiсket = ({ }); } + function closeAcceptModal () { + setAcceptModalOpen(false) + } + return (

ссылка на задачу

-
+

в архив

@@ -819,8 +833,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 = () => { link

ссылка на задачу

-
+
arch

в архив

@@ -990,6 +1000,12 @@ export const TicketFullScreen = () => { )}
+ {acceptModalOpen && + + }
); diff --git a/src/components/Modal/Tracker/TrackerModal/TrackerModal.jsx b/src/components/Modal/Tracker/TrackerModal/TrackerModal.jsx index a640dc53..191abc4b 100644 --- a/src/components/Modal/Tracker/TrackerModal/TrackerModal.jsx +++ b/src/components/Modal/Tracker/TrackerModal/TrackerModal.jsx @@ -30,6 +30,7 @@ import arrowDown from "assets/icons/arrows/selectArrow.png"; import avatarMok from "assets/images/avatarMok.png"; import "./trackerModal.scss"; +import {useNotification} from "@hooks/useNotification"; export const TrackerModal = ({ active, @@ -67,9 +68,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 +94,7 @@ export const TrackerModal = ({ function createTiket() { if (!valueTiket || !descriptionTicket) { + showNotification({show: true, text: 'Введите название и описание', type: 'error'}) return; } @@ -106,25 +110,30 @@ 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("Выберите исполнителя задачи"); - }); - } else { - setActive(false); - setValueTiket(""); - setDescriptionTicket("Описание задачи"); - dispatch(setProjectBoardFetch(projectBoard.id)); + dispatch(setProjectBoardFetch(projectBoard.id)); + } + showNotification({show: true, text: 'Задача создана', type: 'success'}) } }); } @@ -204,10 +213,14 @@ 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..be463075 --- /dev/null +++ b/src/components/Notification/Notification.jsx @@ -0,0 +1,36 @@ +import React from "react"; +import {useDispatch, useSelector} from "react-redux"; + +import { closeNotification, getNotification } from "@redux/outstaffingSlice"; + +import close from "assets/icons/closeProjectPersons.svg"; +import copy from "assets/icons/copyNotification.svg"; +import error from "assets/icons/errorNotification.svg"; +import archive from "assets/icons/archiveNotification.svg"; +import success from "assets/icons/successNotification.svg" + +const images = { + archive: archive, + error: error, + copy: copy, + success: success +} + +import './notification.scss' + +export const Notification = () => { + const dispatch = useDispatch(); + const notificationInfo = useSelector(getNotification) + return ( +
+
+ img +

{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..91751542 100644 --- a/src/components/ProjectTiket/ProjectTiket.jsx +++ b/src/components/ProjectTiket/ProjectTiket.jsx @@ -3,11 +3,13 @@ import { useDispatch } from "react-redux"; import { Link } from "react-router-dom"; import { deleteProject, modalToggle } from "@redux/projectsTrackerSlice"; +import {useNotification} from "@hooks/useNotification"; import { apiRequest } from "@api/request"; import { ModalSelect } from "@components/Modal/ModalSelect/ModalSelect"; import TrackerModal from "@components/Modal/Tracker/TrackerModal/TrackerModal"; +import AcceptModal from "@components/Modal/AcceptModal/AcceptModal" import archiveSet from "assets/icons/archive.svg"; import del from "assets/icons/delete.svg"; @@ -19,7 +21,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 +53,7 @@ export const ProjectTiket = ({ project, index }) => { }, }).then(() => { dispatch(deleteProject(project)); + showNotification({show: true, text: 'Проект успешно была перемещена в архив', type: 'archive'}); }); } @@ -58,6 +63,10 @@ export const ProjectTiket = ({ project, index }) => { ); } + function closeAcceptModal () { + setAcceptModalOpen(false) + } + return (
{project.name} @@ -98,7 +107,10 @@ export const ProjectTiket = ({ project, index }) => {

ссылка на проект

-
+
{ + setModalSelect(false) + setAcceptModalOpen(true) + }}>

в архив

@@ -108,6 +120,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..7a5a3baf --- /dev/null +++ b/src/hooks/useNotification.js @@ -0,0 +1,15 @@ +import { useDispatch } from "react-redux"; + +import { setNotification, closeNotification } 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..1cd2a689 100644 --- a/src/pages/ProjectTracker/ProjectTracker.js +++ b/src/pages/ProjectTracker/ProjectTracker.js @@ -49,6 +49,7 @@ import archive from "assets/images/archiveIcon.png"; import avatarMok from "assets/images/avatarMok.png"; import { getCorrectDate } from "../../components/Calendar/calendarHelper"; +import {useNotification} from "@hooks/useNotification"; export const ProjectTracker = () => { const dispatch = useDispatch(); @@ -72,6 +73,7 @@ export const ProjectTracker = () => { const startWrapperIndexTest = useRef({}); const projectBoard = useSelector(getProjectBoard); const loader = useSelector(getBoarderLoader); + const { showNotification } = useNotification() useEffect(() => { dispatch(activeLoader()); @@ -224,6 +226,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..0f2d9279 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,16 @@ 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;