2024.05.15 - [개발] - 사내 칸반보드 개발 프로젝트 -1
포부를 밝혔던 1편에이어 이제 진짜 코드가 들어가는 2편이 시작된다.
빠르게 설명부터 들어가자
칸반보드의 핵심의 첫번째는 바로 드래그앤 드롭 기능 구현이다.
구글링 해보니 대부분 라이브러리 react-beautiful-dnd 를 사용해서 구현하고 있었다.
나는 라이브러리를 사용하기 보다 직접 구현하고 싶어 사용하지 않기로 했다.
나에게는 chat gpt가 있으니 말이다.
코드를 보여주기전에 나의 스택을 나열하면
1. react
2. tailwind
3. style-components
를 사용했다.
css 라이브러리를 특이하게 두개나 사용하는데 taillwind 는 보통 엘레멘트 css 용도로 사용하고 스타일 컴포넌트는 컴포넌트를 만들때 주로 사용한다. tailwind 를 사용하다보면 가독성이 않좋아져 이렇게 작업하고 있다.
먼저 칸반보드의 첫번째 페이지 todo,inProgress,done 영역을 만들었다.
import React from "react";
const Column = ({ header, children, onDrop, onDragOver }) => {
return (
<div
className="flex min-h-lvh w-full flex-col gap-2 rounded-lg bg-slate-200 p-4"
onDrop={onDrop}
onDragOver={onDragOver}
>
<div className="text-20 mb-4 border-b-4 border-indigo-500">{header}</div>
{children}
</div>
);
};
export default Column;
그다음은 영역 안에 들어갈 card 코드다.
import React from "react";
import styled from "styled-components";
const Card = ({ id, status = "to-do", onDragStart }) => {
return (
<Wrap draggable="true" onDragStart={onDragStart} id={id} data-task-id={id}>
<div>
<StatusChip>{status}</StatusChip>
</div>
</Wrap>
);
};
export default Card;
const Wrap = styled.div`
// 이 부분을 button에서 div로 변경
width: 90%;
background: #fff;
height: 100px;
border-radius: 8px;
padding: 8px;
cursor: pointer; // 드래그 표시를 위해 cursor 추가
`;
const StatusChip = styled.div`
width: fit-content;
background: gray;
padding: 4px;
`;
App.js
import styled from "styled-components";
import "./App.css";
import Column from "./components/Column";
import Card from "./components/Card";
import { useState } from "react";
import AddModal from "./components/AddModal";
function App() {
const [columns, setColumns] = useState({
todo: ["Task 1", "Task 2", "Task 3"],
inProgress: [],
done: [],
});
console.log("columns", columns);
const handleDrop = (e, col) => {
e.preventDefault();
const cardId = e.dataTransfer.getData("text");
const card = document.getElementById(cardId);
const taskId = card.getAttribute("data-task-id");
// Find and remove the card from its current column
const startCol = Object.keys(columns).find((key) =>
columns[key].includes(taskId),
);
// console.log("startCol", startCol);
const updatedStartCol = columns[startCol]?.filter(
(task) => task !== taskId,
);
// Add card to the new column
const updatedCol = [...columns[col], taskId];
setColumns({
...columns,
[startCol]: updatedStartCol,
[col]: updatedCol,
});
};
return (
<div className="App">
<div className="relative flex h-full justify-around gap-3">
<AddModal />
{Object.entries(columns)?.map(([columnName, tasks]) => (
<Column
header={columnName}
key={columnName}
onDrop={(e) => handleDrop(e, columnName)}
onDragOver={(e) => e.preventDefault()}
>
{tasks?.map((task) => (
<Card
key={task}
id={task}
status={columnName}
onDragStart={(e) => e.dataTransfer.setData("text", e.target.id)}
/>
))}
</Column>
))}
</div>
</div>
);
}
export default App;
위에 코드를 보면 궁금증이 생기는 코드들이 있다.
onDrop: 잡은 item을 적절한 곳에 놓았을 때 발생
onDragOver: 잡은 item을 다른 item과 겹쳐졌을 때 milli sec마다 발생함 --> 그래서 이벤트를 막았다.
onDragStart: item을 잡기 시작했을 때 발생
1. e.dataTransfer
이벤트 객체의 dataTransfer 속성은 드래그 앤 드롭 작업 중 데이터를 관리하는 데 사용됩니다. 이 객체를 사용하여 드래그할 데이터를 설정하거나, 드래그 중인 데이터를 다른 대상에 드롭할 때 그 데이터를 검색할 수 있습니다.
2.setData
메소드는 두 개의 인자를 받습니다. 첫 번째 인자는 데이터의 형식,두 번째 인자는 실제로 전달하고자 하는 데이터
즉, 드래그하는 요소의 고유 식별자를 문자열 형태로 전달하고 있습니다.
이 코드의 목적
사용자가 특정 요소를 드래그 시작할 때, 그 요소의 id를 "text" 형태로 데이터 전송 객체에 저장합니다. 이렇게 저장된 데이터는 나중에 드롭 이벤트가 발생했을 때 사용될 수 있으며, 드롭 이벤트 핸들러 내에서 getData 메소드를 사용하여 이 데이터를 검색하고 사용할 수 있습니다.
그리고 또 의문점이 생기는 분이 함수 handleDrop 이다.
친절하게 나눠서 설명하는게 이해가 빠를 것 같다.
1. 이벤트와 데이터 전송처리
e.preventDefault();
const cardId = e.dataTransfer.getData("text");
const card = document.getElementById(cardId);
const taskId = card.getAttribute("data-task-id");
- e.preventDefault();: 기본 이벤트를 막고 드롭 이벤트의 경우, 기본적으로 일부 데이터가 다른 위치로 드롭되는 것을 방지할 수 있습니다.
- e.dataTransfer.getData("text");: 드래그 이벤트에서 설정된 데이터(여기서는 요소의 id)를 검색합니다. 이 id는 드래그된 요소를 DOM에서 식별하기 위해 사용됩니다.
- document.getElementById(cardId);: id를 사용하여 드래그된 요소(DOM 요소)를 찾습니다.
- card.getAttribute("data-task-id");: 드래그된 요소에서 data-task-id 속성을 읽어와서, taskId로 사용합니다. 이는 내부 로직에서 해당 태스크를 식별하는 데 사용됩니다.
2.카드 이동 로직
const startCol = Object.keys(columns).find((key) =>
columns[key].includes(taskId),
);
const updatedStartCol = columns[startCol]?.filter(
(task) => task !== taskId,
);
const updatedCol = [...columns[col], taskId];
- Object.keys(columns).find(...);: 현재 columns 객체에서 taskId를 포함하는 키(열의 이름)를 찾습니다. 이는 카드가 현재 어느 열에 있는지를 식별합니다.
- columns[startCol]?.filter(...);: 카드가 현재 위치한 열에서 해당 taskId를 제거합니다. 즉, 카드를 현재 위치에서 제거하고 업데이트된 열의 데이터를 반환합니다.
- [...columns[col], taskId];: 새로운 열(드롭된 위치)에 taskId를 추가합니다. 이는 드롭 이벤트가 발생한 열에 해당 카드의 id를 추가함으로써 카드를 해당 위치로 "이동"시키는 것입니다.
3.상태 업데이트
setColumns({
...columns,
[startCol]: updatedStartCol,
[col]: updatedCol,
});
- setColumns(...);: React의 상태 업데이트 함수를 사용하여 columns 상태를 업데이트합니다. 여기서는 스프레드 연산자(...)를 사용하여 기존의 columns 상태를 복사한 다음, startCol과 col 키의 값을 각각 updatedStartCol과 updatedCol로 업데이트합니다. 이 과정은 카드가 이전 위치에서 제거되고 새 위치에 추가되는 것을 상태에 반영합니다.
결과물
구현 영상
지금은 결과물이 볼품없지만 일단 1차 완성을 하면 디자인을 개선할 예정이니 너무 걱정 안하셔도 됩니다.
디자인을 개선하는 내용도 포스팅 할 것이다.
'개발' 카테고리의 다른 글
사내 칸반보드 개발 프로젝트 - 4 (0) | 2024.05.22 |
---|---|
사내 칸반보드 개발 프로젝트 -3 (0) | 2024.05.22 |
사내 칸반보드 개발 프로젝트 -1 (1) | 2024.05.15 |
tailwind css prettierrc 적용하기 (0) | 2024.05.10 |
[React] 뽀모도로 개발 일기 - 1 (9) | 2023.03.09 |
댓글