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
10 changes: 9 additions & 1 deletion README.md
Original file line number Diff line number Diff line change
@@ -1 +1,9 @@
# react-todo-list-precourse
# react-todo-list-precourse

1. 기본구조 작성 및 스타일 적용
2. 할 일 추가하기 기능 구현
3. 할 일 삭제하기 기능 구현
4. 할 일 완료 상태 전환하기 구현
5. 필터링 기능 구현
6. 남은 할 일 개수 표시 구현
7. 새로고침 시 작성한 데이터 유지
4 changes: 2 additions & 2 deletions index.html
Original file line number Diff line number Diff line change
Expand Up @@ -6,7 +6,7 @@
<title></title>
</head>
<body>
<div id="app"></div>
<script type="module" src="/src/main.js"></script>
<div id="root"></div>
<script type="module" src="/src/main.jsx"></script>
</body>
</html>
44 changes: 44 additions & 0 deletions src/App.jsx
Original file line number Diff line number Diff line change
@@ -0,0 +1,44 @@
import React, { useState, useEffect } from 'react';
import Header from './components/header.jsx';
import TodoList from './components/todolist.jsx';
import Footer from './components/footer.jsx';
import UseTodoState from './hooks/useTodo.jsx';

function exitProgram() {
console.log("exit");
window.close();
}

function activeLen(todos) {
const activeTodos = todos.filter((todo) => todo.isCompleted == false);
return activeTodos.length;
}

function mainFrame(todos, addTodo, deleteTodo, changeCompleted, filter, useFilter) {
return (
<div className="mainframe">
<Header addTodo={addTodo}/>
<TodoList filter={filter} todos={todos} deleteTodo={deleteTodo} changeCompleted={changeCompleted}/>
<Footer filter={filter} changeFilter={useFilter} todoLeft={activeLen(todos)}/>
</div>
);
}

function App() {
const { todos, setTodos, addTodo, deleteTodo, changeCompleted } = UseTodoState();
const [filter, useFilter] = useState('All');

useEffect(() => {
localStorage.setItem("todoItem", JSON.stringify(todos));
}, [todos]);

return (
<div className="app">
<h1>TODO</h1>
{ mainFrame (todos, addTodo, deleteTodo, changeCompleted, filter, useFilter) }
<button className="exitbtn" onClick={ exitProgram }>프로그램 종료</button>
</div>
);
}

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

function makeFilter(filter, changeFilter) {
const filterName = ['All', 'Active', 'Completed'];
const handleClick = e => {
changeFilter(e.target.textContent);
};
return (
filterName.map(function(element) {
return (element === filter ? <button className="choosen" onClick={handleClick}>{element}</button> : <button onClick={handleClick}>{element}</button>)
})
);
}

function Footer(props) {
return (
<div className="footer">
<div className="itemleft">{props.todoLeft} items left</div>
<div className="filter">
{ makeFilter(props.filter, props.changeFilter) }
</div>
</div>
);
}

export default Footer;
27 changes: 27 additions & 0 deletions src/components/header.jsx
Original file line number Diff line number Diff line change
@@ -0,0 +1,27 @@
import React, { useState } from 'react';

function makeForm(content, setContent) {
return (
<form className="inputform">
<input type="text" className="inputtext" value={content} onChange={e=>setContent(e.target.value)} required/>
<input type="submit" className="inputbtn" value="+"/>
</form>
)
}

function Header({addTodo}) {
const [content, setContent] = useState("");
const handleSubmit = e => {
e.preventDefault();
addTodo(content);
setContent("");
};

return (
<div className="header" onSubmit={handleSubmit}>
{ makeForm(content, setContent) }
</div>
);
}

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

function makeCheckbox(todo, changeCompleted) {
const handleClick = e => {
changeCompleted(todo.id);
};

return (<input type="checkbox" className="completebtn" onClick={handleClick} checked={todo.isCompleted}/>);
}

function makeContent(todo) {
if (todo.isCompleted) {
return (<p className="completed">{ todo.content }</p>)
} else {
return (<p>{ todo.content }</p>)
}
}

function TodoElement(props) {
const handleClick = e => {
props.deleteTodo(props.todo.id);
};

return (
<div className="todoelement">
{ makeCheckbox(props.todo, props.changeCompleted) }
{ makeContent(props.todo) }
<button className="deletebtn" onClick={handleClick}>x</button>
</div>
);
}

export default TodoElement;
26 changes: 26 additions & 0 deletions src/components/todolist.jsx
Original file line number Diff line number Diff line change
@@ -0,0 +1,26 @@
import React, { useState } from 'react';
import TodoElement from './todoelement.jsx';

function makelist(elementProps) {
var list = [...elementProps.todos];

if (elementProps.filter !== "All") {
const target = (elementProps.filter === "Active" ? true : false);
list = list.filter((todo) => todo.isCompleted !== target);
}
return (
list.map(function(element) {
return ( <TodoElement todo={element} deleteTodo={elementProps.deleteTodo} changeCompleted={elementProps.changeCompleted}/> )
})
);
}

function TodoList(props) {
return (
<div className="todolist">
{ makelist(props) }
</div>
);
}

export default TodoList;
49 changes: 49 additions & 0 deletions src/hooks/useTodo.jsx
Original file line number Diff line number Diff line change
@@ -0,0 +1,49 @@
import { useState } from 'react';

function add(next, setNext, setTodos, element) {
setNext((prev) => prev+1);
setTodos((prev) => [...prev, {content:element, isCompleted:false, id:next}]);
localStorage.setItem("todoID", JSON.stringify(next));
}

function change(setTodos, target) {
setTodos((prev) => prev.map((todo) =>
todo.id === target ? {...todo, isCompleted:!todo.isCompleted} : todo
));
}

function initTodo() {
const data = localStorage.getItem("todoItem");

if(data)
return JSON.parse(data);
else
return [];
}

function initNext() {
const data = localStorage.getItem("todoID");

if(data)
return 1+parseInt(JSON.parse(data));
else
return 0;
}

const useTodoState = () => {
const [todos, setTodos] = useState(initTodo());
const [next, setNext] = useState(initNext());

const addTodo = (todo) => {
add(next, setNext, setTodos, todo);
};
const deleteTodo = (target) => {
setTodos((prev) => prev.filter((todo) => todo.id !== target));
};
const changeCompleted = (target) => {
change(setTodos, target)
}
return { todos, setTodos, addTodo, deleteTodo, changeCompleted };
}

export default useTodoState;
138 changes: 138 additions & 0 deletions src/index.css
Original file line number Diff line number Diff line change
@@ -0,0 +1,138 @@
body {
background-color: aliceblue;
display: flex;
justify-content: center;
color: #707070;
}
h1 {
text-align: center;
color:midnightblue;
margin-bottom: 50px;
}

button {
background-color: transparent;
border: 0;
color: #707070;
}

.app {
width: 700px;
font-size: 25px;
}
@media screen and (max-width: 710px) {
.app {
width: 90vw;
font-size: 25px;
}
}

.app .exitbtn {
margin-top: 50px;
float: right;
height: 30px;
width: 120px;
background-color: white;
border: 1px solid;
border-color: lightslategray;
}
.app .exitbtn:hover {
color: dodgerblue;
border-color: dodgerblue;
}
.app .mainframe {
background-color: white;
width: 100%;
border: 1px solid;
border-color: lightslategray;
}

.app .mainframe .header {
width: 100%;
border-bottom: 1px solid;
border-color: lightslategray;
}
.app .mainframe .header .inputform {
width: 100%;
display: flex;
}
.app .mainframe .header .inputtext {
font-size: 25px;
flex: 1 0 auto;
outline: none;
border-width: 0;
margin-left: 10px;
}
.app .mainframe .header .inputbtn {
font-size: 35px;
width: 45px;
height: auto;
border: 0;
color: #707070;
}
.app .mainframe .header .inputbtn:hover {
background-color: dodgerblue;
color:white;
}

.app .mainframe .todolist .todoelement {
width: 100%;
height: auto;
display: flex;
align-items: center;
}
.app .mainframe .todolist .todoelement:hover {
background-color: azure;
color: black;
}
.app .mainframe .todolist .todoelement input[type="checkbox"] {
margin: 15px;
display: inline-block;
width: 30px;
height: 30px;
border:3px solid #707070;
position: relative;
}

.app .mainframe .todolist .todoelement .completed {
text-decoration: line-through;
}
.app .mainframe .todolist .todoelement button {
font-size: 20px;
width: 50px;
height: 60px;
float: right;
margin-left: auto;
margin-right: 15px;
}
.app .mainframe .todolist .todoelement button:hover {
color:dodgerblue;
}

.app .mainframe .footer {
display: flex;
font-size: 15px;
width: 100%;
border-top: 1px solid;
border-color: lightslategray;
}
.app .mainframe .footer .itemleft {
margin-left: 15px;
height: 40px;
display: flex;
align-items: center;
}
.app .mainframe .footer .filter {
margin-left: auto;
}
.app .mainframe .footer .filter button {
width: 100px;
height: 100%;
}
.app .mainframe .footer .filter .choosen {
font-weight: bold;
}
.app .mainframe .footer .filter button:hover {
color:dodgerblue;
font-weight: bold;
}
Empty file removed src/main.js
Empty file.
11 changes: 11 additions & 0 deletions src/main.jsx
Original file line number Diff line number Diff line change
@@ -0,0 +1,11 @@
import React from 'react';
import ReactDOM from 'react-dom/client';
import App from './App.jsx';
import './index.css';

const root = ReactDOM.createRoot(document.getElementById('root'));
root.render(
<React.StrictMode>
<App />
</React.StrictMode>
);