Notice
Recent Posts
Recent Comments
Link
«   2025/05   »
1 2 3
4 5 6 7 8 9 10
11 12 13 14 15 16 17
18 19 20 21 22 23 24
25 26 27 28 29 30 31
Archives
Today
Total
관리 메뉴

사고쳤어요

[React / ts] Task App 만들기 - ② list, task 만들기 본문

웹 풀스택

[React / ts] Task App 만들기 - ② list, task 만들기

kevinmj12 2025. 4. 15. 18:50

 

board에 이어서 각 board마다 존재하는 list와 task를 만들어보자.

현재 만드려고 하는 Task-App의 구조는 board - list - task 이다.

board 내에는 여러 개의 list가 있고, list 내에는 여러 개의 task가 있으며

각 board, list, task는 자유롭게 제거하거나 추가할 수 있다.

 

List

// ListContainer.tsx

import React from "react";
import { IList } from "../../types";
import List from "../List/List";
import { listContainer } from "./ListsContainer.css";
import ActionButton from "../ActionButton/ActionButton";

type TListsContainerProps = {
  lists: IList[];
  boardId: string;
};

const ListsContainer: React.FC<TListsContainerProps> = ({ lists, boardId }) => {
  return (
    <div className={listContainer}>
      {lists.map((list) => (
        <List key={list.listId} list={list} boardId={boardId} />
      ))}
      <ActionButton boardId={boardId} listId={""} list />
    </div>
  );
};

export default ListsContainer;
// List.tsx

import React from "react";
import { GrSubtract } from "react-icons/gr";
import { IList, ITask } from "../../types";
import { useTypedDispatch } from "../../hooks/redux";
import { deleteList, setModalActive } from "../../store/slices/boardsSlice";
import { addLog } from "../../store/slices/loggerSlice";
import { v4 } from "uuid";
import { setModalData } from "../../store/slices/modalSlice";
import Task from "../Task/Task";
import { deleteButton, header, listWrapper, name } from "./List.css";
import ActionButton from "../ActionButton/ActionButton";

type TListProps = {
  list: IList;
  boardId: string;
};

const List: React.FC<TListProps> = ({ list, boardId }) => {
  const dispatch = useTypedDispatch();

  const handleListDelete = (listId: string) => {
    dispatch(deleteList({ boardId, listId }));
    dispatch(
      addLog({
        logId: v4(),
        logMessage: "리스트 삭제하기",
        logAuthor: "User",
        logTimestamp: Date.now().toString(),
      })
    );
  };

  const handleTaskChange = (
    boardId: string,
    listId: string,
    taskId: string,
    task: ITask
  ) => {
    dispatch(setModalData({ boardId, listId, task }));
    dispatch(setModalActive(true));
  };

  return (
    <div className={listWrapper}>
      <div className={header}>
        <div className={name}>{list.listName}</div>
        <GrSubtract
          className={deleteButton}
          onClick={() => handleListDelete(list.listId)}
        />
      </div>

      {list.tasks.map((task, index) => (
        <div
          key={task.taskId}
          onClick={() =>
            handleTaskChange(boardId, list.listId, task.taskId, task)
          }
        >
          <Task
            taskName={task.taskName}
            taskDescription={task.taskDescription}
            boardId={boardId}
            id={task.taskId}
            index={index}
          />
        </div>
      ))}
      <ActionButton boardId={boardId} listId={list.listId} />
    </div>
  );
};

export default List;

 

// boardsSlice.ts

const boardsSlice = createSlice({
  name: "boards",
  initialState,
  reducers: {
    addBoard: (state, { payload }: PayloadAction<TAddBoardAction>) => {
      state.boardArray.push(payload.board);
    },
    deleteList: (state, { payload }: PayloadAction<TDeleteListAction>) => {
      state.boardArray = state.boardArray.map((board) =>
        board.boardId === payload.boardId
          ? {
              ...board,
              lists: board.lists.filter(
                (list) => list.listId !== payload.listId
              ),
            }
          : board
      );
    },
    setModalActive: (state, { payload }: PayloadAction<boolean>) => {
      state.modalActive = payload;
    },
  },
});
// modalSlice.ts

const modalSlice = createSlice({
  name: "modal",
  initialState: initialState,
  reducers: {
    setModalData: (state, { payload }: PayloadAction<TSetModalDataAction>) => {
      state.boardId = payload.boardId;
      state.listId = payload.listId;
      state.task = payload.task;
    },
  },
});

 

리스트에서는 리스트 추가하기, 리스트 제거하기 기능을 수행할 수 있어야 하며

리스트에 존재하는 task들을 모두 보여주어야 한다.

dispatch(deleteList())를 통해 리스트 삭제하기를 구현하였고, (리스트 추가하기는 나중에 ActionButton에 구현할 예정이다)

list.tasks.map()을 통해 task들을 모두 보여주며 task를 선택했을 때 처리를 위해 

task에 onclick={() => handleTaskChange()}를 설정해주었다.

 

Task

// Task.tsx

import React from "react";
import { container, description, title } from "./Task.css";

type TTaskProps = {
  index: number;
  id: string;
  boardId: string;
  taskName: string;
  taskDescription: string;
};

const Task: React.FC<TTaskProps> = ({
  index,
  id,
  boardId,
  taskName,
  taskDescription,
}) => {
  return (
    <div className={container}>
      <div>
        <div className={title}>{taskName}</div>
        <div className={description}>{taskDescription}</div>
      </div>
    </div>
  );
};

export default Task;

Task는 간단히 이름, 설명을 보여주는 것으로 구현하였다.