Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
16 changes: 15 additions & 1 deletion README.md
Original file line number Diff line number Diff line change
@@ -1 +1,15 @@
# react-todo-list-precourse
# react-todo-list-precourse

## 기능 요구 사항
하루 또는 한 주의 할일 목록을 업데이트 하는 할일 목록을 구현한다.
React 라이브러리를 사용하여 웹 앱으로 구현한다.
1. 할 일을 추가하고 삭제할 수 있다.
- 할 일을 추가할 때 사용자는 Enter 키나 추가 버튼을 사용하여 할 일을 목록에 추가할 수 있어야 한다.
- 사용자가 아무것도 입력하지 않은 경우에는 할일을 추가할 수 없다.
2. 할 일의 목록을 볼 수 있다.
3. 할 일의 완료 상태를 전환할 수 있다.

### 선택 요구 사항
1. 현재 진행 중인 할 일, 완료된 할 일, 모든 할 일을 필터링할 수 있다.
2. 해야 할 일의 총개수를 확인할 수 있다.
3. 새로고침을 하여도 이전에 작성한 데이터는 유지되어야 한다.
6 changes: 3 additions & 3 deletions index.html
Original file line number Diff line number Diff line change
Expand Up @@ -3,10 +3,10 @@
<head>
<meta charset="UTF-8" />
<meta name="viewport" content="width=device-width, initial-scale=1.0" />
<title></title>
<title>TodoMVC: React</title>
</head>
<body>
<div id="app"></div>
<script type="module" src="/src/main.js"></script>
<div id="root"></div>
<script type="module" src="/src/App.jsx"></script>
</body>
</html>
6 changes: 6 additions & 0 deletions src/App.jsx
Original file line number Diff line number Diff line change
@@ -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));
13 changes: 13 additions & 0 deletions src/components/FilterButtons.jsx
Original file line number Diff line number Diff line change
@@ -0,0 +1,13 @@
import React from 'react';

const FilterButtons = ({ setFilter }) => {
return (
<div className="filter-buttons">
<button onClick={() => setFilter('all')}>All</button>
<button onClick={() => setFilter('active')}>Active</button>
<button onClick={() => setFilter('completed')}>Completed</button>
</div>
);
};

export default FilterButtons;
25 changes: 25 additions & 0 deletions src/components/TodoInput.jsx
Original file line number Diff line number Diff line change
@@ -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 (
<form onSubmit={handleSubmit} className="todo-input">
<input
type="text"
value={input}
onChange={(e) => setInput(e.target.value)}
placeholder="Add a new todo"
/>
<button type="submit">Add</button>
</form>
);
};

export default TodoInput;
19 changes: 19 additions & 0 deletions src/components/TodoItem.jsx
Original file line number Diff line number Diff line change
@@ -0,0 +1,19 @@
import React from 'react';

const TodoItem = ({ todo, toggleTodo, deleteTodo }) => {
return (
<li>
<input
type="checkbox"
checked={todo.completed}
onChange={toggleTodo}
/>
<span className={todo.completed ? 'completed' : ''}>
{todo.text}
</span>
<button onClick={deleteTodo}>Delete</button>
</li>
);
};

export default TodoItem;
19 changes: 19 additions & 0 deletions src/components/TodoList.jsx
Original file line number Diff line number Diff line change
@@ -0,0 +1,19 @@
import React from 'react';
import TodoItem from './TodoItem';

const TodoList = ({ todos, toggleTodo, deleteTodo }) => {
return (
<ul>
{todos.map((todo, index) => (
<TodoItem
key={index}
todo={todo}
toggleTodo={() => toggleTodo(index)}
deleteTodo={() => deleteTodo(index)}
/>
))}
</ul>
);
};

export default TodoList;
117 changes: 117 additions & 0 deletions src/components/main.css
Original file line number Diff line number Diff line change
@@ -0,0 +1,117 @@
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;
align-items: center;
justify-content: space-between;
}

li input[type="checkbox"] {
margin-right: 10px;
}

li span {
flex: 1;
cursor: pointer;
}

li span.completed {
text-decoration: line-through;
color: #999;
}

li button {
background: white;
border: none;
padding: 10px;
cursor: pointer;
color: black;
}

li button:hover {
background: #ff4d4d;
color: white;
}

.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: #ffffff;
border-radius: 4px 4px 4px 4px;
cursor: pointer;
font-size: 14px;
}

.filter-buttons button:hover {
background: #ffffff;
border: 1px solid #ddd;
}
56 changes: 56 additions & 0 deletions src/components/main.jsx
Original file line number Diff line number Diff line change
@@ -0,0 +1,56 @@
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 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 }]);
};

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);
};

const filteredTodos = todos.filter(todo => {
if (filter === 'completed') return todo.completed;
if (filter === 'active') return !todo.completed;
return true;
});

return (
<div className="todoapp">
<h1 className="header">Todo List</h1>
<TodoInput addTodo={addTodo} />
<TodoList todos={filteredTodos} toggleTodo={toggleTodo} deleteTodo={deleteTodo} />
<div className="footer">
<span>{todos.filter(todo => !todo.completed).length} items left</span>
<FilterButtons setFilter={setFilter} />
</div>
</div>
);
};

export default Main;

Empty file removed src/main.js
Empty file.