task list and create
This commit is contained in:
@@ -1006,6 +1006,9 @@
|
||||
"delete_column": "حذف ستون",
|
||||
"delete_column_confirm": "آیا از حذف این ستون اطمینان دارید؟",
|
||||
"column_deleted": "ستون با موفقیت حذف شد",
|
||||
"add_new_task": "اضافه کردن تسک",
|
||||
"add_task": "اضافه کردن",
|
||||
"task_title": "عنوان تسک",
|
||||
"task_detail": {
|
||||
"labels": "برچسب ها",
|
||||
"user_management": "مدیریت کاربران",
|
||||
|
||||
@@ -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<Props> = ({
|
||||
@@ -39,11 +42,14 @@ const Column: FC<Props> = ({
|
||||
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<Props> = ({
|
||||
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<Props> = ({
|
||||
<div className="mt-3 sm:mt-4 xl:mt-5 shrink-0 flex flex-col gap-3">
|
||||
<input
|
||||
type="text"
|
||||
placeholder="عنوان تسک"
|
||||
value={title}
|
||||
onChange={(e) => 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
|
||||
/>
|
||||
<div className="flex items-center gap-2">
|
||||
<button
|
||||
type="button"
|
||||
className="flex items-center gap-2 bg-black text-white text-[13px] rounded-full px-4 py-2 cursor-pointer"
|
||||
onClick={handleCreateTask}
|
||||
disabled={!title.trim() || createTask.isPending}
|
||||
className="flex items-center gap-2 bg-black text-white text-[13px] rounded-full px-4 py-2 cursor-pointer disabled:opacity-50"
|
||||
>
|
||||
<span>اضافه کردن</span>
|
||||
<span>{t("taskmanager.add_task")}</span>
|
||||
<Add size={18} color="white" />
|
||||
</button>
|
||||
<button
|
||||
type="button"
|
||||
onClick={() => setIsAdding(false)}
|
||||
onClick={resetTaskForm}
|
||||
className="cursor-pointer"
|
||||
aria-label="انصراف"
|
||||
aria-label={t("cancel")}
|
||||
>
|
||||
<CloseCircle size={22} color="#888888" variant="Bold" />
|
||||
</button>
|
||||
@@ -244,7 +287,7 @@ const Column: FC<Props> = ({
|
||||
className="mt-3 sm:mt-4 xl:mt-5 flex gap-2 items-center shrink-0 cursor-pointer"
|
||||
>
|
||||
<AddCircle size={18} color="#888888" />
|
||||
<span className="text-description text-[13px] mt-0.5">اضافه کردن تسک</span>
|
||||
<span className="text-description text-[13px] mt-0.5">{t("taskmanager.add_new_task")}</span>
|
||||
</button>
|
||||
)}
|
||||
</div>
|
||||
|
||||
@@ -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;
|
||||
|
||||
@@ -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] });
|
||||
},
|
||||
});
|
||||
};
|
||||
@@ -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;
|
||||
};
|
||||
@@ -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;
|
||||
};
|
||||
@@ -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,
|
||||
});
|
||||
@@ -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<ColumnType[]>([]);
|
||||
const [tasks, setTasks] = useState<Task[]>([]);
|
||||
const [draggedTaskId, setDraggedTaskId] = useState<string | null>(null);
|
||||
const [draggedColumnId, setDraggedColumnId] = useState<string | null>(null);
|
||||
const [selectedTask, setSelectedTask] = useState<Task | null>(null);
|
||||
|
||||
useEffect(() => {
|
||||
if (!project?.data?.taskPhases) return;
|
||||
@@ -31,10 +36,18 @@ const Workspace: FC = () => {
|
||||
})),
|
||||
);
|
||||
}, [project?.data?.taskPhases]);
|
||||
const [tasks, setTasks] = useState<Task[]>(tasksData.tasks as Task[]);
|
||||
const [draggedTaskId, setDraggedTaskId] = useState<string | null>(null);
|
||||
const [draggedColumnId, setDraggedColumnId] = useState<string | null>(null);
|
||||
const [selectedTask, setSelectedTask] = useState<Task | null>(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<Record<string, Task[]>>((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}
|
||||
/>
|
||||
))}
|
||||
|
||||
|
||||
Reference in New Issue
Block a user