From df969c5d3e7b72033bc4527e6e1b078e0a50e46d Mon Sep 17 00:00:00 2001 From: yeramm <213187@jnu.ac.kr> Date: Mon, 10 Jun 2024 02:46:38 +0900 Subject: [PATCH 1/6] docs: updated READ.md MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit 구현할 기능 목록 추가 --- README.md | 16 +++++++++++++++- 1 file changed, 15 insertions(+), 1 deletion(-) diff --git a/README.md b/README.md index 3c0710d2..fdceb95a 100644 --- a/README.md +++ b/README.md @@ -1 +1,15 @@ -# react-todo-list-precourse \ No newline at end of file +# react-todo-list-precourse + +## 기능 요구 사항 +하루 또는 한 주의 할일 목록을 업데이트 하는 할일 목록을 구현한다. +React 라이브러리를 사용하여 웹 앱으로 구현한다. +1. 할 일을 추가하고 삭제할 수 있다. + - 할 일을 추가할 때 사용자는 Enter 키나 추가 버튼을 사용하여 할 일을 목록에 추가할 수 있어야 한다. + - 사용자가 아무것도 입력하지 않은 경우에는 할일을 추가할 수 없다. +2. 할 일의 목록을 볼 수 있다. +3. 할 일의 완료 상태를 전환할 수 있다. + +### 선택 요구 사항 +1. 현재 진행 중인 할 일, 완료된 할 일, 모든 할 일을 필터링할 수 있다. +2. 해야 할 일의 총개수를 확인할 수 있다. +3. 새로고침을 하여도 이전에 작성한 데이터는 유지되어야 한다. \ No newline at end of file From 241a0a7a096214064eececb0fc10e082fc51bc77 Mon Sep 17 00:00:00 2001 From: yeramm <213187@jnu.ac.kr> Date: Mon, 10 Jun 2024 05:23:23 +0900 Subject: [PATCH 2/6] =?UTF-8?q?feat:=20todo-list=20=EC=B6=94=EA=B0=80,=20?= =?UTF-8?q?=EC=82=AD=EC=A0=9C,=20=EB=AA=A9=EB=A1=9D=20=EA=B5=AC=ED=98=84?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit todo-list를 추가하기, 삭제하기, 목록을 구현함 --- index.html | 6 +-- src/App.jsx | 6 +++ src/components/TodoInput.jsx | 25 +++++++++++ src/components/TodoItem.jsx | 17 ++++++++ src/components/TodoList.jsx | 19 +++++++++ src/components/main.css | 82 ++++++++++++++++++++++++++++++++++++ src/components/main.jsx | 34 +++++++++++++++ src/main.js | 0 8 files changed, 186 insertions(+), 3 deletions(-) create mode 100644 src/App.jsx create mode 100644 src/components/TodoInput.jsx create mode 100644 src/components/TodoItem.jsx create mode 100644 src/components/TodoList.jsx create mode 100644 src/components/main.css create mode 100644 src/components/main.jsx delete mode 100644 src/main.js diff --git a/index.html b/index.html index b021b5c8..c282a61f 100644 --- a/index.html +++ b/index.html @@ -3,10 +3,10 @@ - + TodoMVC: React -
- +
+ diff --git a/src/App.jsx b/src/App.jsx new file mode 100644 index 00000000..1c015500 --- /dev/null +++ b/src/App.jsx @@ -0,0 +1,6 @@ +import React from "react"; +import { createRoot } from "react-dom/client"; +import Main from "./components/main"; + +const root = createRoot(document.getElementById("root")); +root.render(React.createElement(Main)); \ No newline at end of file diff --git a/src/components/TodoInput.jsx b/src/components/TodoInput.jsx new file mode 100644 index 00000000..e42b62f3 --- /dev/null +++ b/src/components/TodoInput.jsx @@ -0,0 +1,25 @@ +import React, { useState } from 'react'; + +const TodoInput = ({ addTodo }) => { + const [input, setInput] = useState(''); + + const handleSubmit = (e) => { + e.preventDefault(); + addTodo(input); + setInput(''); + }; + + return ( +
+ setInput(e.target.value)} + placeholder="Add a new todo" + /> + +
+ ); +}; + +export default TodoInput; diff --git a/src/components/TodoItem.jsx b/src/components/TodoItem.jsx new file mode 100644 index 00000000..b9eda7ff --- /dev/null +++ b/src/components/TodoItem.jsx @@ -0,0 +1,17 @@ +import React from 'react'; + +const TodoItem = ({ todo, toggleTodo, deleteTodo }) => { + return ( +
  • + + {todo.text} + + +
  • + ); +}; + +export default TodoItem; diff --git a/src/components/TodoList.jsx b/src/components/TodoList.jsx new file mode 100644 index 00000000..cea0460b --- /dev/null +++ b/src/components/TodoList.jsx @@ -0,0 +1,19 @@ +import React from 'react'; +import TodoItem from './TodoItem'; + +const TodoList = ({ todos, toggleTodo, deleteTodo }) => { + return ( + + ); +}; + +export default TodoList; diff --git a/src/components/main.css b/src/components/main.css new file mode 100644 index 00000000..6fb3d008 --- /dev/null +++ b/src/components/main.css @@ -0,0 +1,82 @@ +body { + font-family: Arial, sans-serif; + background-color: #f5f5f5; + margin: 0; + padding: 0; +} + +.todoapp { + background: #fff; + margin: 30px auto; + padding: 20px; + box-shadow: 0 2px 4px rgba(0,0,0,0.1), 0 25px 50px rgba(0,0,0,0.1); + width: 300px; +} + +.header { + font-size: 24px; + text-align: center; + color: #333; +} + +.todo-input { + display: flex; + align-items: center; + margin-bottom: 20px; +} + +.todo-input input[type="text"] { + flex: 1; + padding: 10px; + border: 1px solid #ddd; + font-size: 14px; + border-radius: 4px 0 0 4px; + margin-right: -1px; /* 겹치는 경계선 제거 */ +} + +.todo-input button { + padding: 10px; + border: none; + background: #5ebec1; + color: white; + cursor: pointer; + font-size: 14px; + border-radius: 0 4px 4px 0; +} + +.todo-input button:hover { + background: #3da6a9; +} + +ul { + list-style: none; + padding: 0; +} + +li { + padding: 10px; + border-bottom: 1px solid #ddd; + display: flex; + justify-content: space-between; +} + +li span { + cursor: pointer; +} + +li span.completed { + text-decoration: line-through; + color: #999; +} + +li button { + background: #ff6b6b; + border: none; + padding: 10px; + cursor: pointer; + color: white; +} + +li button:hover { + background: #ff4d4d; +} diff --git a/src/components/main.jsx b/src/components/main.jsx new file mode 100644 index 00000000..7effe459 --- /dev/null +++ b/src/components/main.jsx @@ -0,0 +1,34 @@ +import React, { useState } from 'react'; +import TodoInput from './TodoInput'; +import TodoList from './TodoList'; +import './main.css'; + +const App = () => { + const [todos, setTodos] = useState([]); + + const addTodo = (text) => { + if (text.trim() === '') return; + setTodos([...todos, { text, completed: false }]); + }; + + const toggleTodo = (index) => { + const newTodos = [...todos]; + newTodos[index].completed = !newTodos[index].completed; + setTodos(newTodos); + }; + + const deleteTodo = (index) => { + const newTodos = todos.filter((_, i) => i !== index); + setTodos(newTodos); + }; + + return ( +
    +

    todos

    + + +
    + ); +}; + +export default App; \ No newline at end of file diff --git a/src/main.js b/src/main.js deleted file mode 100644 index e69de29b..00000000 From de0c9e024eead3bc1e417dcc395af2b2948aeae0 Mon Sep 17 00:00:00 2001 From: yeramm <213187@jnu.ac.kr> Date: Mon, 10 Jun 2024 05:35:27 +0900 Subject: [PATCH 3/6] =?UTF-8?q?feat:=20todo-list=20=EC=99=84=EB=A3=8C=20?= =?UTF-8?q?=EC=83=81=ED=83=9C=20=EC=A0=84=ED=99=98,=20=ED=95=84=ED=84=B0?= =?UTF-8?q?=20=EC=B6=94=EA=B0=80?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit todo-list의 완료 상태를 전환하는 기능 추가 todo-list의 완료 상태를 필터링 할 수 있는 버튼 추가 --- src/components/FilterButtons.jsx | 13 ++++++++++++ src/components/TodoItem.jsx | 10 ++++++---- src/components/main.css | 34 +++++++++++++++++++++++++++++++- src/components/main.jsx | 19 ++++++++++++++---- 4 files changed, 67 insertions(+), 9 deletions(-) create mode 100644 src/components/FilterButtons.jsx diff --git a/src/components/FilterButtons.jsx b/src/components/FilterButtons.jsx new file mode 100644 index 00000000..c83fb15f --- /dev/null +++ b/src/components/FilterButtons.jsx @@ -0,0 +1,13 @@ +import React from 'react'; + +const FilterButtons = ({ setFilter }) => { + return ( +
    + + + +
    + ); +}; + +export default FilterButtons; diff --git a/src/components/TodoItem.jsx b/src/components/TodoItem.jsx index b9eda7ff..c66a9ebd 100644 --- a/src/components/TodoItem.jsx +++ b/src/components/TodoItem.jsx @@ -3,10 +3,12 @@ import React from 'react'; const TodoItem = ({ todo, toggleTodo, deleteTodo }) => { return (
  • - + + {todo.text} diff --git a/src/components/main.css b/src/components/main.css index 6fb3d008..5858e5f3 100644 --- a/src/components/main.css +++ b/src/components/main.css @@ -31,7 +31,7 @@ body { border: 1px solid #ddd; font-size: 14px; border-radius: 4px 0 0 4px; - margin-right: -1px; /* 겹치는 경계선 제거 */ + margin-right: -1px; } .todo-input button { @@ -57,10 +57,16 @@ li { padding: 10px; border-bottom: 1px solid #ddd; display: flex; + align-items: center; justify-content: space-between; } +li input[type="checkbox"] { + margin-right: 10px; +} + li span { + flex: 1; cursor: pointer; } @@ -80,3 +86,29 @@ li button { li button:hover { background: #ff4d4d; } + +.footer { + display: flex; + justify-content: space-between; + align-items: center; + color: #666; + font-size: 14px; + margin-top: 20px; +} + +.filter-buttons { + display: flex; + gap: 10px; +} + +.filter-buttons button { + padding: 10px; + border: none; + background: #ddd; + cursor: pointer; + font-size: 14px; +} + +.filter-buttons button:hover { + background: #ccc; +} diff --git a/src/components/main.jsx b/src/components/main.jsx index 7effe459..dd8692ea 100644 --- a/src/components/main.jsx +++ b/src/components/main.jsx @@ -1,10 +1,12 @@ import React, { useState } from 'react'; import TodoInput from './TodoInput'; import TodoList from './TodoList'; +import FilterButtons from './FilterButtons'; import './main.css'; -const App = () => { +const Main = () => { const [todos, setTodos] = useState([]); + const [filter, setFilter] = useState('all'); const addTodo = (text) => { if (text.trim() === '') return; @@ -22,13 +24,22 @@ const App = () => { setTodos(newTodos); }; + const filteredTodos = todos.filter(todo => { + if (filter === 'completed') return todo.completed; + if (filter === 'active') return !todo.completed; + return true; + }); + return (
    -

    todos

    +

    Todo List

    - + +
    + +
    ); }; -export default App; \ No newline at end of file +export default Main; From e2f2f909fc418ded7aa0386f3432a8de98f5831e Mon Sep 17 00:00:00 2001 From: yeramm <213187@jnu.ac.kr> Date: Mon, 10 Jun 2024 05:39:10 +0900 Subject: [PATCH 4/6] =?UTF-8?q?feat:=20todo-list=20=EC=B4=9D=20=EA=B0=9C?= =?UTF-8?q?=EC=88=98=EB=A5=BC=20=ED=99=95=EC=9D=B8=ED=95=A0=20=EC=88=98=20?= =?UTF-8?q?=EC=9E=88=EB=8A=94=20=ED=85=8D=EC=8A=A4=ED=8A=B8=20=EC=B6=94?= =?UTF-8?q?=EA=B0=80?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit todo-list 해야할 일의 총 개수를 확인할 수 있는 텍스트를 추가함 --- src/components/main.jsx | 1 + 1 file changed, 1 insertion(+) diff --git a/src/components/main.jsx b/src/components/main.jsx index dd8692ea..a9625a24 100644 --- a/src/components/main.jsx +++ b/src/components/main.jsx @@ -36,6 +36,7 @@ const Main = () => {
    + {todos.filter(todo => !todo.completed).length} items left!
    From b4143bdd923f4cdbb62010e06cdb4e1dc3d379d2 Mon Sep 17 00:00:00 2001 From: yeramm <213187@jnu.ac.kr> Date: Mon, 10 Jun 2024 05:41:58 +0900 Subject: [PATCH 5/6] =?UTF-8?q?feat:=20todo-list=EA=B0=80=20=20=EC=9C=A0?= =?UTF-8?q?=EC=A7=80=EB=90=98=EB=8F=84=EB=A1=9D=20=EC=B6=94=EA=B0=80?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit 새로고침을 해도 todo-list가 유지되는 것을 추가함 --- src/components/main.jsx | 16 +++++++++++++--- 1 file changed, 13 insertions(+), 3 deletions(-) diff --git a/src/components/main.jsx b/src/components/main.jsx index a9625a24..b9639239 100644 --- a/src/components/main.jsx +++ b/src/components/main.jsx @@ -1,13 +1,22 @@ -import React, { useState } from 'react'; +import React, { useState, useEffect } from 'react'; import TodoInput from './TodoInput'; import TodoList from './TodoList'; import FilterButtons from './FilterButtons'; import './main.css'; const Main = () => { - const [todos, setTodos] = useState([]); + const [todos, setTodos] = useState(() => { + // 페이지 로드 시 로컬 스토리지에서 데이터를 가져옴 + const savedTodos = localStorage.getItem('todos'); + return savedTodos ? JSON.parse(savedTodos) : []; + }); const [filter, setFilter] = useState('all'); + useEffect(() => { + // todos 상태가 변경될 때마다 로컬 스토리지에 저장 + localStorage.setItem('todos', JSON.stringify(todos)); + }, [todos]); + const addTodo = (text) => { if (text.trim() === '') return; setTodos([...todos, { text, completed: false }]); @@ -36,7 +45,7 @@ const Main = () => {
    - {todos.filter(todo => !todo.completed).length} items left! + {todos.filter(todo => !todo.completed).length} items left
    @@ -44,3 +53,4 @@ const Main = () => { }; export default Main; + From ca1831f7a1ea247790e07f0ded0881357fb250e1 Mon Sep 17 00:00:00 2001 From: yeramm <213187@jnu.ac.kr> Date: Mon, 10 Jun 2024 05:57:20 +0900 Subject: [PATCH 6/6] =?UTF-8?q?design:=20main.css=20=EC=88=98=EC=A0=95?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit 할일 목록 삭제 버튼의 css 스타일 수정 필터링 버튼의 css 스타일 수정 --- src/components/main.css | 11 +++++++---- 1 file changed, 7 insertions(+), 4 deletions(-) diff --git a/src/components/main.css b/src/components/main.css index 5858e5f3..16d710c5 100644 --- a/src/components/main.css +++ b/src/components/main.css @@ -76,15 +76,16 @@ li span.completed { } li button { - background: #ff6b6b; + background: white; border: none; padding: 10px; cursor: pointer; - color: white; + color: black; } li button:hover { background: #ff4d4d; + color: white; } .footer { @@ -104,11 +105,13 @@ li button:hover { .filter-buttons button { padding: 10px; border: none; - background: #ddd; + background: #ffffff; + border-radius: 4px 4px 4px 4px; cursor: pointer; font-size: 14px; } .filter-buttons button:hover { - background: #ccc; + background: #ffffff; + border: 1px solid #ddd; }