diff --git a/package-lock.json b/package-lock.json index 78e8437..823fa70 100644 --- a/package-lock.json +++ b/package-lock.json @@ -8,6 +8,7 @@ "name": "short-tracker", "version": "0.1.0", "dependencies": { + "@hello-pangea/dnd": "^16.5.0", "@reduxjs/toolkit": "^1.9.5", "@testing-library/jest-dom": "^5.17.0", "@testing-library/react": "^13.4.0", @@ -4404,6 +4405,24 @@ "integrity": "sha512-OfX7E2oUDYxtBvsuS4e/jSn4Q9Qb6DzgeYtsAdkPZ47znpoNsMgZw0+tVijiv3uGNR6dgNlty6r9rzIzHjtd/A==", "dev": true }, + "node_modules/@hello-pangea/dnd": { + "version": "16.5.0", + "resolved": "https://registry.npmjs.org/@hello-pangea/dnd/-/dnd-16.5.0.tgz", + "integrity": "sha512-n+am6O32jo/CFXciCysz83lPM3I3F58FJw4uS44TceieymcyxQSfzK5OhzPAKrVBZktmuOI6Zim9WABTMtXv4A==", + "dependencies": { + "@babel/runtime": "^7.23.2", + "css-box-model": "^1.2.1", + "memoize-one": "^6.0.0", + "raf-schd": "^4.0.3", + "react-redux": "^8.1.3", + "redux": "^4.2.1", + "use-memo-one": "^1.1.3" + }, + "peerDependencies": { + "react": "^16.8.5 || ^17.0.0 || ^18.0.0", + "react-dom": "^16.8.5 || ^17.0.0 || ^18.0.0" + } + }, "node_modules/@humanwhocodes/config-array": { "version": "0.11.13", "resolved": "https://registry.npmjs.org/@humanwhocodes/config-array/-/config-array-0.11.13.tgz", @@ -15588,6 +15607,14 @@ "postcss": "^8.4" } }, + "node_modules/css-box-model": { + "version": "1.2.1", + "resolved": "https://registry.npmjs.org/css-box-model/-/css-box-model-1.2.1.tgz", + "integrity": "sha512-a7Vr4Q/kd/aw96bnJG332W9V9LkJO69JRcaCYDUqjp6/z0w6VcZjgAcTbgFxEPfBgdnAwlh3iwu+hLopa+flJw==", + "dependencies": { + "tiny-invariant": "^1.0.6" + } + }, "node_modules/css-declaration-sorter": { "version": "6.4.1", "resolved": "https://registry.npmjs.org/css-declaration-sorter/-/css-declaration-sorter-6.4.1.tgz", @@ -24099,6 +24126,11 @@ "node": ">= 4.0.0" } }, + "node_modules/memoize-one": { + "version": "6.0.0", + "resolved": "https://registry.npmjs.org/memoize-one/-/memoize-one-6.0.0.tgz", + "integrity": "sha512-rkpe71W0N0c0Xz6QD0eJETuWAJGnJ9afsl1srmwPrI+yBCkge5EycXXbYRyvL29zZVUWQCY7InPRCv3GDXuZNw==" + }, "node_modules/memoizerific": { "version": "1.11.3", "resolved": "https://registry.npmjs.org/memoizerific/-/memoizerific-1.11.3.tgz", @@ -27071,6 +27103,11 @@ "performance-now": "^2.1.0" } }, + "node_modules/raf-schd": { + "version": "4.0.3", + "resolved": "https://registry.npmjs.org/raf-schd/-/raf-schd-4.0.3.tgz", + "integrity": "sha512-tQkJl2GRWh83ui2DiPTJz9wEiMN20syf+5oKfB03yYP7ioZcJwsIK8FjrtLwH1m7C7e+Tt2yYBlrOpdT+dyeIQ==" + }, "node_modules/ramda": { "version": "0.29.0", "resolved": "https://registry.npmjs.org/ramda/-/ramda-0.29.0.tgz", @@ -30148,8 +30185,7 @@ "node_modules/tiny-invariant": { "version": "1.3.1", "resolved": "https://registry.npmjs.org/tiny-invariant/-/tiny-invariant-1.3.1.tgz", - "integrity": "sha512-AD5ih2NlSssTCwsMznbvwMZpJ1cbhkGd2uueNxzv2jDlEeZdU04JQfRnggJQ8DrcVBGjAsCKwFBbDlVNtEMlzw==", - "dev": true + "integrity": "sha512-AD5ih2NlSssTCwsMznbvwMZpJ1cbhkGd2uueNxzv2jDlEeZdU04JQfRnggJQ8DrcVBGjAsCKwFBbDlVNtEMlzw==" }, "node_modules/tiny-warning": { "version": "1.0.3", @@ -30851,6 +30887,14 @@ } } }, + "node_modules/use-memo-one": { + "version": "1.1.3", + "resolved": "https://registry.npmjs.org/use-memo-one/-/use-memo-one-1.1.3.tgz", + "integrity": "sha512-g66/K7ZQGYrI6dy8GLpVcMsBp4s17xNkYJVSMvTEevGy3nDxHOfE6z8BVE22+5G5x7t3+bhzrlTDB7ObrEE0cQ==", + "peerDependencies": { + "react": "^16.8.0 || ^17.0.0 || ^18.0.0" + } + }, "node_modules/use-resize-observer": { "version": "9.1.0", "resolved": "https://registry.npmjs.org/use-resize-observer/-/use-resize-observer-9.1.0.tgz", diff --git a/package.json b/package.json index 4333ba4..566a800 100644 --- a/package.json +++ b/package.json @@ -4,6 +4,7 @@ "private": true, "homepage": "./", "dependencies": { + "@hello-pangea/dnd": "^16.5.0", "@reduxjs/toolkit": "^1.9.5", "@testing-library/jest-dom": "^5.17.0", "@testing-library/react": "^13.4.0", diff --git a/src/components/Forms/AuthorizationForm/AuthorizationForm.module.scss b/src/components/Forms/AuthorizationForm/AuthorizationForm.module.scss index 231815a..1837a46 100644 --- a/src/components/Forms/AuthorizationForm/AuthorizationForm.module.scss +++ b/src/components/Forms/AuthorizationForm/AuthorizationForm.module.scss @@ -14,8 +14,8 @@ &__container { padding: 32px 32px 12px 32px; width: 100%; - display: flex; - flex-direction: column; + display: grid; + gap: 12px; align-items: center; } &__question { diff --git a/src/components/Task/Task.tsx b/src/components/Task/Task.tsx index 7ef3cd7..6a9f8aa 100644 --- a/src/components/Task/Task.tsx +++ b/src/components/Task/Task.tsx @@ -1,9 +1,12 @@ -import React from 'react'; -import { ClockIcon, CommentsIcon, FlagIcon } from 'ui-lib/Icons'; -import EditButton from 'ui-lib/Buttons/editTaskButton/editTaskButton'; import { TaskEditMenu } from 'components/TaskEditMenu/TaskEditMenu'; -import styles from './Task.module.scss'; +import React, { useEffect } from 'react'; +import { useDispatch } from 'react-redux'; +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'; // содержимое карточки export interface TaskProps { @@ -30,13 +33,33 @@ export const Task: React.FC = ({ completedTime, }) => { const [isMenuOpened, setIsMenuOpened] = React.useState(false); + const dispatch = useDispatch(); + const taskMenuActiveId = useSelector((state) => state.taskMenuActive).value; const handleToggleEditMenu = () => { - setIsMenuOpened(!isMenuOpened); + if (isMenuOpened) { + setIsMenuOpened(false); + dispatch(resetActiveMenu()); + } else { + setIsMenuOpened(true); + dispatch(setActiveMenu(taskID)); + } + }; + + const handleCloseEditMenu = () => { + setIsMenuOpened(false); }; + useEffect(() => { + if (taskMenuActiveId !== taskID && isMenuOpened) { + setIsMenuOpened(false); + } + // eslint-disable-next-line react-hooks/exhaustive-deps + }, [taskMenuActiveId, isMenuOpened]); + const handleKeyDown = (evt: React.KeyboardEvent) => { if (evt.key === 'Enter' || evt.key === ' ') { + evt.preventDefault(); handleToggleEditMenu(); } }; @@ -57,6 +80,7 @@ export const Task: React.FC = ({ isLead={isLead} ownTask={ownTask} handleToggleEditMenu={handleToggleEditMenu} + handleCloseEditMenu={handleCloseEditMenu} status={status} taskID={taskID} performers={performers} diff --git a/src/components/TaskEditMenu/TaskEditMenu.tsx b/src/components/TaskEditMenu/TaskEditMenu.tsx index 2d9f9d0..959f122 100644 --- a/src/components/TaskEditMenu/TaskEditMenu.tsx +++ b/src/components/TaskEditMenu/TaskEditMenu.tsx @@ -2,18 +2,17 @@ import React from 'react'; import { UniversalButton } from 'ui-lib/Buttons'; import { BallpenIcon, TrashIcon } from 'ui-lib/Icons'; -import styles from './TaskEditMenu.module.scss'; -import { useDispatch } from '../../services/hooks'; -import { updateTaskStore } from '../../store/taskSlice'; +import { useClose, useDispatch } from '../../services/hooks'; import updateTaskThunk from '../../thunks/update-task-thunk'; import { TPerformers } from '../../types/types'; -import createTaskThunk from '../../thunks/create-task-thunk'; +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[]; @@ -25,6 +24,7 @@ export const TaskEditMenu: React.FC = ({ isLead, ownTask, handleToggleEditMenu, + handleCloseEditMenu, status, performers, deadlineDate, @@ -32,6 +32,7 @@ export const TaskEditMenu: React.FC = ({ taskID, }) => { const dispatch = useDispatch(); + useClose(styles.container, handleCloseEditMenu); const notOwnLeadTask = isLead && !ownTask; const notOwnPerformerTask = !isLead && !ownTask; diff --git a/src/pages/Main/Lead/Lead.tsx b/src/pages/Main/Lead/Lead.tsx index 9f050bf..e2279b3 100644 --- a/src/pages/Main/Lead/Lead.tsx +++ b/src/pages/Main/Lead/Lead.tsx @@ -5,10 +5,71 @@ 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 { v4 as uuidv4 } from 'uuid'; 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 updateTaskThunk from '../../../thunks/update-task-thunk'; + +interface ITaskSort { + tasksArray: TResults[]; + handleCheckTaskOwner: (performers: TPerformers[]) => boolean; + droppableId: string; +} + +const TaskSort: FC = (props) => { + const { tasksArray, handleCheckTaskOwner, droppableId } = props; + + const getStyle = (style: any, snapshot: any) => { + if (!snapshot.isDragging) return {}; + if (!snapshot.isDropAnimating) { + return style; + } + + return { + ...style, + transitionDuration: '0.001s', + }; + }; + + return ( + + {(provided, snapshot) => ( +
+
+ {tasksArray.map((task, index) => ( + + {/* eslint-disable-next-line @typescript-eslint/no-shadow */} + {(provided, snapshot) => ( +
+ +
+ )} +
+ ))} +
+
+ )} +
+ ); +}; interface ITaskCard { allTasks: TTask; @@ -68,6 +129,40 @@ const Lead: FC = (props) => { // eslint-disable-next-line react-hooks/exhaustive-deps }, [results]); + const onDragEnd = (result: any) => { + const { source, destination, draggableId } = result; + + // dropped outside the list + if (!destination) { + return; + } + const sInd = source.droppableId; + const dInd = destination.droppableId; + const itemIndex = allTasks.results.findIndex( + (elem) => elem.id === Number(draggableId) + ); + const item = allTasks.results[itemIndex]; + + const updateTaskStatus = () => { + dispatch( + updateTaskThunk({ + id: draggableId, + data: { + description: item.description, + status: dInd, + deadline_date: item.deadline_date, + // performers, + performers: [2], + }, + }) + ); + }; + + if (!(sInd === dInd)) { + updateTaskStatus(); + } + }; + return (
@@ -80,78 +175,28 @@ const Lead: FC = (props) => {
-
- {todoTasks.map((task) => ( - - ))} -
-
- {inProgressTasks.map((task) => ( - - ))} -
-
- {doneTasks.map((task) => ( - - ))} -
-
- {holdTasks.map((task) => ( - - ))} -
+ + + + + +
diff --git a/src/pages/Tasks/Tasks.tsx b/src/pages/Tasks/Tasks.tsx index 094a4eb..36413a7 100644 --- a/src/pages/Tasks/Tasks.tsx +++ b/src/pages/Tasks/Tasks.tsx @@ -1,5 +1,6 @@ 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'; @@ -22,7 +23,8 @@ const Tasks: FC = (props) => { return (
{[props].map(() => ( - + // eslint-disable-next-line react/destructuring-assignment + ))}
); diff --git a/src/store/store.ts b/src/store/store.ts index 2315bf9..fa2e5b0 100644 --- a/src/store/store.ts +++ b/src/store/store.ts @@ -1,11 +1,12 @@ import { configureStore } from '@reduxjs/toolkit'; import thunk from 'redux-thunk'; +import createTaskReducer from './createTaskSlice'; +import modalReducer from './modalSlice'; import systemReducer from './systemSlice'; -import userReducer from './userSlice'; +import { taskMenuActiveReducer } from './taskMenuActiveSlice'; import taskReducer from './taskSlice'; +import userReducer from './userSlice'; import usersReducer from './usersSlice'; -import modalReducer from './modalSlice'; -import createTaskReducer from './createTaskSlice'; const store = configureStore({ reducer: { @@ -15,6 +16,7 @@ const store = configureStore({ users: usersReducer, modals: modalReducer, createTask: createTaskReducer, + taskMenuActive: taskMenuActiveReducer, }, middleware: (getDefaultMiddleware) => getDefaultMiddleware().concat(thunk), // eslint-disable-next-line no-undef diff --git a/src/store/taskMenuActiveSlice.ts b/src/store/taskMenuActiveSlice.ts new file mode 100644 index 0000000..c9e0a0b --- /dev/null +++ b/src/store/taskMenuActiveSlice.ts @@ -0,0 +1,19 @@ +import { createSlice } from '@reduxjs/toolkit'; + +const initialState = { value: -1 }; + +const taskMenuActiveSlice = createSlice({ + name: 'taskMenuActive', + initialState, + reducers: { + setActiveMenu: (state, { payload }: { payload: number }) => { + state.value = payload; + }, + resetActiveMenu: (state) => { + state.value = initialState.value; + }, + }, +}); + +export const taskMenuActiveReducer = taskMenuActiveSlice.reducer; +export const { setActiveMenu, resetActiveMenu } = taskMenuActiveSlice.actions; diff --git a/src/store/taskSlice.ts b/src/store/taskSlice.ts index 1696938..fb22523 100644 --- a/src/store/taskSlice.ts +++ b/src/store/taskSlice.ts @@ -53,7 +53,7 @@ const taskSlice = createSlice({ }), updateTaskStore(state, action: PayloadAction) { const elemIndex = current(state).results.findIndex( - (elem) => elem.id === action.payload.id + (elem) => elem.id === Number(action.payload.id) ); state.results[elemIndex].status = action.payload.res.status; }, diff --git a/src/ui-lib/Buttons/UniversalButton/UniversalButton.module.scss b/src/ui-lib/Buttons/UniversalButton/UniversalButton.module.scss index a1274b4..ff2b368 100644 --- a/src/ui-lib/Buttons/UniversalButton/UniversalButton.module.scss +++ b/src/ui-lib/Buttons/UniversalButton/UniversalButton.module.scss @@ -12,7 +12,7 @@ background: v.$accent-color; color: var(--white, v.$white-color); - @include m.ButtonLarge; + @include m.Button; @include m.noselect; &:hover { @@ -26,7 +26,7 @@ } .emptyButton { - @include m.ButtonLarge; + @include m.Button; @include m.noselect; display: flex; @@ -39,7 +39,7 @@ border-radius: 8px; border: 1px solid var(--accent, v.$accent-color); - background-color: v.$white-color; + background-color: transparent; color: v.$accent-color; white-space: nowrap; diff --git a/src/ui-lib/Buttons/UniversalButton/UniversalButton.tsx b/src/ui-lib/Buttons/UniversalButton/UniversalButton.tsx index 3063a83..0d867e0 100644 --- a/src/ui-lib/Buttons/UniversalButton/UniversalButton.tsx +++ b/src/ui-lib/Buttons/UniversalButton/UniversalButton.tsx @@ -35,7 +35,7 @@ UniversalButton.defaultProps = { width: 256, height: 40, className: '', - fontSize: 18, + fontSize: 12, }; export default UniversalButton; diff --git a/src/ui-lib/Inputs/PasswordInput/PasswordInput.module.scss b/src/ui-lib/Inputs/PasswordInput/PasswordInput.module.scss index 61fbf1e..f960bd8 100644 --- a/src/ui-lib/Inputs/PasswordInput/PasswordInput.module.scss +++ b/src/ui-lib/Inputs/PasswordInput/PasswordInput.module.scss @@ -2,5 +2,4 @@ width: 100%; display: flex; justify-content: center; - margin-top: 12px; } diff --git a/src/ui-lib/Inputs/UniversalInput/UniversalInput.module.scss b/src/ui-lib/Inputs/UniversalInput/UniversalInput.module.scss index 23645e0..5f3aeb7 100644 --- a/src/ui-lib/Inputs/UniversalInput/UniversalInput.module.scss +++ b/src/ui-lib/Inputs/UniversalInput/UniversalInput.module.scss @@ -7,7 +7,7 @@ flex-shrink: 0; } .label { - margin-bottom: 8px; + margin-bottom: 4px; @include m.S1; color: v.$name-task; }