Skip to content

feaT: 태스크 드래그 앤 드롭 기능 구현 #325

New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Merged
merged 7 commits into from
Aug 13, 2024
38 changes: 10 additions & 28 deletions frontend/src/components/backlog/StoryBlock.tsx
Original file line number Diff line number Diff line change
@@ -1,24 +1,21 @@
import { MouseEvent } from "react";
import { Socket } from "socket.io-client";
import { useOutletContext } from "react-router-dom";
import useShowDetail from "../../hooks/pages/backlog/useShowDetail";
import { BacklogStatusType, EpicCategoryDTO } from "../../types/DTO/backlogDTO";
import EpicDropdown from "./EpicDropdown";
import BacklogStatusChip from "./BacklogStatusChip";
import CategoryChip from "./CategoryChip";
import ChevronDown from "../../assets/icons/chevron-down.svg?react";
import ChevronRight from "../../assets/icons/chevron-right.svg?react";
import TaskContainer from "./TaskContainer";
import TaskHeader from "./TaskHeader";
import BacklogStatusDropdown from "./BacklogStatusDropdown";
import useStoryEmitEvent from "../../hooks/pages/backlog/useStoryEmitEvent";
import ConfirmModal from "../common/ConfirmModal";
import useShowDetail from "../../hooks/pages/backlog/useShowDetail";
import useBacklogInputChange from "../../hooks/pages/backlog/useBacklogInputChange";
import { MouseEvent } from "react";
import { MOUSE_KEY } from "../../constants/event";
import useStoryEmitEvent from "../../hooks/pages/backlog/useStoryEmitEvent";
import useDropdownState from "../../hooks/common/dropdown/useDropdownState";
import TrashCan from "../../assets/icons/trash-can.svg?react";
import { useModal } from "../../hooks/common/modal/useModal";
import ConfirmModal from "../common/ConfirmModal";
import EpicDropdown from "./EpicDropdown";
import TaskCreateBlock from "./TaskCreateBlock";
import ChevronRight from "../../assets/icons/chevron-right.svg?react";
import ChevronDown from "../../assets/icons/chevron-down.svg?react";
import TrashCan from "../../assets/icons/trash-can.svg?react";
import { MOUSE_KEY } from "../../constants/event";
import { BacklogStatusType, EpicCategoryDTO } from "../../types/DTO/backlogDTO";

interface StoryBlockProps {
id: number;
Expand All @@ -27,11 +24,8 @@ interface StoryBlockProps {
point: number | null;
progress: number;
status: BacklogStatusType;
children: React.ReactNode;
taskExist: boolean;
epicList?: EpicCategoryDTO[];
finished?: boolean;
lastTaskRankValue?: string;
}

const StoryBlock = ({
Expand All @@ -43,9 +37,6 @@ const StoryBlock = ({
status,
taskExist,
epicList,
finished = false,
lastTaskRankValue,
children,
}: StoryBlockProps) => {
const { socket }: { socket: Socket } = useOutletContext();
const { showDetail, handleShowDetail } = useShowDetail();
Expand Down Expand Up @@ -280,15 +271,6 @@ const StoryBlock = ({
</button>
</div>
)}
{showDetail && (
<TaskContainer>
<TaskHeader />
{children}
{!finished && (
<TaskCreateBlock storyId={id} {...{ lastTaskRankValue }} />
)}
</TaskContainer>
)}
</>
);
};
Expand Down
36 changes: 36 additions & 0 deletions frontend/src/components/backlog/StoryDragContainer.tsx
Original file line number Diff line number Diff line change
@@ -0,0 +1,36 @@
import React, { DragEvent } from "react";

interface StoryDragContainerProps {
index: number;
setRef: (index: number) => (element: HTMLDivElement) => void;
onDragStart: () => void;
onDragEnd: (event: DragEvent) => void;
currentlyDraggedOver: boolean;
children: React.ReactNode;
}

const StoryDragContainer = ({
index,
setRef,
onDragStart,
onDragEnd,
currentlyDraggedOver,
children,
}: StoryDragContainerProps) => (
<div
className="relative"
ref={setRef(index)}
draggable={true}
onDragStart={onDragStart}
onDragEnd={onDragEnd}
>
<div
className={`${
currentlyDraggedOver ? "w-full h-1 bg-blue-400" : ""
} absolute`}
/>
{children}
</div>
);

export default StoryDragContainer;
41 changes: 41 additions & 0 deletions frontend/src/components/backlog/TaskDragContainer.tsx
Original file line number Diff line number Diff line change
@@ -0,0 +1,41 @@
import { DragEvent } from "react";

interface TaskDragContainerProps {
storyIndex: number;
taskIndex: number;
setRef: (
storyIndex: number,
taskIndex: number
) => (element: HTMLDivElement) => void;
onDragStart: () => void;
onDragEnd: (event: DragEvent) => void;
currentlyDraggedOver: boolean;
children: React.ReactNode;
}

const TaskDragContainer = ({
storyIndex,
taskIndex,
setRef,
onDragStart,
onDragEnd,
currentlyDraggedOver,
children,
}: TaskDragContainerProps) => (
<div
className="relative"
ref={setRef(storyIndex, taskIndex)}
draggable={true}
onDragStart={onDragStart}
onDragEnd={onDragEnd}
>
<div
className={`${
currentlyDraggedOver ? "w-full h-1 bg-blue-400" : ""
} absolute`}
/>
{children}
</div>
);

export default TaskDragContainer;
52 changes: 51 additions & 1 deletion frontend/src/hooks/pages/backlog/useBacklogSocket.ts
Original file line number Diff line number Diff line change
Expand Up @@ -78,7 +78,7 @@ const useBacklogSocket = (socket: Socket) => {
if (content.epicId) {
setBacklog((prevBacklog) => {
let targetStory: StoryDTO | null = null;
backlog.epicList.some((epic) => {
prevBacklog.epicList.some((epic) => {
const foundStory = epic.storyList.find(
(story) => story.id === content.id
);
Expand Down Expand Up @@ -168,6 +168,56 @@ const useBacklogSocket = (socket: Socket) => {
});
break;
case BacklogSocketTaskAction.UPDATE:
if (content.storyId) {
setBacklog((prevBacklog) => {
let targetTask: TaskDTO | null = null;
prevBacklog.epicList.some((epic) => {
epic.storyList.some((story) => {
const foundTask = story.taskList.find(
(task) => task.id === content.id
);

if (foundTask) {
targetTask = { ...foundTask };
return true;
}

return false;
});

if (targetTask) {
return true;
}

return false;
});

if (!targetTask) {
return prevBacklog;
}

const newEpicList = prevBacklog.epicList.map((epic) => {
const newStoryList = epic.storyList.map((story) => {
const newTaskList = story.taskList.filter(
(task) => task.id !== content.id
);

if (story.id === content.storyId) {
newTaskList.push({
...targetTask,
...content,
} as TaskDTO);
}
return { ...story, taskList: newTaskList };
});

return { ...epic, storyList: newStoryList };
});
return { epicList: newEpicList };
});
break;
}

setBacklog((prevBacklog) => {
const newEpicList = prevBacklog.epicList.map((epic) => {
const newStoryList = epic.storyList.map((story) => {
Expand Down
22 changes: 9 additions & 13 deletions frontend/src/pages/backlog/EpicPage.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -27,7 +27,7 @@ const EpicPage = () => {
{...backlog.epicList.map(
({ id: epicId, name, color, rankValue, storyList }) => (
<EpicBlock
storyExist={storyList.length > 1}
storyExist={storyList.length > 0}
epic={{ id: epicId, name, color, rankValue }}
>
{...storyList.map(({ id, title, point, status, taskList }) => {
Expand All @@ -40,19 +40,15 @@ const EpicPage = () => {
: 0;

return (
<StoryBlock
{...{ id, title, point, status }}
epic={{ id: epicId, name, color, rankValue }}
progress={progress}
taskExist={taskList.length > 0}
lastTaskRankValue={
taskList.length
? taskList[taskList.length - 1].rankValue
: undefined
}
>
<>
<StoryBlock
{...{ id, title, point, status }}
epic={{ id: epicId, name, color, rankValue }}
progress={progress}
taskExist={taskList.length > 0}
/>
{...taskList.map((task) => <TaskBlock {...task} />)}
</StoryBlock>
</>
);
})}
{showDetail ? (
Expand Down
18 changes: 9 additions & 9 deletions frontend/src/pages/backlog/FinishedStoryPage.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -38,16 +38,16 @@ const FinishedStoryPage = () => {
: 0;

return (
<StoryBlock
{...{ id, title, point, status }}
epic={epic}
progress={progress}
taskExist={taskList.length > 0}
epicList={epicCategoryList}
finished={true}
>
<>
<StoryBlock
{...{ id, title, point, status }}
epic={epic}
progress={progress}
taskExist={taskList.length > 0}
epicList={epicCategoryList}
/>
{...taskList.map((task) => <TaskBlock {...task} />)}
</StoryBlock>
</>
);
})}
</div>
Expand Down
Loading
Loading