칸반 스타일의 할 일 앱 만들기

이 게시물은 무엇에 관한 것입니까?



이 기사에서는 Kanban 스타일의 todo 앱을 구축하는 방법을 살펴보겠습니다. 각 할일 작업에는 시작되지 않음, 진행 중, 완료의 3가지 상태가 있습니다. 새 할 일이 추가되자마자 기본 상태는 시작되지 않음이고 사용자는 상태 사이를 드래그할 수 있습니다. 이 앱은 최소한의 기능 구현보다는 스타일링에 중점을 둡니다.

여기에서 앱을 확인하세요: Kanban style todo App



콘텐츠


  • 디자인
  • 새 할 일 추가
  • 할 일 상태 변경
  • 할 일 삭제

  • 각각에 대해 자세히 살펴보고 구현 방법을 살펴보겠습니다.

    설계





    새 할 일 추가



    Todo는 입력 영역을 사용하여 추가되며 기본 상태는 시작되지 않음입니다.

    // Component
    const TodoInput = ({ onTodoAdd }) => {
      const [todoInput, setTodoInput] = useState("");
      const onAdd = (e) => {
        e.preventDefault();
        onTodoAdd(todoInput);
        setTodoInput("");
      };
      return (
        <form className="todo-input">
          <input
            placeholder="Add Todo entry"
            value={todoInput}
            onChange={(e) => setTodoInput(e.target.value)}
          />
          <button onClick={onAdd} type="submit">
            Add
          </button>
        </form>
      );
    };
    
    // onTodoAdd Prop implementation
    const onTodoAdd = (todoText) => {
        setTodos((_t) => {
          return [
            ..._t,
            {
              id: uuidv4(), // Generate unique id 
              value: todoText,
              state: TODO_STATE.NOT_STARTED,
            },
          ];
        });
      };
    


    할 일 상태 변경



    할일의 상태에 따라 할일을 보관할 많은 상태 컨테이너가 필요합니다. 따라서 이 경우에는 3개의 컨테이너[시작되지 않음, 진행 중, 완료]가 있습니다.
    각 컨테이너는 드롭할 항목의 드롭 영역 역할을 합니다.
    각 작업 항목은 끌어서 놓을 수 있는 항목으로 작동하며 사용 가능한 모든 놓기 영역에 놓을 수 있습니다.
    드롭 영역: 시작되지 않음, 진행 중, 완료, 삭제.
    끌어서 놓기 기능을 구현하기 위해 사용할 것입니다React DnD.

    export const TODO_STATE = {
      NOT_STARTED: "Not started",
      IN_PROGRESS: "In progress",
      DONE: "Done",
    };
    
    const TodoContent = ({ todos, onTodoDrag, onTodoDelete }) => {
      const notStartedTodos = getTodosBasedOnState(todos, TODO_STATE.NOT_STARTED);
      const inProgressTodos = getTodosBasedOnState(todos, TODO_STATE.IN_PROGRESS);
      const doneTodos = getTodosBasedOnState(todos, TODO_STATE.DONE);
      const [isDragActive, setIsDragActive] = useState(false);
      const onDragActive = (dragActive) => {
        setIsDragActive(dragActive);
      };
      return (
        <DndProvider backend={HTML5Backend}>
          <div className="todo-content">
            <DraggableItemContainer
              title="Not Started"
              todos={notStartedTodos}
              onTodoDrag={onTodoDrag}
              state={TODO_STATE.NOT_STARTED}
              onDragActive={onDragActive}
              onTodoDelete={onTodoDelete}
            />
            <DraggableItemContainer
              title="In Progress"
              todos={inProgressTodos}
              onTodoDrag={onTodoDrag}
              state={TODO_STATE.IN_PROGRESS}
              onDragActive={onDragActive}
              onTodoDelete={onTodoDelete}
            />
            <DraggableItemContainer
              title="Done"
              todos={doneTodos}
              onTodoDrag={onTodoDrag}
              state={TODO_STATE.DONE}
              onDragActive={onDragActive}
              onTodoDelete={onTodoDelete}
            />
          </div>
          {isDragActive && (
            <div className="delete-drag-container">
              <DeleteDragItemBox />
            </div>
          )}
        </DndProvider>
      );
    };
    


    드래그 가능한 컨테이너

    const DraggableItemContainer = ({
      title = "",
      todos = [],
      onTodoDrag,
      state,
      onDragActive,
      onTodoDelete,
    }) => {
      const [{ canDrop, isOver }, drop] = useDrop(() => ({
        accept: ITEM_TYPES.TODO,
        drop: () => ({ state }),
        collect: (monitor) => ({
          isOver: monitor.isOver(),
          canDrop: monitor.canDrop(),
        }),
      }));
      const isActive = canDrop && isOver;
      const style = {
        border: isActive ? "3px dashed black" : "1px solid black",
      };
      return (
        <div className="draggable-item-container" ref={drop} style={style}>
          <h4 className="title">
            {title} - [{todos.length}]
          </h4>
          <div className="content">
            {todos.map((t) => {
              return (
                <DraggableItem
                  key={t.id}
                  todo={t}
                  onTodoDrag={onTodoDrag}
                  onDragActive={onDragActive}
                  onTodoDelete={onTodoDelete}
                />
              );
            })}
          </div>
        </div>
      );
    };
    


    드래그 가능한 항목 a.k.a Todo 항목

    const DraggableItem = ({ todo, onTodoDrag, onDragActive, onTodoDelete }) => {
      const [{ isDragging }, drag] = useDrag(() => ({
        type: ITEM_TYPES.TODO,
        item: { todo },
        end: (item, monitor) => {
          const dropResult = monitor.getDropResult();
          if (item && dropResult) {
            if (!dropResult.delete) {
              onTodoDrag(item.todo.id, dropResult.state);
            } else {
              onTodoDelete(item.todo.id);
            }
            onDragActive(false);
          }
        },
        collect: (monitor) => ({
          isDragging: monitor.isDragging(),
          handlerId: monitor.getHandlerId(),
        }),
        isDragging: (monitor) => {
          if (todo.id === monitor.getItem().todo.id) {
            onDragActive(true);
          }
        },
      }));
      const opacity = isDragging ? 0.4 : 1;
      const textDecoration =
        todo.state === TODO_STATE.DONE ? "line-through" : "none";
      let backgroundColor = "";
      switch (todo.state) {
        case TODO_STATE.NOT_STARTED: {
          backgroundColor = "lightcoral";
          break;
        }
        case TODO_STATE.IN_PROGRESS: {
          backgroundColor = "lightyellow";
          break;
        }
        case TODO_STATE.DONE: {
          backgroundColor = "lightgreen";
          break;
        }
        default: {
          backgroundColor = "white";
          break;
        }
      }
      return (
        <div
          className="draggable-item"
          ref={drag}
          style={{ opacity, textDecoration, backgroundColor }}
        >
          {todo.value}
        </div>
      );
    };
    


    이벤트 : onTodoDrag

    const onTodoDrag = (id, state) => {
        setTodos((_t) =>
          _t.map((t) => {
            if (t.id === id) {
              return { ...t, state };
            }
            return t;
          })
        );
     };
    


    할 일 삭제



    드롭존 삭제

    const DeleteDragItemBox = () => {
      const [{ canDrop, isOver }, drop] = useDrop(() => ({
        accept: ITEM_TYPES.TODO,
        drop: () => ({ delete: true }),
        collect: (monitor) => ({
          isOver: monitor.isOver(),
          canDrop: monitor.canDrop(),
        }),
      }));
      const isActive = canDrop && isOver;
      const style = {
        border: isActive ? "3px dashed black" : "none",
      };
      return (
        <div className="delete-drag-box" style={style} ref={drop}>
          <DeleteIcon width={'4rem'}/>
        </div>
      );
    };
    
    const onTodoDelete = (id) => {
        setTodos((_t) => _t.filter((t) => t.id !== id));
    };
    


    결론



    이 게임을 구현하면 원하는 논리를 구현하기 위해 React에서 상태 및 부작용을 사용하는 방법을 배울 수 있습니다. 이 앱은 실제 응용 프로그램에 사용되는 새로운 구성 요소 학습의 일부로 만들어졌습니다.
    안전을 유지하고 다른 사람에게 손을 빌려주세요 :)
  • Kanban style todo App
  • Project Source
  • Vignesh Iyer
  • 좋은 웹페이지 즐겨찾기