Skip to content

Commit

Permalink
editing task
Browse files Browse the repository at this point in the history
  • Loading branch information
sasanqc committed Jun 24, 2023
1 parent 1739c27 commit 32b5968
Show file tree
Hide file tree
Showing 15 changed files with 230 additions and 69 deletions.
53 changes: 39 additions & 14 deletions components/CreateBoard.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -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<CreateBoardProps> = ({ 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<ImperativeInput>(null);
const columnsRef = useRef<Array<ImperativeInput | null>>([]);

const handleDeleteColumn = (e: MouseEvent, index: number) => {
e.stopPropagation();
Expand All @@ -25,6 +33,7 @@ const CreateBoard: React.FC<CreateBoardProps> = ({ board, onCreateBoard }) => {
};

const onChangedName = (e: React.FormEvent<HTMLInputElement>) => {
e.stopPropagation();
setName((e.target as HTMLInputElement).value);
};

Expand All @@ -33,23 +42,36 @@ const CreateBoard: React.FC<CreateBoardProps> = ({ 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 (
<div className="modal-content space-y-6 ">
Expand Down Expand Up @@ -78,7 +100,8 @@ const CreateBoard: React.FC<CreateBoardProps> = ({ board, onCreateBoard }) => {
onChange={(e: React.FormEvent<HTMLInputElement>) =>
handleChangedColumn(e, index)
}
value={columns[index]}
ref={(el) => (columnsRef.current[index] = el)}
value={columns[index].name}
/>
<div className="ml-4 cursor-pointer">
<CrossIcon
Expand All @@ -93,7 +116,9 @@ const CreateBoard: React.FC<CreateBoardProps> = ({ board, onCreateBoard }) => {
type={"small secondary"}
classes="w-full"
label="+ Add New Column"
onClick={() => setColumns((prev) => [...prev, ""])}
onClick={() =>
setColumns((prev) => [...prev, { name: "", tasks: [] }])
}
/>
</div>
<Button
Expand Down
52 changes: 35 additions & 17 deletions components/CreateTask.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -4,6 +4,7 @@ import CrossIcon from "@/icons/icon-cross.svg";
import Select from "./UI/Select";
import TextInput from "./UI/TextInput";
import Task from "@/model/Task";

interface CreateTaskProps {
task?: Task;
columns: { label: string; value: string }[];
Expand All @@ -17,14 +18,19 @@ const CreateTask: React.FC<CreateTaskProps> = ({
columns,
onCreateTask,
}) => {
const [subtasks, setSubtasks] = useState(
task?.subtasks ? task.subtasks.map((el) => el.title) : ["", ""]
);
const initialSubTasks = [
{ title: "", isCompleted: false },
{ title: "", isCompleted: false },
];
const [subtasks, setSubtasks] = useState(task?.subtasks || initialSubTasks);
const [title, setTitle] = useState(task?.title || "");
const [description, setDescription] = useState("");
const [status, setStatus] = useState(columns[0].value);
const inputRef = useRef<ImperativeInput>(null);

const [description, setDescription] = useState(task?.description || "");
const [status, setStatus] = useState(
columns.find((el) => el.value === task?.status)?.value || columns[0].value
);
const inputRef = useRef<ImperativeInput>(null);
const columnsRef = useRef<Array<ImperativeInput | null>>([]);
const onChangedTitle = (e: React.FormEvent<HTMLInputElement>) => {
setTitle((e.target as HTMLInputElement).value);
};
Expand All @@ -33,7 +39,7 @@ const CreateTask: React.FC<CreateTaskProps> = ({
index: number
) => {
const updatedColumns = [...subtasks];
updatedColumns[index] = (e.target as HTMLInputElement).value;
updatedColumns[index].title = (e.target as HTMLInputElement).value;
setSubtasks(updatedColumns);
};

Expand All @@ -49,22 +55,27 @@ const CreateTask: React.FC<CreateTaskProps> = ({
inputRef.current?.error("Can't be empty");
return;
}

//validate subtasks title
for (let i = 0; i < subtasks.length; i++) {
const element = subtasks[i];
if (element.title.length === 0) {
columnsRef.current[i]?.error("Can't be empty");
return;
}
}
onCreateTask({
title,
description,
status,
subtasks: subtasks
.filter((el) => el.length > 0)
.map((el) => {
return { title: el, isCompleted: false };
}),
subtasks,
});
};

return (
<div className="modal-content">
<h2 className="text-black4 mb-6 dark:text-white">Add New Task</h2>
<h2 className="text-black4 mb-6 dark:text-white">
{task ? "Edit Task" : "Add New Task"}
</h2>
<div className="space-y-6">
<TextInput
label={"Title"}
Expand Down Expand Up @@ -107,7 +118,8 @@ const CreateTask: React.FC<CreateTaskProps> = ({
onChange={(e: React.FormEvent<HTMLInputElement>) =>
handleChangedSubtask(e, index)
}
value={subtasks[index]}
ref={(el) => (columnsRef.current[index] = el)}
value={subtasks[index].title}
/>
<div className="ml-4 cursor-pointer">
<CrossIcon
Expand All @@ -121,18 +133,24 @@ const CreateTask: React.FC<CreateTaskProps> = ({
type={"small secondary"}
classes="w-full"
label="+ Add New Subtask"
onClick={() => setSubtasks((prev) => [...prev, ""])}
onClick={() =>
setSubtasks((prev) => [
...prev,
{ title: "", isCompleted: false },
])
}
/>
</div>

<Select
label={"Status"}
items={columns}
onChanged={(value) => setStatus(value)}
initialItem={columns.findIndex((el) => el.value === status)}
/>

<Button
label="Create Task"
label={task ? "Edit Task" : "Create Task"}
onClick={handleSubmitTask}
classes={"w-full"}
type={"primary small"}
Expand Down
15 changes: 15 additions & 0 deletions components/EditTask.tsx
Original file line number Diff line number Diff line change
@@ -0,0 +1,15 @@
import Task from "@/model/Task";
import React from "react";
import CreateTask from "./CreateTask";

interface EditTaskProps {
task: Task;
columns: { label: string; value: string }[];
onEditTask: (task: Task) => void;
}

const EditTask: React.FC<EditTaskProps> = ({ task, onEditTask, columns }) => {
return <CreateTask task={task} onCreateTask={onEditTask} columns={columns} />;
};

export default EditTask;
14 changes: 12 additions & 2 deletions components/UI/Button.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -3,11 +3,21 @@ interface ButtonProps {
classes?: string;
label: string;
type: string;
disabled?: boolean;
}
const Button: React.FC<ButtonProps> = ({ label, type, classes, onClick }) => {
const Button: React.FC<ButtonProps> = ({
label,
type,
classes,
onClick,
disabled,
}) => {
return (
<button
className={`text-lg rounded-full px-6 font-bold cursor-pointer transition-colors ${classes} ${
disabled={disabled}
className={`text-lg rounded-full px-6 font-bold transition-colors ${
disabled ? "opacity-25 cursor-not-allowed" : "cursor-pointer"
} ${classes} ${
type?.includes("primary")
? "text-white bg-primary2 hover:bg-primary1"
: ""
Expand Down
14 changes: 6 additions & 8 deletions components/UI/Modal.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -12,14 +12,12 @@ const Modal: React.FC<ModalProps> = ({
}) => {
const modalRef = useRef<HTMLDivElement>(null);

const backDropHandler = useCallback(
(e: MouseEvent) => {
if (!modalRef?.current?.contains(e.target as Node)) {
onClickBackdrop();
}
},
[onClickBackdrop]
);
const backDropHandler = useCallback((e: MouseEvent) => {
if (!modalRef?.current?.contains(e.target as Node)) {
console.log("on Clicked Backdrop");
onClickBackdrop();
}
}, []);

useEffect(() => {
setTimeout(() => {
Expand Down
9 changes: 8 additions & 1 deletion components/UI/TextInput.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -28,6 +28,13 @@ const TextInput = forwardRef<ImperativeInput, TextInputProps>(
},
}));

const handleChange = (e: React.FormEvent<HTMLInputElement>) => {
e.stopPropagation();
if (error) {
setError("");
}
onChange(e);
};
return (
<div className="w-full">
{label && (
Expand All @@ -51,7 +58,7 @@ const TextInput = forwardRef<ImperativeInput, TextInputProps>(
: "dark:border-black1 border-black border-opacity-25"
}`}
placeholder={placeholder}
onChange={onChange}
onChange={handleChange}
required
/>
{error && (
Expand Down
10 changes: 9 additions & 1 deletion components/ViewTask.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -3,6 +3,9 @@ import Select from "./UI/Select";
import Checkbox from "./UI/Checkbox";
import Task from "@/model/Task";
import Dropdown from "./UI/Dropdown";
import { useDispatch } from "react-redux";
import { setActiveModal } from "@/store/uiSlice";
import ModalEnum from "@/model/ModalEnum";

interface ViewTaskProps {
task: Task;
Expand All @@ -19,6 +22,8 @@ const ViewTask: React.FC<ViewTaskProps> = ({
handleChangeTaskStatus,
onDeleteTask,
}) => {
const dispatch = useDispatch();

const [editedTask, setEditedTask] = useState(task);

const onChangeSubtask = (index: number, isComleted: boolean) => {
Expand All @@ -34,6 +39,9 @@ const ViewTask: React.FC<ViewTaskProps> = ({
setEditedTask(updatedTask);
handleChangeTaskStatus(value);
};
const onClickEditTask = () => {
dispatch(setActiveModal(ModalEnum.EDIT_TASK));
};
useEffect(() => {
setEditedTask(task);
return () => {};
Expand All @@ -47,7 +55,7 @@ const ViewTask: React.FC<ViewTaskProps> = ({
items={[
{
label: "Edit Task",
onClick: () => {},
onClick: onClickEditTask,
className: "text-gray3",
},
{
Expand Down
2 changes: 1 addition & 1 deletion data/data.json
Original file line number Diff line number Diff line change
@@ -1 +1 @@
{"boards":[{"name":"Marketing Plan","columns":[{"name":"Todo","tasks":[]},{"name":"Doing","tasks":[]},{"name":"Done","tasks":[]}]},{"name":"Roadmap","columns":[{"name":"Now","tasks":[]},{"name":"Next","tasks":[]},{"name":"Later","tasks":[]}]},{"name":"fhdf","columns":[{"name":"fghfdh","tasks":[]}]},{"name":"fdsss","columns":[]}]}
{"boards":[{"name":"Marketing Plan","columns":[{"name":"Todo","tasks":[]},{"name":"Doing","tasks":[]},{"name":"Done","tasks":[]}]},{"name":"Roadmap","columns":[{"name":"Now","tasks":[]},{"name":"Next","tasks":[]},{"name":"Later","tasks":[]}]},{"name":"fhdf","columns":[{"name":"fghfdh","tasks":[]}]},{"name":"fdsss","columns":[{"name":"sww2222 dsfsdf","tasks":[{"title":"this is not a simple task ","description":"test desc edited","status":"sww2222 dsfsdf","subtasks":[{"title":"sub t2","isCompleted":true},{"title":"give a name","isCompleted":true}]}]},{"name":"qwesdf fsdf","tasks":[]}]},{"name":"sf","columns":[{"name":"wew","tasks":[]},{"name":"wew","tasks":[]}]},{"name":"safir","columns":[{"name":"col1","tasks":[]},{"name":"col2","tasks":[]},{"name":"sdfsf","tasks":[]},{"name":"sdfsdfsf","tasks":[]},{"name":"sdfsfsf","tasks":[]},{"name":"sfsdfsdfsfsfsf","tasks":[]}]}]}
11 changes: 8 additions & 3 deletions hooks/useUpdateBoard.ts
Original file line number Diff line number Diff line change
@@ -1,18 +1,23 @@
import Board from "@/model/Board";
import ModalEnum from "@/model/ModalEnum";
import { updateBoardApi } from "@/services/apiBoards";
import { setActiveModal } from "@/store/uiSlice";
import { selectModal, setActiveModal } from "@/store/uiSlice";
import { useMutation, useQueryClient } from "@tanstack/react-query";
import { useDispatch } from "react-redux";
import { useDispatch, useSelector } from "react-redux";

export const useUpdateBoard = () => {
const queryClient = useQueryClient();
const dispatch = useDispatch();
const activeModal = useSelector(selectModal);
const { mutate: updateBoard, isLoading: isUpdating } = useMutation({
mutationFn: ({ id, board }: { id: string; board: Board }) =>
updateBoardApi(id, board),
onSuccess: () => {
queryClient.invalidateQueries({ queryKey: ["boards"] });
dispatch(setActiveModal(undefined));
//do not close modal if viewing task.
if (activeModal !== ModalEnum.VIEW_TASK) {
dispatch(setActiveModal(undefined));
}
},
});
return { isUpdating, updateBoard };
Expand Down
Loading

0 comments on commit 32b5968

Please sign in to comment.