diff --git a/README.md b/README.md index 3c0710d2..88709043 100644 --- a/README.md +++ b/README.md @@ -1 +1,9 @@ -# react-todo-list-precourse \ No newline at end of file +# react-todo-list-precourse + +1. 기본구조 작성 및 스타일 적용 +2. 할 일 추가하기 기능 구현 +3. 할 일 삭제하기 기능 구현 +4. 할 일 완료 상태 전환하기 구현 +5. 필터링 기능 구현 +6. 남은 할 일 개수 표시 구현 +7. 새로고침 시 작성한 데이터 유지 \ No newline at end of file diff --git a/index.html b/index.html index b021b5c8..82b6987d 100644 --- a/index.html +++ b/index.html @@ -6,7 +6,7 @@ -
- +
+ diff --git a/src/App.jsx b/src/App.jsx new file mode 100644 index 00000000..a61da914 --- /dev/null +++ b/src/App.jsx @@ -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 ( +
+
+ +
+ ); +} + +function App() { + const { todos, setTodos, addTodo, deleteTodo, changeCompleted } = UseTodoState(); + const [filter, useFilter] = useState('All'); + + useEffect(() => { + localStorage.setItem("todoItem", JSON.stringify(todos)); + }, [todos]); + + return ( +
+

TODO

+ { mainFrame (todos, addTodo, deleteTodo, changeCompleted, filter, useFilter) } + +
+ ); +} + +export default App; \ No newline at end of file diff --git a/src/components/footer.jsx b/src/components/footer.jsx new file mode 100644 index 00000000..2cbfc1c0 --- /dev/null +++ b/src/components/footer.jsx @@ -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 ? : ) + }) + ); +} + +function Footer(props) { + return ( +
+
{props.todoLeft} items left
+
+ { makeFilter(props.filter, props.changeFilter) } +
+
+ ); +} + +export default Footer; \ No newline at end of file diff --git a/src/components/header.jsx b/src/components/header.jsx new file mode 100644 index 00000000..4fb6eabf --- /dev/null +++ b/src/components/header.jsx @@ -0,0 +1,27 @@ +import React, { useState } from 'react'; + +function makeForm(content, setContent) { + return ( +
+ setContent(e.target.value)} required/> + +
+ ) +} + +function Header({addTodo}) { + const [content, setContent] = useState(""); + const handleSubmit = e => { + e.preventDefault(); + addTodo(content); + setContent(""); + }; + + return ( +
+ { makeForm(content, setContent) } +
+ ); +} + +export default Header; \ No newline at end of file diff --git a/src/components/todoelement.jsx b/src/components/todoelement.jsx new file mode 100644 index 00000000..075dff67 --- /dev/null +++ b/src/components/todoelement.jsx @@ -0,0 +1,33 @@ +import React from 'react'; + +function makeCheckbox(todo, changeCompleted) { + const handleClick = e => { + changeCompleted(todo.id); + }; + + return (); +} + +function makeContent(todo) { + if (todo.isCompleted) { + return (

{ todo.content }

) + } else { + return (

{ todo.content }

) + } +} + +function TodoElement(props) { + const handleClick = e => { + props.deleteTodo(props.todo.id); + }; + + return ( +
+ { makeCheckbox(props.todo, props.changeCompleted) } + { makeContent(props.todo) } + +
+ ); +} + +export default TodoElement; \ No newline at end of file diff --git a/src/components/todolist.jsx b/src/components/todolist.jsx new file mode 100644 index 00000000..2a566704 --- /dev/null +++ b/src/components/todolist.jsx @@ -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 ( ) + }) + ); +} + +function TodoList(props) { + return ( +
+ { makelist(props) } +
+ ); +} + +export default TodoList; \ No newline at end of file diff --git a/src/hooks/useTodo.jsx b/src/hooks/useTodo.jsx new file mode 100644 index 00000000..bab11613 --- /dev/null +++ b/src/hooks/useTodo.jsx @@ -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; \ No newline at end of file diff --git a/src/index.css b/src/index.css new file mode 100644 index 00000000..660be0bd --- /dev/null +++ b/src/index.css @@ -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; +} \ No newline at end of file diff --git a/src/main.js b/src/main.js deleted file mode 100644 index e69de29b..00000000 diff --git a/src/main.jsx b/src/main.jsx new file mode 100644 index 00000000..0f664110 --- /dev/null +++ b/src/main.jsx @@ -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( + + + +); \ No newline at end of file