<완성작 구경하기👇👇👇(깃헙 호스팅)>
https://k-junyyy.github.io/TODO-LIST-APP/
🚀
작년 말에 지인의 아시는 분의 소개로 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
1. Create React File
https://ko.reactjs.org/docs/create-a-new-react-app.html
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();
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
'👨🏻💻Project' 카테고리의 다른 글
저의 첫 오픈소스를 소개합니다. (0) | 2024.05.15 |
---|---|
[Project] Todo List App 개발 (0) | 2023.09.04 |
[Project] socket.io를 이용한 채팅 앱 구현 <#4 사용자 접속, 종료, 닉네임 변경 알림 띄우기 & 다른기기와 채팅하기> (0) | 2021.06.05 |
[Project] socket.io를 이용한 채팅 앱 구현 <#3 채팅 입력 시 채팅로그에 표시하기> (0) | 2021.06.04 |
[Project] socket.io를 이용한 채팅 앱 구현 <#2 채팅창 디자인하기> (0) | 2021.06.03 |