반응형

 

<완성작 구경하기👇👇👇(깃헙 호스팅)>

https://k-junyyy.github.io/TODO-LIST-APP/

 

ToDo List App

 

k-junyyy.github.io

 

🚀

작년 말에 지인의 아시는 분의 소개로 React로 대시보드를 만드는 프로젝트를 진행 했었는데 그때 배웠던
얄팍한(..?) 리액트 지식으로 간단한 프로젝트인 ToDo List 앱을 만들어 보려고 합니다.

그리고 ToDo List App의 기본 기능으로 해야 할 일들 목록 추가 제거 및 로그인 페이지를 React로 제작하고
서버에서는 Spring Boot로 서버 API역할을 구축하고
유저별 정보를 저장할 MySQL과 AWS를 연동해서 서버를 개발할 예정입니다.

예전에 Vue로 간단한 To-Do List App을 제작한 적이 있었는데,
단순히 유튜브로 보고 따라한거라 컴포넌트, props 개념도 몰랐었고 기억도 잘 안 나서
최근에 배우기 시작한 리액트로 한번 To-Do List App을 제대로 제작해보려고 합니다.

https://cocoon1787.tistory.com/544?category=895017 

 

[Project] Vue.js 로 ToDo App 만들기 <#2 완성>

개발환경 $ vue --version @vue/cli 4.5.12 vscode, windows10 k-junyyy.github.io/TODO-APP/ todo k-junyyy.github.io <프로젝트 디렉터리 구조> <구현한 기능> ToDo를 입력 후 엔터 시 아래에 List 추가 de..

cocoon1787.tistory.com

 

 

1. Create React File

https://ko.reactjs.org/docs/create-a-new-react-app.html

 

새로운 React 앱 만들기 – React

A JavaScript library for building user interfaces

ko.reactjs.org

npx create-react-app my-app
cd my-app
npm start

npx는 npm의 5.2.0 버전부터 새로 추가된 도구인데 npm을 좀 더 편하게 사용하기 위해서 npm에서 제공해주는 하나의 툴입니다. 저의 경우에는 React파일의 이름을 client라고 하였습니다.

npx create-react-app client

 

 

2. 타이틀 변경 + favicon 제작

파워포인트로 대충 만들어봤습니다. ^^;;;;

 

3. UI 제작

 

4. /<>

맨 처음 레이아웃을 잡는 게 중요합니다.

pulbic/index.html 파일에서 다음 style 추가

<style>
  #root {
	width: 100vw;
	height: 100vh;
  }
</style>

 

src/index.js

import React from "react";
import ReactDOM from "react-dom";
import { BrowserRouter } from "react-router-dom";
import "./index.css";
import App from "./App";
import reportWebVitals from "./reportWebVitals";
import "bootstrap/dist/css/bootstrap.css";
import "bootstrap-icons/font/bootstrap-icons.css";

ReactDOM.render(
  <React.StrictMode>
    <BrowserRouter>
      <App />
    </BrowserRouter>
  </React.StrictMode>,
  document.getElementById("root")
);

// If you want to start measuring performance in your app, pass a function
// to log results (for example: reportWebVitals(console.log))
// or send to an analytics endpoint. Learn more: https://bit.ly/CRA-vitals
reportWebVitals();
"react-router-dom": "^6.2.1" 사용
 
 

src/App.jsx

import React from "react";
import { Route, Routes } from "react-router-dom";
import styled from "styled-components";

import Main from "./container/Main";
import Login from "./container/Login";
import BackgroundImg from "./img/todo_background.jpg";

const Background = styled.div`
  background-image: url(${BackgroundImg});
  width: 100vw;
  height: 100vh;
  background-position: center center;
  display: table-cell;
`;

function App() {
  return (
    <Background>
      <Routes>
        <Route path="/" element={<Main></Main>} />
        <Route path="/Login" element={<Login></Login>} />
      </Routes>
    </Background>
  );
}

export default App;

 

src/components/Modal.jsx

import React from "react";
import styled from "styled-components";

const ModalBackground = styled.div`
  background-color: rgb(0, 0, 0, 0.5);
  position: fixed;
  top: 0px;
  left: 0px;
  width: 100vw;
  height: 100vh;
  z-index: 99999;
`;

const ModalBox = styled.div`
  background-color: white;
  border: 1px solid #e1e6e8;
  border-radius: 10px;
  position: absolute;
  top: calc(40%);
  left: calc(50% - 140px);
  width: 280px;
  font-weight: 600;
  font-size: 16px;
  display: flex;
  flex-direction: column;
  justify-content: space-between;
`;

const ModalMsgBox = styled.div`
  text-align: center;
  font-weight: 600;
  font-size: 16px;
  text-align: center;
  margin: 12px 12px;
`;

const ModalBtnBox = styled.div`
  display: flex;
  justify-content: space-around;
  height: 52px;
  cursor: pointer;
`;

const ModalCancleBtn = styled.div`
  text-align: center;
  line-height: 52px;
  width: 50%;
  color: #c7c8ce;
  &:hover {
    color: #5d5fef;
  }
  border-top: 2px solid #e1e6e8;
  border-right: 1px solid #e1e6e8;
`;
const ModalConfirmBtn = styled.div`
  text-align: center;
  line-height: 52px;
  width: 50%;
  color: #c7c8ce;
  &:hover {
    color: #5d5fef;
  }
  border-top: 2px solid #e1e6e8;
  border-left: 1px solid #e1e6e8;
`;

function Modal({ msg, onClickCancle, onClickConfirm }) {
  return (
    <ModalBackground>
      <ModalBox>
        <ModalMsgBox>
          {msg.split("\n").map((text, idx) => (
            <div key={idx}>{text}</div>
          ))}
        </ModalMsgBox>
        <ModalBtnBox>
          <ModalCancleBtn onClick={onClickCancle}>취소</ModalCancleBtn>
          <ModalConfirmBtn onClick={onClickConfirm}>확인</ModalConfirmBtn>
        </ModalBtnBox>
      </ModalBox>
    </ModalBackground>
  );
}

export default Modal;

 

src/container/Main.jsx

import React, { useState } from "react";
import styled from "styled-components";

import Modal from "../components/Modal";

// ToDo 전체
const Wrapper = styled.div`
  background-color: white;
  width: 90%;
  height: auto;
  min-width: 360px;
  max-width: 768px;
  margin: 40px auto;
  padding: 20px 30px;
`;
// 최상단 헤드 ToDO List App
const Head = styled.div`
  font-size: 40px;
  font-weight: 800;
  text-align: center;
  margin-bottom: 30px;
`;
// 입력박스
const InputBox = styled.div`
  border: 1px solid #c7c8ce;
  border-radius: 10px;
  width: 100%;
  height: 45px;
  padding-left: 15px;
  padding-right: 30px;
  display: flex;
`;
// 입력창
const Input = styled.input`
  border: 0px;
  outline: none;
  font-size: 20px;
  width: 100%;
`;
// Enter 아이콘
const EnterIcon = styled.i.attrs({
  className: "bi bi-arrow-return-right",
})`
  font-size: 25px;
  margin-top: 3px;
  margin-right: 10px;
  color: #888e95;
  cursor: pointer;
`;
// Trash 아이콘
const TrashIcon = styled.i.attrs({
  className: "bi bi-trash-fill",
})`
  font-size: 25px;
  color: #48484d;
  cursor: pointer;
`;
// 선택 삭제 버튼
const DeleteSelectedBtn = styled.button`
  background-color: #6c757d;
  border-radius: 5px;
  border: 0px;
  color: white;
  width: 130px;
  height: 30px;
  &:hover {
    background-color: #5a6268;
  }
  margin-right: 15px;
`;
// 전체 삭제 버튼
const ClearAllBtn = styled.button`
  background-color: #6c757d;
  border-radius: 5px;
  border: 0px;
  color: white;
  width: 80px;
  height: 30px;
  &:hover {
    background-color: #5a6268;
  }
`;
// 구분 바
const Bar = styled.hr`
  border: 1px solid rgb(0, 0, 0, 0.1);
  margin: 10px 0px;
  display: ${(props) => (props.listCnt === 0 ? "none" : "")};
`;
// List 라인
const ListWrapper = styled.div`
  display: flex;
  flex-direction: row;
  justify-content: space-between;
  align-items: center;
  margin: 10px 0px;
`;
// checkbox + todo name
const ListBox = styled.div`
  display: flex;
  align-items: center;
`;
// 체크박스
const CheckBox = styled.input`
  margin-right: 20px;
  width: 15px;
  height: 15px;
`;
// 굵게
const B = styled.b`
  font-weight: 800;
`;

function Main() {
  const [todo, setTodo] = useState("");
  const [checkAll, setCheckAll] = useState(false);
  const [showModal, setShowModal] = useState(false);
  const [modalMsg, setModalMsg] = useState("");
  const [deleteType, setDeleteType] = useState("");
  const [todoList, setTodoList] = useState([
    { name: "Exercise", checked: false },
    { name: "Study", checked: false },
    { name: "Shopping", checked: false },
  ]);

  const onChangeInput = (e) => {
    setTodo(e.target.value);
  };

  const onKeyPress = (e) => {
    if (e.key === "Enter") {
      addTodo(todo);
      setTodo("");
    }
  };

  const addTodo = (todo) => {
    const newTodo = { name: todo, checked: false };
    setTodoList(todoList.concat(newTodo));
  };

  const onClickCheckAll = () => {
    setTodoList(todoList.map((todo) => ({ ...todo, checked: !checkAll })));

    setCheckAll(!checkAll);
  };

  const onChangeCheck = (idx) => {
    setTodoList(
      todoList.map((todo, id) =>
        id === idx ? { ...todo, checked: !todo.checked } : todo
      )
    );
  };

  const deleteTodo = (idx) => {
    setTodoList(todoList.filter((todo, id) => id !== idx));
  };

  const deleteSelected = () => {
    setShowModal(true);
    setDeleteType("Selected");
    setModalMsg("선택된 항목을\n삭제하시겠습니까?");
  };

  const clearAll = () => {
    setShowModal(true);
    setDeleteType("All");
    setModalMsg("전체 항목을\n삭제하시겠습니까?");
  };

  const onClickCancle = () => {
    setShowModal(false);
  };

  const onClickConfirm = () => {
    setShowModal(false);
    setCheckAll(false);

    if (deleteType === "Selected") {
      setTodoList(todoList.filter((todo) => todo.checked === false));
    } else if (deleteType === "All") {
      setTodoList([]);
    }
  };

  return (
    <Wrapper>
      {showModal && (
        <Modal
          msg={modalMsg}
          onClickCancle={onClickCancle}
          onClickConfirm={onClickConfirm}
        ></Modal>
      )}
      <Head>ToDo List App</Head>
      <InputBox>
        <EnterIcon></EnterIcon>
        <Input
          name="todo"
          type="text"
          value={todo}
          placeholder="Please enter your ToDo List"
          onChange={onChangeInput}
          onKeyPress={onKeyPress}
        ></Input>
      </InputBox>
      <ListWrapper>
        <ListBox>
          <CheckBox
            type="checkbox"
            onChange={onClickCheckAll}
            checked={checkAll}
          />
          <B>List</B>
        </ListBox>
      </ListWrapper>
      <Bar></Bar>
      {todoList.map((item, idx) => (
        <ListWrapper key={idx}>
          <ListBox>
            <CheckBox
              name=""
              type="checkbox"
              onChange={() => onChangeCheck(idx)}
              checked={todoList[idx].checked}
            />
            {item.name}
          </ListBox>
          <TrashIcon onClick={() => deleteTodo(idx)}></TrashIcon>
        </ListWrapper>
      ))}
      <Bar listCnt={todoList.length}></Bar>
      <ListWrapper>
        <B>Completed Todos : {todoList.length}</B>
        <ListBox>
          <DeleteSelectedBtn onClick={deleteSelected}>
            Delete selected
          </DeleteSelectedBtn>
          <ClearAllBtn onClick={clearAll}>Clear all</ClearAllBtn>
        </ListBox>
      </ListWrapper>
    </Wrapper>
  );
}

export default Main;

- 기능

  • 항목 추가
  • 항목 삭제
  • 선택 항목 삭제 (삭제 시 모달 창으로 한번 더 물음)
  • 전체 항목 삭제 (삭제 시 모달창으로 한 번 더 물음)

 

 

 

📋 진행 상황

  • 메인 페이지 제작
  • 로그인 페이지 제작
  • 로컬에서 MySQL에 유저 데이터(로그인 정보, Todo List) 저장 테스트
  • Spring Boot, gradle 설치
  • Spring Boot와 MySQL 연동 후 JPA로 API 처리하기
  • AWS EC2 생성
  • EC2와 Mysql 연동
  • 배포하기

 

 

⚫️ Github Link

https://github.com/K-Junyyy/TODO-LIST-APP.git

 

GitHub - K-Junyyy/TODO-LIST-APP

Contribute to K-Junyyy/TODO-LIST-APP development by creating an account on GitHub.

github.com

 

 

반응형

+ Recent posts