From 833ac2446f58f9d65232c34cca602a6c7de3dacd Mon Sep 17 00:00:00 2001 From: hamid zarghami Date: Sat, 11 Jul 2026 11:54:23 +0330 Subject: [PATCH] task list and create --- src/langs/fa.json | 3 + src/pages/taskmanager/components/Column.tsx | 55 +++++++++++++++++-- .../taskmanager/project/types/ProjectTypes.ts | 3 +- .../taskmanager/task/hooks/useTaskData.ts | 14 +++++ .../taskmanager/task/service/TaskService.ts | 7 +++ src/pages/taskmanager/task/types/TaskTypes.ts | 18 ++++++ .../task/utils/mapTaskItemToTask.ts | 14 +++++ src/pages/taskmanager/workspace.tsx | 31 +++++++++-- 8 files changed, 133 insertions(+), 12 deletions(-) create mode 100644 src/pages/taskmanager/task/hooks/useTaskData.ts create mode 100644 src/pages/taskmanager/task/service/TaskService.ts create mode 100644 src/pages/taskmanager/task/types/TaskTypes.ts create mode 100644 src/pages/taskmanager/task/utils/mapTaskItemToTask.ts diff --git a/src/langs/fa.json b/src/langs/fa.json index 30e1db7..42eff48 100644 --- a/src/langs/fa.json +++ b/src/langs/fa.json @@ -1006,6 +1006,9 @@ "delete_column": "حذف ستون", "delete_column_confirm": "آیا از حذف این ستون اطمینان دارید؟", "column_deleted": "ستون با موفقیت حذف شد", + "add_new_task": "اضافه کردن تسک", + "add_task": "اضافه کردن", + "task_title": "عنوان تسک", "task_detail": { "labels": "برچسب ها", "user_management": "مدیریت کاربران", diff --git a/src/pages/taskmanager/components/Column.tsx b/src/pages/taskmanager/components/Column.tsx index 578df58..b33a880 100644 --- a/src/pages/taskmanager/components/Column.tsx +++ b/src/pages/taskmanager/components/Column.tsx @@ -4,6 +4,8 @@ import { useTranslation } from "react-i18next"; import { toast } from "react-toastify"; import ModalConfrim from "../../../components/ModalConfrim"; import { ErrorType } from "../../../helpers/types"; +import { useCreateTask } from "../task/hooks/useTaskData"; +import type { TaskItemType } from "../task/types/TaskTypes"; import { useDeleteTaskPhase } from "../task-phase/hooks/useTaskPhaseData"; import type { Column as ColumnType, Task as TaskType } from "../types"; import ColumnMenu from "./ColumnMenu"; @@ -23,6 +25,7 @@ type Props = { onColumnDrop: (overColumnId: string) => void; onTaskClick?: (task: TaskType) => void; onColumnDeleted?: (columnId: string) => void; + onTaskCreated?: (task: TaskItemType, taskPhaseId: string) => void; }; const Column: FC = ({ @@ -39,11 +42,14 @@ const Column: FC = ({ onColumnDrop, onTaskClick, onColumnDeleted, + onTaskCreated, }) => { const { t } = useTranslation("global"); const deleteTaskPhase = useDeleteTaskPhase(); + const createTask = useCreateTask(); const [isAdding, setIsAdding] = useState(false); + const [title, setTitle] = useState(""); const [isDeleteConfirmOpen, setIsDeleteConfirmOpen] = useState(false); const [isDragOver, setIsDragOver] = useState(false); const [isColumnDragOver, setIsColumnDragOver] = useState(false); @@ -130,6 +136,36 @@ const Column: FC = ({ onColumnDrop(column.id); }; + const resetTaskForm = () => { + setTitle(""); + setIsAdding(false); + }; + + const handleCreateTask = () => { + const trimmedTitle = title.trim(); + if (!trimmedTitle || !projectId) return; + + createTask.mutate( + { + title: trimmedTitle, + taskPhaseId: column.id, + order: tasks.length, + projectId, + }, + { + onSuccess: (data) => { + const task = (data?.data ?? data) as TaskItemType; + onTaskCreated?.(task, column.id); + toast.success(t("success")); + resetTaskForm(); + }, + onError: (error: ErrorType) => { + toast.error(error.response?.data?.error.message[0]); + }, + }, + ); + }; + const handleDeleteColumn = () => { deleteTaskPhase.mutate( { id: column.id, projectId }, @@ -215,23 +251,30 @@ const Column: FC = ({
setTitle(e.target.value)} + onKeyDown={(e) => { + if (e.key === "Enter") handleCreateTask(); + }} + placeholder={t("taskmanager.task_title")} className="w-full bg-white rounded-lg px-3 py-2.5 text-sm outline-none" autoFocus />
@@ -244,7 +287,7 @@ const Column: FC = ({ className="mt-3 sm:mt-4 xl:mt-5 flex gap-2 items-center shrink-0 cursor-pointer" > - اضافه کردن تسک + {t("taskmanager.add_new_task")} )}
diff --git a/src/pages/taskmanager/project/types/ProjectTypes.ts b/src/pages/taskmanager/project/types/ProjectTypes.ts index fbda931..d52b8cc 100644 --- a/src/pages/taskmanager/project/types/ProjectTypes.ts +++ b/src/pages/taskmanager/project/types/ProjectTypes.ts @@ -1,4 +1,5 @@ import { IResponse } from "../../../../types/response.types"; +import { TaskItemType } from "../../task/types/TaskTypes"; export type ProjectItem = { id: string; @@ -32,7 +33,7 @@ export type ProjectDetailType = CreateProjectType & { export type UpdateProjectType = CreateProjectType; -export type ProjectTaskType = unknown; +export type ProjectTaskType = TaskItemType; export type ProjectTaskPhaseType = { id: string; diff --git a/src/pages/taskmanager/task/hooks/useTaskData.ts b/src/pages/taskmanager/task/hooks/useTaskData.ts new file mode 100644 index 0000000..686b54d --- /dev/null +++ b/src/pages/taskmanager/task/hooks/useTaskData.ts @@ -0,0 +1,14 @@ +import { useMutation, useQueryClient } from "@tanstack/react-query"; +import * as api from "../service/TaskService"; +import { CreateTaskType } from "../types/TaskTypes"; + +export const useCreateTask = () => { + const queryClient = useQueryClient(); + + return useMutation({ + mutationFn: (variables: CreateTaskType) => api.createTask(variables), + onSuccess: (_data, variables) => { + queryClient.invalidateQueries({ queryKey: ["project", variables.projectId] }); + }, + }); +}; diff --git a/src/pages/taskmanager/task/service/TaskService.ts b/src/pages/taskmanager/task/service/TaskService.ts new file mode 100644 index 0000000..c330274 --- /dev/null +++ b/src/pages/taskmanager/task/service/TaskService.ts @@ -0,0 +1,7 @@ +import axios from "../../../../config/axios"; +import { CreateTaskType } from "../types/TaskTypes"; + +export const createTask = async (params: CreateTaskType) => { + const { data } = await axios.post(`/task-manager/tasks`, params); + return data; +}; diff --git a/src/pages/taskmanager/task/types/TaskTypes.ts b/src/pages/taskmanager/task/types/TaskTypes.ts new file mode 100644 index 0000000..20fc4c1 --- /dev/null +++ b/src/pages/taskmanager/task/types/TaskTypes.ts @@ -0,0 +1,18 @@ +export type CreateTaskType = { + title: string; + taskPhaseId: string; + order: number; + projectId: string; +}; + +export type TaskItemType = { + id: string; + title: string; + taskPhaseId: string; + order: number; + tag?: string; + dateRange?: string; + attachments?: number; + checklistDone?: number; + checklistTotal?: number; +}; diff --git a/src/pages/taskmanager/task/utils/mapTaskItemToTask.ts b/src/pages/taskmanager/task/utils/mapTaskItemToTask.ts new file mode 100644 index 0000000..c492813 --- /dev/null +++ b/src/pages/taskmanager/task/utils/mapTaskItemToTask.ts @@ -0,0 +1,14 @@ +import type { Task } from "../../types"; +import type { TaskItemType } from "../types/TaskTypes"; + +export const mapTaskItemToTask = (task: TaskItemType, taskPhaseId: string, fallbackOrder: number): Task => ({ + id: task.id, + columnId: task.taskPhaseId ?? taskPhaseId, + order: task.order ?? fallbackOrder, + title: task.title, + tag: task.tag ?? "", + dateRange: task.dateRange ?? "", + attachments: task.attachments ?? 0, + checklistDone: task.checklistDone ?? 0, + checklistTotal: task.checklistTotal ?? 0, +}); diff --git a/src/pages/taskmanager/workspace.tsx b/src/pages/taskmanager/workspace.tsx index 8905ab6..1a84f5c 100644 --- a/src/pages/taskmanager/workspace.tsx +++ b/src/pages/taskmanager/workspace.tsx @@ -5,8 +5,9 @@ import AddNewColumn from "./components/AddNewColumn"; import Column from "./components/Column"; import HeaderWorkspace from "./components/HeaderWorkspace"; import TaskDetailModal from "./components/task-detail/TaskDetailModal"; -import tasksData from "./data/tasks.json"; import { useGetProject } from "./project/hooks/useProjectData"; +import { mapTaskItemToTask } from "./task/utils/mapTaskItemToTask"; +import type { TaskItemType } from "./task/types/TaskTypes"; import type { TaskPhaseItemType } from "./task-phase/types/TaskPhaseTypes"; import type { Column as ColumnType, Task } from "./types"; import { reorderColumns } from "./utils/reorderColumns"; @@ -16,6 +17,10 @@ const Workspace: FC = () => { const { slug: projectId = "" } = useParams<{ slug: string }>(); const { data: project, isPending } = useGetProject(projectId); const [columns, setColumns] = useState([]); + const [tasks, setTasks] = useState([]); + const [draggedTaskId, setDraggedTaskId] = useState(null); + const [draggedColumnId, setDraggedColumnId] = useState(null); + const [selectedTask, setSelectedTask] = useState(null); useEffect(() => { if (!project?.data?.taskPhases) return; @@ -31,10 +36,18 @@ const Workspace: FC = () => { })), ); }, [project?.data?.taskPhases]); - const [tasks, setTasks] = useState(tasksData.tasks as Task[]); - const [draggedTaskId, setDraggedTaskId] = useState(null); - const [draggedColumnId, setDraggedColumnId] = useState(null); - const [selectedTask, setSelectedTask] = useState(null); + + useEffect(() => { + if (!project?.data?.taskPhases) return; + + const apiTasks = project.data.taskPhases.flatMap((phase) => + (phase.tasks ?? []).map((task, index) => + mapTaskItemToTask(task as TaskItemType, phase.id, index), + ), + ); + + setTasks(apiTasks); + }, [project?.data?.taskPhases]); const tasksByColumn = useMemo(() => { return columns.reduce>((acc, column) => { @@ -91,6 +104,13 @@ const Workspace: FC = () => { } }; + const handleTaskCreated = (task: TaskItemType, taskPhaseId: string) => { + setTasks((prev) => [ + ...prev, + mapTaskItemToTask(task, taskPhaseId, prev.filter((item) => item.columnId === taskPhaseId).length), + ]); + }; + const selectedTaskColumn = selectedTask ? columns.find((column) => column.id === selectedTask.columnId) : null; if (isPending) { @@ -118,6 +138,7 @@ const Workspace: FC = () => { onColumnDrop={handleColumnDrop} onTaskClick={setSelectedTask} onColumnDeleted={handleColumnDeleted} + onTaskCreated={handleTaskCreated} /> ))}