diff --git a/src/components/SideBar/SideBar.tsx b/src/components/SideBar/SideBar.tsx index 036954e..54a5229 100644 --- a/src/components/SideBar/SideBar.tsx +++ b/src/components/SideBar/SideBar.tsx @@ -12,8 +12,9 @@ import { lid } from 'assets/images'; // Initial image import SideBarUser from 'components/SideBarUser/SideBarUser'; import SideBarUserMenu from 'components/SideBarUserMenu/SideBarUserMenu'; import NewEmployeePopup from 'components/Popup/NewEmployee/NewEmployee'; -import React, { useState } from 'react'; -import { useSelector } from 'services/hooks'; +import React, { KeyboardEvent, useState } from 'react'; +import { useDispatch, useSelector } from 'services/hooks'; +import { getAllTeamTasks } from 'store/tasksOfUserSlice'; import { TTask } from 'types/types'; import { v4 as uuidv4 } from 'uuid'; import styles from './SideBar.module.scss'; @@ -22,7 +23,7 @@ const SideBar: React.FC = () => { const currentUser = useSelector((state) => state.user); const tasks: TTask = useSelector((state) => state.task); const currentUsers = useSelector((state) => state.users); - + const dispatch = useDispatch(); const [isSidebarMenuOpen, setisSidebarMenuOpen] = useState(false); const [isNewEmployeePopupOpen, setIsNewEmployeePopupOpen] = useState(false); @@ -36,6 +37,13 @@ const SideBar: React.FC = () => { const handleCancel = () => { setIsNewEmployeePopupOpen(false); + + const showAllTasks = () => dispatch(getAllTeamTasks()); + const handleKeyDown = (evt: KeyboardEvent) => { + if (evt.key === 'Enter' || evt.key === ' ') { + evt.preventDefault(); + showAllTasks(); + } }; return ( @@ -91,10 +99,16 @@ const SideBar: React.FC = () => { {isNewEmployeePopupOpen && } diff --git a/src/components/SideBarUser/SideBarUser.tsx b/src/components/SideBarUser/SideBarUser.tsx index 48d18d6..f2e80dc 100644 --- a/src/components/SideBarUser/SideBarUser.tsx +++ b/src/components/SideBarUser/SideBarUser.tsx @@ -1,16 +1,29 @@ import { lid } from 'assets/images'; // Initial image -import React from 'react'; +import React, { KeyboardEvent } from 'react'; +import { useDispatch } from 'services/hooks'; +import { getUserTasks } from 'store/tasksOfUserSlice'; import styles from './SidebarUser.module.scss'; type Tprops = { fullName: string; + id: number; }; -const SideBarUser: React.FC = ({ fullName }) => { +const SideBarUser: React.FC = ({ fullName, id }) => { + const dispatch = useDispatch(); + const getTasks = () => dispatch(getUserTasks({ id })); + const handleKeyDown = (evt: KeyboardEvent) => { + if (evt.key === 'Enter' || evt.key === ' ') { + evt.preventDefault(); + getTasks(); + } + }; return (
  • Изображение пользователя - +
  • ); }; diff --git a/src/components/Task/Task.tsx b/src/components/Task/Task.tsx index 6a9f8aa..7f34ac9 100644 --- a/src/components/Task/Task.tsx +++ b/src/components/Task/Task.tsx @@ -5,7 +5,6 @@ import { useSelector } from 'services/hooks'; import { resetActiveMenu, setActiveMenu } from 'store/taskMenuActiveSlice'; import EditButton from 'ui-lib/Buttons/editTaskButton/editTaskButton'; import { ClockIcon, CommentsIcon, FlagIcon } from 'ui-lib/Icons'; -import { TPerformers } from '../../types/types'; import styles from './Task.module.scss'; // содержимое карточки @@ -13,24 +12,26 @@ export interface TaskProps { text: string; date: string; headerText: string; - isLead: boolean | null; ownTask: boolean; status: string; taskID: number; - performers: TPerformers[]; completedTime: string; + taskCreatorId: number; + isCurrentUserLead: boolean; + currentUserId: number; } export const Task: React.FC = ({ text, date, headerText, - isLead, ownTask, status, taskID, - performers, completedTime, + taskCreatorId, + isCurrentUserLead, + currentUserId, }) => { const [isMenuOpened, setIsMenuOpened] = React.useState(false); const dispatch = useDispatch(); @@ -67,7 +68,7 @@ export const Task: React.FC = ({ return (
    {/* Шапка отображается только для лида для не своей задачи */} - {isLead && !ownTask && ( + {isCurrentUserLead && currentUserId !== taskCreatorId && (

    {headerText}

    @@ -77,15 +78,16 @@ export const Task: React.FC = ({ {/* Открытие меню редактирования */} {isMenuOpened && ( )} diff --git a/src/components/TaskEditMenu/TaskEditMenu.tsx b/src/components/TaskEditMenu/TaskEditMenu.tsx index 959f122..420c613 100644 --- a/src/components/TaskEditMenu/TaskEditMenu.tsx +++ b/src/components/TaskEditMenu/TaskEditMenu.tsx @@ -1,42 +1,54 @@ /* eslint-disable jsx-a11y/label-has-associated-control */ import React from 'react'; +import { TaskStatus } from 'types/types'; import { UniversalButton } from 'ui-lib/Buttons'; import { BallpenIcon, TrashIcon } from 'ui-lib/Icons'; import { useClose, useDispatch } from '../../services/hooks'; import updateTaskThunk from '../../thunks/update-task-thunk'; -import { TPerformers } from '../../types/types'; import styles from './TaskEditMenu.module.scss'; // import { changeTaskStatus } from '../../store/taskSlice'; export interface TaskEditMenuProps { - isLead: boolean | null; ownTask: boolean; handleToggleEditMenu: () => void; handleCloseEditMenu: () => void; status: string; taskID: number; - performers: TPerformers[]; deadlineDate: string; description: string; + taskCreatorId: number; + isCurrentUserLead: boolean; + currentUserId: number; } export const TaskEditMenu: React.FC = ({ - isLead, ownTask, handleToggleEditMenu, handleCloseEditMenu, status, - performers, deadlineDate, description, taskID, + taskCreatorId, + isCurrentUserLead, + currentUserId, }) => { + const [currentStatus, setStatus] = React.useState(status); const dispatch = useDispatch(); useClose(styles.container, handleCloseEditMenu); - const notOwnLeadTask = isLead && !ownTask; - const notOwnPerformerTask = !isLead && !ownTask; - const [currentStatus, setStatus] = React.useState(status); + const isTaskEditable = currentUserId === taskCreatorId; + const isStatusEditable = isTaskEditable || ownTask; + + const isArchivedVisible = + status === TaskStatus.ARCHIVED || (status === TaskStatus.DONE && isCurrentUserLead); + const isHoldVisible = status === TaskStatus.HOLD || status === TaskStatus.IN_PROGRESS; + const isDoneVisible = status === TaskStatus.DONE || status === TaskStatus.IN_PROGRESS; + const isInProgressVisible = + status === TaskStatus.IN_PROGRESS || status === TaskStatus.TO_DO; + const isToDoVisible = + status === TaskStatus.TO_DO || (status === TaskStatus.ARCHIVED && isCurrentUserLead); + const onChange = (evt: React.ChangeEvent) => { setStatus(evt.target.value); }; @@ -66,8 +78,7 @@ export const TaskEditMenu: React.FC = ({
    {/* Кнопки отображаются у лида для задачи сотруднику, */} {/* а также у лида и сотрудника для своей задачи */} - {/* как применить условие ИЛИ? */} - {(notOwnLeadTask || ownTask) && ( + {isTaskEditable && (
    = ({ Редактировать
    -
    - - Удалить -
    + {(status === TaskStatus.DONE || status === TaskStatus.ARCHIVED) && ( +
    + + Удалить +
    + )}
    )} {/* Смена статуса отображаются для своих задач */} {/* и задачи у сотрудника от лида */} - {(notOwnPerformerTask || ownTask) && ( + {isStatusEditable && (
    - - - - {/* */} - + {isToDoVisible && ( + + )} + {isInProgressVisible && ( + + )} + {isDoneVisible && ( + + )} + {isHoldVisible && ( + + )} + {isArchivedVisible && ( + + )}

    Переместить

    diff --git a/src/pages/Main/Lead/Lead.tsx b/src/pages/Main/Lead/Lead.tsx index e2279b3..089cedc 100644 --- a/src/pages/Main/Lead/Lead.tsx +++ b/src/pages/Main/Lead/Lead.tsx @@ -1,24 +1,27 @@ +import { DragDropContext, Draggable, Droppable } from '@hello-pangea/dnd'; import Search from 'components/Search/Search'; import SideBar from 'components/SideBar/SideBar'; -import Tasks from 'pages/Tasks/Tasks'; -import React, { FC, useEffect, useState } from 'react'; -import { UniversalButton } from 'ui-lib/Buttons'; import Status from 'components/Status/Status'; -import { TPerformers, TResults, TTask } from 'types/types'; +import Tasks from 'pages/Tasks/Tasks'; +import { FC, useEffect, useMemo, useState } from 'react'; +import { handleCheckIfTaskForMe } from 'services/functions'; import { useDispatch, useSelector } from 'services/hooks'; import { openCreateTaskModal } from 'store'; -import { DragDropContext, Droppable, Draggable } from '@hello-pangea/dnd'; -import styles from './Lead.module.scss'; +import { resetActiveMenu } from 'store/taskMenuActiveSlice'; +import { TResults, TTask, TaskStatus } from 'types/types'; +import { UniversalButton } from 'ui-lib/Buttons'; import updateTaskThunk from '../../../thunks/update-task-thunk'; +import styles from './Lead.module.scss'; interface ITaskSort { tasksArray: TResults[]; - handleCheckTaskOwner: (performers: TPerformers[]) => boolean; droppableId: string; } -const TaskSort: FC = (props) => { - const { tasksArray, handleCheckTaskOwner, droppableId } = props; +const TaskSort: FC = ({ tasksArray, droppableId }) => { + const { is_team_lead: isCurrentUserLead, id: currentUserId } = useSelector( + (state) => state.user + ); const getStyle = (style: any, snapshot: any) => { if (!snapshot.isDragging) return {}; @@ -48,17 +51,18 @@ const TaskSort: FC = (props) => { style={getStyle(provided.draggableProps.style, snapshot)} >
    )} @@ -75,12 +79,19 @@ interface ITaskCard { allTasks: TTask; } -const Lead: FC = (props) => { - const { allTasks } = props; +const Lead: FC = ({ allTasks }) => { const { count, results } = allTasks; const dispatch = useDispatch(); const currentUser = useSelector((state) => state.user); - + const tasksOfUserId = useSelector((state) => state.tasksOfUser).id; + const resultsToRender = useMemo( + () => + tasksOfUserId !== -1 + ? results.filter((task) => handleCheckIfTaskForMe(tasksOfUserId, task.performers)) + : results, + [results, tasksOfUserId] + ); + console.log(resultsToRender); const openCreateTask = () => { dispatch(openCreateTaskModal()); }; @@ -97,17 +108,17 @@ const Lead: FC = (props) => { const done: TResults[] = []; const hold: TResults[] = []; // eslint-disable-next-line no-plusplus - for (let i = 0; i < results.length; i++) { - if (results[i].status === 'to do') { - todo.push(results[i]); + for (let i = 0; i < resultsToRender.length; i++) { + if (resultsToRender[i].status === TaskStatus.TO_DO) { + todo.push(resultsToRender[i]); } - if (results[i].status === 'in progress') { - inProgress.push(results[i]); + if (resultsToRender[i].status === TaskStatus.IN_PROGRESS) { + inProgress.push(resultsToRender[i]); } - if (results[i].status === 'done') { - done.push(results[i]); + if (resultsToRender[i].status === TaskStatus.DONE) { + done.push(resultsToRender[i]); } - if (results[i].status === 'hold') { + if (resultsToRender[i].status === TaskStatus.HOLD) { hold.push(results[i]); } } @@ -116,18 +127,13 @@ const Lead: FC = (props) => { setDoneTasks(done); setHoldTasks(hold); }; - // Проверка есть ли текущий пользователь в списке - const handleCheckTaskOwner = (performers: TPerformers[]) => { - const res = performers.filter((user) => user.full_name === currentUser.full_name); - return res.length > 0; - }; useEffect(() => { - if (results.length >= 1) { - parseTasks(); - } + parseTasks(); // eslint-disable-next-line react-hooks/exhaustive-deps - }, [results]); + }, [resultsToRender]); + + const onDragStart = () => dispatch(resetActiveMenu()); const onDragEnd = (result: any) => { const { source, destination, draggableId } = result; @@ -143,6 +149,28 @@ const Lead: FC = (props) => { ); const item = allTasks.results[itemIndex]; + const { status } = item; + const isCurrentUserLead = currentUser.is_team_lead; + + const canDragToInProgress = + status === TaskStatus.TO_DO && dInd === TaskStatus.IN_PROGRESS; + const canDragToToDo = + status === TaskStatus.ARCHIVED && isCurrentUserLead && dInd === TaskStatus.TO_DO; + const canDragToDone = status === TaskStatus.IN_PROGRESS && dInd === TaskStatus.DONE; + const canDragToHold = status === TaskStatus.IN_PROGRESS && dInd === TaskStatus.HOLD; + const canDragToArchived = + status === TaskStatus.DONE && isCurrentUserLead && dInd === TaskStatus.ARCHIVED; + + if ( + !canDragToToDo && + !canDragToInProgress && + !canDragToDone && + !canDragToHold && + !canDragToArchived + ) { + return; + } + const updateTaskStatus = () => { dispatch( updateTaskThunk({ @@ -175,27 +203,11 @@ const Lead: FC = (props) => {
    - - - - - + + + + +
    diff --git a/src/pages/Main/Main.tsx b/src/pages/Main/Main.tsx index 5f9bc41..5d2cfaa 100644 --- a/src/pages/Main/Main.tsx +++ b/src/pages/Main/Main.tsx @@ -12,7 +12,7 @@ const Main = () => { const [isUpdated, setIsUpdated] = useState(false); const dispatch = useDispatch(); const { isLoggedIn } = useSelector((state) => state.system); - const isLead = useSelector((state) => state.user); + const isLead = useSelector((state) => state.user).is_team_lead; const tasks: TTask = useSelector((state) => state.task); useEffect(() => { diff --git a/src/pages/Tasks/Tasks.tsx b/src/pages/Tasks/Tasks.tsx index 36413a7..c2b7426 100644 --- a/src/pages/Tasks/Tasks.tsx +++ b/src/pages/Tasks/Tasks.tsx @@ -1,9 +1,7 @@ import { FC } from 'react'; -import { v4 as uuidv4 } from 'uuid'; -import { DragDropContext, Droppable, Draggable } from '@hello-pangea/dnd'; import styles from './Tasks.module.scss'; -import { TaskProps, Task } from '../../components/Task/Task'; +import { Task, TaskProps } from '../../components/Task/Task'; import { TaskStateProps } from '../../components/TaskState/TaskState'; const Tasks: FC = (props) => { diff --git a/src/services/functions.ts b/src/services/functions.ts new file mode 100644 index 0000000..3adc7e5 --- /dev/null +++ b/src/services/functions.ts @@ -0,0 +1,7 @@ +import { TPerformers } from 'types/types'; + +// Проверка есть ли текущий пользователь в списке +export const handleCheckIfTaskForMe = (id: number, performers: TPerformers[]) => { + const res = performers.filter((user) => user.id === id); + return res.length > 0; +}; diff --git a/src/store/store.ts b/src/store/store.ts index fa2e5b0..0fccff4 100644 --- a/src/store/store.ts +++ b/src/store/store.ts @@ -5,6 +5,7 @@ import modalReducer from './modalSlice'; import systemReducer from './systemSlice'; import { taskMenuActiveReducer } from './taskMenuActiveSlice'; import taskReducer from './taskSlice'; +import { tasksOfUserSliceReducer } from './tasksOfUserSlice'; import userReducer from './userSlice'; import usersReducer from './usersSlice'; @@ -17,6 +18,7 @@ const store = configureStore({ modals: modalReducer, createTask: createTaskReducer, taskMenuActive: taskMenuActiveReducer, + tasksOfUser: tasksOfUserSliceReducer, }, middleware: (getDefaultMiddleware) => getDefaultMiddleware().concat(thunk), // eslint-disable-next-line no-undef diff --git a/src/store/tasksOfUserSlice.ts b/src/store/tasksOfUserSlice.ts new file mode 100644 index 0000000..7859cf9 --- /dev/null +++ b/src/store/tasksOfUserSlice.ts @@ -0,0 +1,19 @@ +import { createSlice } from '@reduxjs/toolkit'; + +const initialState = { id: -1 }; + +const tasksOfUserSlice = createSlice({ + name: 'tasksOfUser', + initialState, + reducers: { + getUserTasks: (state, { payload: { id } }: { payload: { id: number } }) => { + state.id = id; + }, + getAllTeamTasks: (state) => { + state.id = initialState.id; + }, + }, +}); + +export const tasksOfUserSliceReducer = tasksOfUserSlice.reducer; +export const { getUserTasks, getAllTeamTasks } = tasksOfUserSlice.actions; diff --git a/src/types/types.ts b/src/types/types.ts index f56b7bf..a8058ce 100644 --- a/src/types/types.ts +++ b/src/types/types.ts @@ -21,7 +21,7 @@ export type TUser2 = { last_name: string; telegram_nickname: string; username: string | null; - id: string; + id: number; }; export type Tusers = { @@ -80,3 +80,11 @@ export type TCreateTask = { link: string; performers: number[]; }; + +export enum TaskStatus { + TO_DO = 'to do', + IN_PROGRESS = 'in progress', + HOLD = 'hold', + DONE = 'done', + ARCHIVED = 'archived', +}