To Do List - Front (React)
โTodo List - ํ ์ผ ๋ชฉ๋ก UI ๋ง๋ค๊ธฐโ
์์ ํ์ผ
โข
App.js
โข
App.css
โข
components
โฆ
TodoContainer.jsx
โฆ
TodoHeader.jsx
โฆ
TodoInput.jsx
โฆ
TodoList.jsx
โฆ
TodoItem.jsx
โฆ
TodoFooter.jsx
์ปดํฌ๋ํธ ๊ตฌ์กฐ
โข
App.
โฆ
TodoContainer
โช
TodoHeader
โช
TodoInput
โช
TodoList
โข
TodoItem
โช
TodoFooter
์ฝ๋ ์์
โข
components
โฆ
TodoContainer.jsx
โฆ
TodoHeader.jsx
โฆ
TodoInput.jsx
โฆ
TodoList.jsx
โฆ
TodoItem.jsx
โฆ
TodoFooter.jsx
โข
App.css
โข
App.js
TodoContainer.jsx
import React, { useEffect, useState } from 'react'
import TodoHeader from './TodoHeader'
import TodoInput from './TodoInput'
import TodoList from './TodoList'
import TodoFooter from './TodoFooter'
const TodoContainer = () => {
// โ
state
const [todoList, setTodoList] = useState([]);
const [input, setInput] = useState('')
// โ hook
useEffect(() => {
// โ
Mount, Update
// ํ ์ผ ๋ชฉ๋ก ์์ฒญ [GET]
fetch('http://192.168.30.119:8080/todos')
.then( ( response ) => response.json() )
.then( ( data ) => setTodoList(data) )
.catch( (error) => console.log(error) );
console.log('[GET] - /todos - ํ ์ผ๋ชฉ๋ก ์์ฒญ');
// โ
UnMount
return () => {
}
}, [])
// ํ ์ผ ๋ฑ๋ก
const onSubmit = () => {
console.log('ํ ์ผ : ' + input);
if( input == '' ) {
// alert('ํ ์ผ์ ์
๋ ฅํด์ฃผ์ธ์')
// return
}
const data = {
name: input == '' ? '์ ๋ชฉ์์' : input, // ํ ์ผ ์ ๋ชฉ
status: 0, // ์๋ฃ ์ฌ๋ถ(๋ฏธ์๋ฃ-0,์๋ฃ-1)
};
const init = {
method : 'POST',
headers: {
'Content-Type': 'application/json',
},
body: JSON.stringify(data)
};
// ํ ์ผ ๋ฑ๋ก [POST]
// โก ์๋ก ๋ฑ๋ก๋ ํ ์ผ ๋ฐ์ดํฐ
fetch('http://192.168.30.119:8080/todos',init)
.then( ( response ) => response.json() )
.then( ( data ) => setTodoList([data, ...todoList]) )
.catch( (error) => console.log(error) );
setInput('')
}
// ํ ์ผ ์
๋ ฅ ์ด๋ฒคํธ
const onChange = (e) => {
setInput(e.target.value);
}
// ํ ์ผ ์๋ฃ ์ฌ๋ถ ์์
const onToggle = (todo) => {
console.log('ํ ์ผ ์๋ฃ ์ฌ๋ถ ์ฒ๋ฆฌ');
console.log(todo);
// PUT ์์ฒญ
const data = {
no: todo.no,
name: todo.name,
status: todo.status ? 0 : 1,
}
const init = {
method: 'PUT',
headers: {
'Content-Type' : 'application/json',
},
body: JSON.stringify(data),
}
// ํ ์ผ ์์ [PUT]
// โก '์์ ์๋ฃ ๋ฉ์์ง'
fetch('http://192.168.30.119:8080/todos',init)
.then( ( response ) => response.text() )
.then( ( data ) => console.log(data) )
.catch( (error) => console.log(error) );
// ํ ์ผ ์๋ฃ ์ฒ๋ฆฌ ํ,
// โก ํด๋ฆญํ ํ ์ผ์ ์๋ฃ ์ฌ๋ถ๋ฅผ toggle
// let updatedTodoList = todoList.map((item) => {
// return item.no === todo.no ? {...item, status: !item.status} : item;
// });
// })
// โก ์๋ฃ๋ ํ ์ผ์ ์๋์ชฝ ์ ๋ ฌ
// let sortedTodoList = updatedTodoList.sort((a, b) => {
// ์ ๋ ฌ ๊ธฐ์ค
// 1. ์๋ฃ ์ฌ๋ถ - 0, 1 ์ค๋ฆ์ฐจ์
// 2. ํ ์ผ ๋ฒํธ ๋ด๋ฆผ์ฐจ์
// return a.status - b.status == 0 ? b.no - a.no : a.status - b.status;
// })
const sortedTodoList
= todoList.map((item) => item.no === todo.no ? {...item, status: !item.status} : item)
.sort((a, b) => a.status - b.status == 0 ? b.no - a.no : a.status - b.status)
setTodoList(sortedTodoList)
};
// ํ ์ผ ์ญ์
const onDelete = (no) => {
// DELETE ์์ฒญ
const init = {
method: 'DELETE',
};
// ํ ์ผ ์ญ์ [DELETE]
// โก '์ญ์ ์๋ฃ ๋ฉ์์ง'
fetch(`http://192.168.30.119:8080/todos/${no}`,init)
.then( ( response ) => response.text() )
.then( ( data ) => console.log(data) )
.catch( (error) => console.log(error) );
// ์ญ์ ๋ ํ ์ผ ํญ๋ชฉ ์ ๊ฑฐ
const updatedTodoList = todoList.filter( (todo) => todo.no != no )
setTodoList( updatedTodoList )
}
// ์ ์ฒด ์ญ์
const onDeleteAll = () => {
// [DELETE] /todos/-1
// DELETE ์์ฒญ
const init = {
method: 'DELETE',
};
// ์ ์ฒด ํ ์ผ ์ญ์ [DELETE]
// โก '์ญ์ ์๋ฃ ๋ฉ์์ง'
fetch(`http://192.168.30.119:8080/todos/-1`,init)
.then( ( response ) => response.text() )
.then( ( data ) => console.log(data) )
.catch( (error) => console.log(error) );
// ์ญ์ ๋ ํ ์ผ ํญ๋ชฉ ์ ๊ฑฐ
setTodoList( [] )
}
// ์ ์ฒด ์๋ฃ
const onCompleteAll = () => {
// PUT ์์ฒญ
// ์ ์ฒด์๋ฃ
// /todos : data : { no : -1 }
const data = {
no: -1,
}
const init = {
method: 'PUT',
headers: {
'Content-Type' : 'application/json',
},
body: JSON.stringify(data),
}
// ํ ์ผ ์ ์ฒด ์๋ฃ [PUT]
// โก '์์ ์๋ฃ ๋ฉ์์ง'
fetch('http://192.168.30.119:8080/todos',init)
.then( ( response ) => response.text() )
.then( ( data ) => console.log(data) )
.catch( (error) => console.log(error) );
const sortedTodoList
= todoList.map((item) => ({ ...item, status: 1 }) )
.sort((a, b) => a.status - b.status == 0 ? b.no - a.no : a.status - b.status)
setTodoList(sortedTodoList)
}
// ํ ์ผ ์์
const onUpdate = (todo) => {
console.log('ํ ์ผ ์์ ์ฒ๋ฆฌ');
console.log(todo);
// PUT ์์ฒญ
const data = {
no: todo.no,
name: todo.name,
status: todo.status,
}
const init = {
method: 'PUT',
headers: {
'Content-Type' : 'application/json',
},
body: JSON.stringify(data),
}
// ํ ์ผ ์์ [PUT]
// โก '์์ ์๋ฃ ๋ฉ์์ง'
fetch('http://192.168.30.119:8080/todos',init)
.then( ( response ) => response.text() )
.then( ( data ) => console.log(data) )
.catch( (error) => console.log(error) );
const sortedTodoList
= todoList.sort((a, b) => a.status - b.status == 0 ? b.no - a.no : a.status - b.status)
setTodoList(sortedTodoList)
};
return (
<div className="container">
<TodoHeader/>
<TodoInput input={input} onChange={onChange} onSubmit={onSubmit}/>
<TodoList todoList={todoList} onToggle={onToggle} onDelete={onDelete} onUpdate={onUpdate} />
<TodoFooter onDeleteAll={onDeleteAll} onCompleteAll={onCompleteAll} />
</div>
)
}
export default TodoContainer
JavaScript
๋ณต์ฌ
TodoHeader.jsx
import React from 'react'
const TodoHeader = () => {
return (
<div className='header'>
<h1>To Do List</h1>
</div>
)
}
export default TodoHeader
JavaScript
๋ณต์ฌ
TodoInput.jsx
import React, { useState } from 'react'
const TodoInput = ({ input, onSubmit, onChange }) => {
return (
<div>
<form className='form'>
<input placeholder='ํ ์ผ ์
๋ ฅ'
className='input'
onChange={onChange}
value={input}
/>
<button type='button'
className='btn'
onClick={onSubmit}
>์ถ๊ฐ</button>
</form>
</div>
)
}
export default TodoInput
JavaScript
๋ณต์ฌ
TodoList.jsx
import React from 'react'
import TodoItem from './TodoItem'
const TodoList = ( { todoList, onToggle, onDelete, onUpdate } ) => {
return (
<ul className="todoList">
{todoList.map( (todo) => (
<TodoItem
key={todo.no}
todo={todo}
onToggle={onToggle}
onDelete={onDelete}
onUpdate={onUpdate}
/>
))}
</ul>
)
}
export default TodoList
JavaScript
๋ณต์ฌ
TodoItem.jsx
import React from 'react'
const TodoItem = ( { todo, onToggle, onDelete, onUpdate } ) => {
let { no, name, status } = todo;
status = status == 1 ? true : false;
const className = status ? 'todoItem active' : 'todoItem';
return (
<li className={className}>
<div className="item">
<input type="checkbox"
id={todo.no}
checked={status}
onChange={ () => onToggle(todo) } />
<label htmlFor={todo.no}></label>
{/* ํ ์ผ */}
<input type="text"
id={`name-${todo.no}`}
className='input'
// value={todo.name}
defaultValue={todo.name}
/>
</div>
<div className="item">
<button className='btn btn-sm'
onClick={ () => {
todo.name = document.getElementById(`name-${todo.no}`).value;
onUpdate(todo);
}}>โ
</button>
<button className='btn btn-sm' onClick={ () => onDelete(no) }>โ</button>
</div>
</li>
)
}
export default TodoItem
JavaScript
๋ณต์ฌ
TodoFooter.jsx
import React from 'react'
const TodoFooter = ({ onDeleteAll, onCompleteAll }) => {
return (
<div className='footer'>
<div className="item">
<button className='btn' onClick={ onDeleteAll }>์ ์ฒด์ญ์ </button>
</div>
<div className="item">
<button className='btn' onClick={ onCompleteAll }>์ ์ฒด์๋ฃ</button>
</div>
</div>
)
}
export default TodoFooter
JavaScript
๋ณต์ฌ
App.css
.container {
width: 480px;
min-height: 600px;
margin: 100px auto;
padding: 20px;
border: 2px solid cornflowerblue;
border-radius: 10px;
box-shadow: 5px 5px 5px rgba(0,0,0,0.3);
background-color: rgba(200,200,200,0.1);
}
.header {
text-align: center;
font-size: 20px;
color: royalblue;
}
.form {
display: flex;
justify-content: space-between;
margin: 10px 10px 10px 0;
}
.form .input {
width: 80%;
height: 30px;
}
.input::placeholder {
color: royalblue;
}
.input {
border: none;
border-bottom: 1px solid royalblue;
padding-left: 12px;
font-size: 18px;
outline: none;
color: blue;
font-weight: bold;
background-color: transparent;
}
.form .btn {
width: 15%;
}
.btn {
width: 80px;
height: 40px;
padding: 0 10px;
font-weight: bold;
border: none;
border-radius: 12px;
background-color: cornflowerblue;
color: white;
cursor: pointer;
}
.btn.btn-sm {
width: auto;
height: 40px;
padding: 0 12px;
}
.btn:hover {
background-color: royalblue;
box-shadow: 2px 2px 5px rgba(0,0,0,0.5);
}
.btn:active {
box-shadow: none;
}
.todoList {
height: 500px;
overflow-y: auto;
}
ul { margin: 0; padding: 0; }
.todoItem {
list-style-type: none;
display: flex;
justify-content: space-between;
align-items: center;
margin: 10px 10px 10px 0;
padding: 20px;
border: 2px solid cornflowerblue;
border-radius: 12px;
color: royalblue;
font-size: 18px;
box-shadow: 5px 5px 5px rgba(0,0,0,0.3);
}
.todoItem.active {
background-color: royalblue;
color: white;
}
.todoItem.active .btn:hover {
background-color: blue;
}
.todoItem .item {
display: flex;
align-items: center;
gap: 14px;
}
/* ์ฒดํฌ๋ฐ์ค */
.todoItem input[type='checkbox'] {
display: none;
}
.todoItem label {
display: inline-block;
width: 20px;
height: 20px;
border: 3px dashed royalblue;
border-radius: 50%;
}
.todoItem input:checked ~ label {
background-color: cornflowerblue;
border: 3px solid blue;
}
.todoItem input:checked ~ input {
text-decoration: line-through;
}
.footer {
display: flex;
justify-content: space-between;
align-items: center;
margin: 10px 10px 10px 0;
}
/* ์คํฌ๋กค๋ฐ */
::-webkit-scrollbar-track
{
-webkit-box-shadow: inset 0 0 6px rgba(0,0,0,0.3);
background-color: transparent;
border-radius: 10px;
}
::-webkit-scrollbar
{
width: 10px;
background-color: transparent;
}
::-webkit-scrollbar-thumb
{
border-radius: 10px;
background-image: -webkit-gradient(linear,
left bottom,
left top,
color-stop(0.44, rgb(122,153,217)),
color-stop(0.72, rgb(73,125,189)),
color-stop(0.86, rgb(28,58,148)));
}
/* ๋ฐฐ๊ฒฝ */
body {
background: linear-gradient(
-45deg,
#ffffff,
#ffe4e9,
#fcc8e2,
#f6baed,
#efb0ff,
#d3a2ff,
#8eb8ff,
#91efff,
#d5f9ff
);
background-size: 400% 400%;
animation: gradient 9s ease infinite;
height: 100vh;
}
@keyframes gradient {
0% {
background-position: 0% 50%;
}
50% {
background-position: 100% 50%;
}
100% {
background-position: 0% 50%;
}
}
CSS
๋ณต์ฌ
App.js
import './App.css';
import TodoContainer from './components/TodoContainer';
function App() {
return (
<>
<TodoContainer/>
</>
);
}
export default App;
JavaScript
๋ณต์ฌ