diff --git a/src/components/task/kanban/TaskItem.tsx b/src/components/task/kanban/TaskItem.tsx
new file mode 100644
index 00000000..5aebda7a
--- /dev/null
+++ b/src/components/task/kanban/TaskItem.tsx
@@ -0,0 +1,26 @@
+import { Draggable, DraggableId } from '@hello-pangea/dnd';
+
+type TaskItemProps = {
+ draggableId: DraggableId;
+ name: string;
+ color: string;
+ index: number;
+};
+
+export default function TaskItem({ draggableId, name, color, index }: TaskItemProps) {
+ return (
+
+ {(dragProvided) => (
+
+ )}
+
+ );
+}
diff --git a/src/components/task/kanban/TaskItemList.tsx b/src/components/task/kanban/TaskItemList.tsx
new file mode 100644
index 00000000..d9d87070
--- /dev/null
+++ b/src/components/task/kanban/TaskItemList.tsx
@@ -0,0 +1,36 @@
+import { useMemo } from 'react';
+import { Droppable } from '@hello-pangea/dnd';
+import TaskItem from '@components/task/kanban/TaskItem';
+import { generatePrefixId } from '@utils/converter';
+import { DND_DRAGGABLE_PREFIX, DND_DROPPABLE_PREFIX, DND_TYPE } from '@constants/dnd';
+import type { Task } from '@/types/TaskType';
+
+type TaskItemListProps = {
+ statusId: number;
+ color: string;
+ tasks: Task[];
+};
+
+export default function TaskItemList({ statusId, color, tasks }: TaskItemListProps) {
+ const droppableId = useMemo(() => generatePrefixId(statusId, DND_DROPPABLE_PREFIX.TASK), [statusId]);
+ return (
+
+ {(taskDropProvided) => (
+
+ {tasks.map((task) => {
+ const { taskId, name, order } = task;
+ const draggableId = generatePrefixId(taskId, DND_DRAGGABLE_PREFIX.TASK);
+ const index = order - 1;
+ return ;
+ })}
+ {taskDropProvided.placeholder}
+
+ )}
+
+ );
+}
diff --git a/src/components/task/kanban/TaskStatusContainer.tsx b/src/components/task/kanban/TaskStatusContainer.tsx
new file mode 100644
index 00000000..c4e85849
--- /dev/null
+++ b/src/components/task/kanban/TaskStatusContainer.tsx
@@ -0,0 +1,37 @@
+import { useMemo } from 'react';
+import { Draggable } from '@hello-pangea/dnd';
+import TaskItemList from '@components/task/kanban/TaskItemList';
+import { generatePrefixId } from '@utils/converter';
+import { DND_DRAGGABLE_PREFIX } from '@constants/dnd';
+import { BsPencil } from 'react-icons/bs';
+import { TaskWithStatus } from '@/types/TaskType';
+
+type TaskStatusContainerProps = {
+ statusTask: TaskWithStatus;
+};
+
+export default function TaskStatusContainer({ statusTask }: TaskStatusContainerProps) {
+ const { statusId, name, color, order, tasks } = statusTask;
+ const draggableId = useMemo(() => generatePrefixId(statusId, DND_DRAGGABLE_PREFIX.STATUS), [statusId]);
+ const index = useMemo(() => order - 1, [order]);
+
+ return (
+
+ {(statusDragProvided) => (
+
+
+
+
+ )}
+
+ );
+}
diff --git a/src/constants/dnd.ts b/src/constants/dnd.ts
new file mode 100644
index 00000000..757beb8b
--- /dev/null
+++ b/src/constants/dnd.ts
@@ -0,0 +1,19 @@
+type DndTypeKeys = keyof typeof DND_TYPE;
+type DndTypeValues = (typeof DND_TYPE)[DndTypeKeys];
+type DndDroppablePrefix = {
+ [K in DndTypeKeys]: `${DndTypeValues}-CONTAINER`;
+};
+
+export const DND_TYPE = Object.freeze({
+ STATUS: 'STATUS',
+ TASK: 'TASK',
+});
+
+export const DND_DROPPABLE_PREFIX: Readonly = Object.freeze(
+ Object.entries(DND_TYPE).reduce((obj, [key, value]) => {
+ obj[key as DndTypeKeys] = `${value}-CONTAINER`;
+ return obj;
+ }, {} as DndDroppablePrefix),
+);
+
+export const DND_DRAGGABLE_PREFIX = Object.freeze({ ...DND_TYPE });
diff --git a/src/pages/project/KanbanPage.tsx b/src/pages/project/KanbanPage.tsx
index 0bd318d6..6cee0869 100644
--- a/src/pages/project/KanbanPage.tsx
+++ b/src/pages/project/KanbanPage.tsx
@@ -1,26 +1,13 @@
import { useState } from 'react';
-import { DragDropContext, Draggable, Droppable } from '@hello-pangea/dnd';
-import { BsPencil } from 'react-icons/bs';
+import { DragDropContext, Droppable, DropResult } from '@hello-pangea/dnd';
+import TaskStatusContainer from '@components/task/kanban/TaskStatusContainer';
+import { DND_DROPPABLE_PREFIX, DND_TYPE } from '@constants/dnd';
import deepClone from '@utils/deepClone';
-
+import { parsePrefixId } from '@utils/converter';
import { TASK_DUMMY } from '@mocks/mockData';
-import type { DropResult } from '@hello-pangea/dnd';
import type { Task, TaskWithStatus } from '@/types/TaskType';
-const DND_TYPE = {
- STATUS: 'STATUS',
- TASK: 'TASK',
-};
-
-// ToDo: 유틸리티로 분리할지 고려하기
-function generatorPrefixId(id: number | string, prefix: string, delimiter: string = '-') {
- return `${prefix}${delimiter}${id}`;
-}
-function parserPrefixId(prefixId: string, delimiter: string = '-') {
- const result = prefixId.split(delimiter);
- return result[result.length - 1];
-}
-
+// ToDo: 상태 DnD와 할일 DnD 로직 통합 가능한가 생각해보기
function createChangedStatus(statusTasks: TaskWithStatus[], dropResult: DropResult) {
const { source, destination } = dropResult;
@@ -40,9 +27,9 @@ function createChangedTasks(statusTasks: TaskWithStatus[], dropResult: DropResul
// ToDo: 메세지 포맷 정하고 수정하기
if (!destination) throw Error('Error: DnD destination is null');
- const sourceStatusId = Number(parserPrefixId(source.droppableId));
- const destinationStatusId = Number(parserPrefixId(destination.droppableId));
- const taskId = Number(parserPrefixId(draggableId));
+ const sourceStatusId = Number(parsePrefixId(source.droppableId));
+ const destinationStatusId = Number(parsePrefixId(destination.droppableId));
+ const taskId = Number(parsePrefixId(draggableId));
const newStatusTasks = deepClone(statusTasks);
const { tasks: sourceTasks } = newStatusTasks.find((data) => data.statusId === sourceStatusId)! as TaskWithStatus;
@@ -61,7 +48,6 @@ function createChangedTasks(statusTasks: TaskWithStatus[], dropResult: DropResul
}
// ToDo: DnD시 가시성을 위한 애니메이션 처리 추가할 것
-// ToDo: 칸반보드 ItemList, Item 컴포넌트로 분리할 것
export default function KanbanPage() {
const [statusTasks, setStatusTasks] = useState(TASK_DUMMY);
@@ -85,71 +71,16 @@ export default function KanbanPage() {
return (
-
+
{(statusDropProvided) => (
- {statusTasks.map((data) => {
- const { statusId, name, color, order, tasks } = data;
- const draggableId = generatorPrefixId(statusId, 'status-drag');
- const droppableId = generatorPrefixId(statusId, 'status');
- const index = order - 1;
- return (
-
- {(statusDragProvided) => (
-
-
-
- {(taskDropProvided) => (
-
- {tasks.map((task) => {
- const { taskId, name, order } = task;
- const draggableId = generatorPrefixId(taskId, 'task');
- const index = order - 1;
- return (
-
- {(dragProvided) => (
-
- )}
-
- );
- })}
- {taskDropProvided.placeholder}
-
- )}
-
-
- )}
-
- );
- })}
+ {statusTasks.map((statusTask) => (
+
+ ))}
{statusDropProvided.placeholder}
)}
diff --git a/src/types/TaskStatusType.tsx b/src/types/TaskStatusType.tsx
index e8bc76e3..8a4b3ea8 100644
--- a/src/types/TaskStatusType.tsx
+++ b/src/types/TaskStatusType.tsx
@@ -1,3 +1,4 @@
+// ToDo: ERD에 맞춰 ProjectStatus로 변경할 것
// ToDo: API 설계 완료시 데이터 타입 변경할 것
export type TaskStatus = {
statusId: number;
diff --git a/src/utils/converter.ts b/src/utils/converter.ts
new file mode 100644
index 00000000..37847405
--- /dev/null
+++ b/src/utils/converter.ts
@@ -0,0 +1,8 @@
+export function generatePrefixId(id: number | string, prefix: string, delimiter: string = '-') {
+ return `${prefix}${delimiter}${id}`;
+}
+
+export function parsePrefixId(prefixId: string, delimiter: string = '-') {
+ const result = prefixId.split(delimiter);
+ return result[result.length - 1];
+}