Skip to content

Commit

Permalink
Refactor: #33 컴포넌트 분리 / types & constants 등 관심사 분리
Browse files Browse the repository at this point in the history
  • Loading branch information
Seok93 committed Jul 6, 2024
1 parent dbb1e79 commit bb76ad5
Show file tree
Hide file tree
Showing 7 changed files with 139 additions and 81 deletions.
26 changes: 26 additions & 0 deletions src/components/task/kanban/TaskItem.tsx
Original file line number Diff line number Diff line change
@@ -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 (
<Draggable draggableId={draggableId} index={index}>
{(dragProvided) => (
<div
className="m-5 flex h-30 items-center justify-start gap-5 rounded-sl bg-[#FEFEFE] p-5"
ref={dragProvided.innerRef}
{...dragProvided.draggableProps}
{...dragProvided.dragHandleProps}
>
<div style={{ borderColor: color }} className="h-8 w-8 rounded-full border" />
<div className="select-none overflow-hidden text-ellipsis text-nowrap">{name}</div>
</div>
)}
</Draggable>
);
}
36 changes: 36 additions & 0 deletions src/components/task/kanban/TaskItemList.tsx
Original file line number Diff line number Diff line change
@@ -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 (
<Droppable droppableId={droppableId} type={DND_TYPE.TASK}>
{(taskDropProvided) => (
<article
style={{ borderColor: color }}
className="h-full w-full grow border-l-[3px] bg-scroll"
ref={taskDropProvided.innerRef}
{...taskDropProvided.droppableProps}
>
{tasks.map((task) => {
const { taskId, name, order } = task;
const draggableId = generatePrefixId(taskId, DND_DRAGGABLE_PREFIX.TASK);
const index = order - 1;
return <TaskItem key={taskId} draggableId={draggableId} color={color} index={index} name={name} />;
})}
{taskDropProvided.placeholder}
</article>
)}
</Droppable>
);
}
37 changes: 37 additions & 0 deletions src/components/task/kanban/TaskStatusContainer.tsx
Original file line number Diff line number Diff line change
@@ -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 (
<Draggable draggableId={draggableId} index={index} key={statusId}>
{(statusDragProvided) => (
<article
className="flex min-w-125 grow basis-1/3 flex-col"
ref={statusDragProvided.innerRef}
{...statusDragProvided.draggableProps}
>
<header className="flex items-center gap-4" {...statusDragProvided.dragHandleProps}>
<h2 className="select-none font-bold text-emphasis">{name}</h2>
<span>
<BsPencil className="cursor-pointer" />
</span>
</header>
<TaskItemList statusId={statusId} color={color} tasks={tasks} />
</article>
)}
</Draggable>
);
}
19 changes: 19 additions & 0 deletions src/constants/dnd.ts
Original file line number Diff line number Diff line change
@@ -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<DndDroppablePrefix> = 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 });
93 changes: 12 additions & 81 deletions src/pages/project/KanbanPage.tsx
Original file line number Diff line number Diff line change
@@ -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;

Expand All @@ -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;
Expand All @@ -61,7 +48,6 @@ function createChangedTasks(statusTasks: TaskWithStatus[], dropResult: DropResul
}

// ToDo: DnD시 가시성을 위한 애니메이션 처리 추가할 것
// ToDo: 칸반보드 ItemList, Item 컴포넌트로 분리할 것
export default function KanbanPage() {
const [statusTasks, setStatusTasks] = useState<TaskWithStatus[]>(TASK_DUMMY);

Expand All @@ -85,71 +71,16 @@ export default function KanbanPage() {

return (
<DragDropContext onDragEnd={handleDragEnd}>
<Droppable droppableId="container" direction="horizontal" type={DND_TYPE.STATUS}>
<Droppable droppableId={DND_DROPPABLE_PREFIX.STATUS} type={DND_TYPE.STATUS} direction="horizontal">
{(statusDropProvided) => (
<section
className="flex grow gap-10 pt-10"
ref={statusDropProvided.innerRef}
{...statusDropProvided.droppableProps}
>
{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 (
<Draggable draggableId={draggableId} index={index} key={statusId}>
{(statusDragProvided) => (
<article
className="flex min-w-125 grow basis-1/3 flex-col"
ref={statusDragProvided.innerRef}
{...statusDragProvided.draggableProps}
>
<header className="flex items-center gap-4" {...statusDragProvided.dragHandleProps}>
<h2 className="font-bold text-emphasis">{name}</h2>
<span>
<BsPencil className="cursor-pointer" />
</span>
</header>
<Droppable droppableId={droppableId} type={DND_TYPE.TASK}>
{(taskDropProvided) => (
<article
style={{ borderColor: color }}
className="h-full w-full grow border-l-[3px] bg-scroll"
ref={taskDropProvided.innerRef}
{...taskDropProvided.droppableProps}
>
{tasks.map((task) => {
const { taskId, name, order } = task;
const draggableId = generatorPrefixId(taskId, 'task');
const index = order - 1;
return (
<Draggable key={taskId} draggableId={draggableId} index={index}>
{(dragProvided) => (
<div
className="m-5 flex h-30 items-center justify-start gap-5 rounded-sl bg-[#FEFEFE] p-5"
ref={dragProvided.innerRef}
{...dragProvided.draggableProps}
{...dragProvided.dragHandleProps}
>
<div style={{ borderColor: color }} className="h-8 w-8 rounded-full border" />
<div className="select-none overflow-hidden text-ellipsis text-nowrap">
{name}
</div>
</div>
)}
</Draggable>
);
})}
{taskDropProvided.placeholder}
</article>
)}
</Droppable>
</article>
)}
</Draggable>
);
})}
{statusTasks.map((statusTask) => (
<TaskStatusContainer statusTask={statusTask} />
))}
{statusDropProvided.placeholder}
</section>
)}
Expand Down
1 change: 1 addition & 0 deletions src/types/TaskStatusType.tsx
Original file line number Diff line number Diff line change
@@ -1,3 +1,4 @@
// ToDo: ERD에 맞춰 ProjectStatus로 변경할 것
// ToDo: API 설계 완료시 데이터 타입 변경할 것
export type TaskStatus = {
statusId: number;
Expand Down
8 changes: 8 additions & 0 deletions src/utils/converter.ts
Original file line number Diff line number Diff line change
@@ -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];
}

0 comments on commit bb76ad5

Please sign in to comment.