From 32b5968a046f01af66d9eb820e4434de4ffad385 Mon Sep 17 00:00:00 2001 From: sasanqc Date: Sat, 24 Jun 2023 14:38:04 +0330 Subject: [PATCH] editing task --- components/CreateBoard.tsx | 53 ++++++++++++++++++++++-------- components/CreateTask.tsx | 52 ++++++++++++++++++++---------- components/EditTask.tsx | 15 +++++++++ components/UI/Button.tsx | 14 ++++++-- components/UI/Modal.tsx | 14 ++++---- components/UI/TextInput.tsx | 9 +++++- components/ViewTask.tsx | 10 +++++- data/data.json | 2 +- hooks/useUpdateBoard.ts | 11 +++++-- layout/Header.tsx | 10 ++++-- layout/Sidebar.tsx | 3 +- layout/Tasks.tsx | 26 +++++++-------- model/ModalEnum.tsx | 1 + pages/index.tsx | 64 ++++++++++++++++++++++++++++++++++--- styles/globals.css | 15 +++++++++ 15 files changed, 230 insertions(+), 69 deletions(-) create mode 100644 components/EditTask.tsx diff --git a/components/CreateBoard.tsx b/components/CreateBoard.tsx index 660ae95..5e09420 100644 --- a/components/CreateBoard.tsx +++ b/components/CreateBoard.tsx @@ -3,19 +3,27 @@ import TextInput from "./UI/TextInput"; import CrossIcon from "@/icons/icon-cross.svg"; import Button from "./UI/Button"; import Board from "@/model/Board"; +import { useQuery } from "@tanstack/react-query"; + interface ImperativeInput { error: (message: string) => void; } + interface CreateBoardProps { board?: Board; onCreateBoard: (board: Board) => void; } + const CreateBoard: React.FC = ({ board, onCreateBoard }) => { - const [columns, setColumns] = useState( - board?.columns ? board.columns.map((el) => el.name) : ["", ""] - ); + const initialCols = [ + { name: "", tasks: [] }, + { name: "", tasks: [] }, + ]; + const { data } = useQuery<{ boards: Board[] }>({ queryKey: ["boards"] }); + const [columns, setColumns] = useState(board?.columns || initialCols); const [name, setName] = useState(board?.name || ""); const inputRef = useRef(null); + const columnsRef = useRef>([]); const handleDeleteColumn = (e: MouseEvent, index: number) => { e.stopPropagation(); @@ -25,6 +33,7 @@ const CreateBoard: React.FC = ({ board, onCreateBoard }) => { }; const onChangedName = (e: React.FormEvent) => { + e.stopPropagation(); setName((e.target as HTMLInputElement).value); }; @@ -33,23 +42,36 @@ const CreateBoard: React.FC = ({ board, onCreateBoard }) => { index: number ) => { const updatedColumns = [...columns]; - updatedColumns[index] = (e.target as HTMLInputElement).value; + updatedColumns[index].name = (e.target as HTMLInputElement).value; setColumns(updatedColumns); }; const onCreateNewBoard = () => { + // validate name if (name?.trim().length === 0) { inputRef.current?.error("Can't be empty"); return; } - onCreateBoard({ - name, - columns: columns - .filter((el) => el.length > 0) - .map((el) => { - return { name: el, tasks: [] }; - }), - }); + + //do not let create duplicate boards + if (!board) { + const boardsName = data?.boards.map((el) => el.name.trim().toLowerCase()); + if (boardsName?.includes(name.trim().toLowerCase())) { + inputRef.current?.error("Duplicate name"); + return; + } + } + + //validate columns name + for (let i = 0; i < columns.length; i++) { + const element = columns[i]; + if (element.name.length === 0) { + columnsRef.current[i]?.error("Can't be empty"); + return; + } + } + + onCreateBoard({ name, columns }); }; return (
@@ -78,7 +100,8 @@ const CreateBoard: React.FC = ({ board, onCreateBoard }) => { onChange={(e: React.FormEvent) => handleChangedColumn(e, index) } - value={columns[index]} + ref={(el) => (columnsRef.current[index] = el)} + value={columns[index].name} />
= ({ board, onCreateBoard }) => { type={"small secondary"} classes="w-full" label="+ Add New Column" - onClick={() => setColumns((prev) => [...prev, ""])} + onClick={() => + setColumns((prev) => [...prev, { name: "", tasks: [] }]) + } />
- ))} + {board?.columns?.length === 0 && ( +
+

+ This board is empty. Create a new column to get started. +

+
+ )} ); }; diff --git a/model/ModalEnum.tsx b/model/ModalEnum.tsx index 4faffe1..bdd4966 100644 --- a/model/ModalEnum.tsx +++ b/model/ModalEnum.tsx @@ -5,5 +5,6 @@ enum ModalEnum { CREATE_BOARD = "CREATE_BOARD", DELETE_BOARD = "DELETE_BOARD", DELETE_TASK = "DELETE_TASK", + VIEW_TASK = "VIEW_TASK", } export default ModalEnum; diff --git a/pages/index.tsx b/pages/index.tsx index 533f37a..c32c512 100644 --- a/pages/index.tsx +++ b/pages/index.tsx @@ -26,6 +26,8 @@ import { useUpdateBoard } from "@/hooks/useUpdateBoard"; import ModalEnum from "@/model/ModalEnum"; import Task from "@/model/Task"; +import EditTask from "@/components/EditTask"; + interface HomeProps { prefetchedData: { boards: Board[] }; } @@ -49,7 +51,6 @@ const Home: React.FC = ({ prefetchedData = { boards: [] } }) => { html?.classList.add(localStorage.getItem("theme") || "light"); return () => {}; }, []); - console.log("render home page"); const handleAddNewTask = (task: Task) => { const status = task.status; @@ -84,6 +85,36 @@ const Home: React.FC = ({ prefetchedData = { boards: [] } }) => { } }; + const handleEditTask = (task: Task) => { + const board = { ...boards[activeBoard] }; + + if (openedTask) { + // get a copy of the old task. + const oldTask = + board.columns[openedTask.colIndex].tasks[openedTask.taskIndex]; + + //check if status has changed + const statusIsChanged = oldTask.status !== task.status; + + if (!statusIsChanged) { + board.columns[openedTask.colIndex].tasks[openedTask.taskIndex] = task; + updateBoard({ id: activeBoard.toString(), board }); + return; + } + + // if status has changed remove it from previous col and add it to the new col + board.columns[openedTask.colIndex].tasks.splice(openedTask.taskIndex, 1); + //add the task to target column. first find it's index + const targetColIndex = board.columns.findIndex( + (el: { name: string; tasks: Task[] }) => el.name === task.status + ); + if (targetColIndex > -1) { + board.columns[targetColIndex].tasks.push(task); + } + updateBoard({ id: activeBoard.toString(), board }); + } + }; + const handleChangeTaskStatus = (status: string) => { const board = { ...boards[activeBoard] }; if (openedTask) { @@ -125,10 +156,10 @@ const Home: React.FC = ({ prefetchedData = { boards: [] } }) => { }; return ( -
-
+
+
-
+
@@ -178,7 +209,30 @@ const Home: React.FC = ({ prefetchedData = { boards: [] } }) => { )} - {openedTask && !(activeModal === ModalEnum.DELETE_TASK) && ( + {openedTask && activeModal === ModalEnum.EDIT_TASK && ( + { + dispatch(setActiveModal(undefined)); + dispatch(setOpenedTask(undefined)); + }} + > + { + return { label: col.name, value: col.name }; + } + )} + onEditTask={handleEditTask} + /> + + )} + + {openedTask && activeModal === ModalEnum.VIEW_TASK && ( { dispatch(setActiveModal(undefined)); diff --git a/styles/globals.css b/styles/globals.css index 8a09797..b6bdd86 100644 --- a/styles/globals.css +++ b/styles/globals.css @@ -24,6 +24,21 @@ letter-spacing: 2.4px; } } + .modal-content { @apply w-full md:min-w-[480px] bg-white dark:bg-black2 p-8 rounded-md; } + +.app-container { + @apply flex-1 flex overflow-auto relative; +} +.app-container--visible { + width: calc(100%); + left: 0px; + transition: all 0.5s ease; +} +.app-container--hidden { + width: calc(100% + 300px); + left: -300px; + transition: all 0.5s ease; +}