To Do List ํ๋ก์ ํธ
โTodo List - ํ ์ผ ๋ชฉ๋ก UI ๋ง๋ค๊ธฐโ
์คํ ์ข ๋๋ฌ์ฃผ์ธ์
ํ๋ก ํธ ์๋ (React)
ํ๋ก์ ํธ ๊ตฌ์กฐ
์์ ํ์ผ
โข
App.js
โข
App.css
โข
components
โฆ
TodoContainer.jsx
โฆ
TodoHeader.jsx
โฆ
TodoInput.jsx
โฆ
TodoList.jsx
โฆ
TodoItem.jsx
โฆ
TodoFooter.jsx
์ปดํฌ๋ํธ ๊ตฌ์กฐ
โข
๏ธ client
โฆ
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, { useState, useEffect, useRef } from 'react';
import TodoHeader from './TodoHeader';
import TodoInput from './TodoInput';
import TodoList from './TodoList';
import TodoFooter from './TodoFooter';
const TodoContainer = () => {
const [todoList, setTodoList] = useState([]);
const [input, setInput] = useState('');
useEffect(() => {
fetch('http://localhost:8080/todos')
.then((response) => response.json())
.then((data) => setTodoList(data))
.catch((error) => console.log(error));
}, []);
const onChange = (e) => {
setInput(e.target.value);
};
const onSubmit = async (e) => {
e.preventDefault();
if( input == '' ) {
input = '์ ๋ชฉ์์'
}
// POST request
const data = {
name: input,
status: 0,
};
const init = {
method: 'POST',
headers: {
'Content-Type': 'application/json',
},
body: JSON.stringify(data),
};
try {
const response = await fetch('http://localhost:8080/todos', init);
const newTodo = await response.json();
const updatedList = [newTodo, ...todoList]
setTodoList( updatedList );
} catch (error) {
console.log(error);
}
setInput('');
};
const onRemove = async (no) => {
console.log('remove...');
// DELETE ์์ฒญ
const init = {
method: 'DELETE',
headers: {
'Content-Type': 'application/json',
},
};
try {
const response = await fetch(`http://localhost:8080/todos/${no}`, init);
console.log(response);
} catch (error) {
console.log(error);
}
setTodoList( (todoList) => todoList.filter((todo) => todo.no !== no) )
};
const onToggle = async (todo) => {
console.log('toggle...');
// 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),
};
try {
const response = await fetch('http://localhost:8080/todos', init);
console.log(response);
} catch (error) {
console.log(error);
}
setTodoList((todoList) => {
return todoList.map((item) => {
return item.no === todo.no ? { ...item, status: !item.status } : item;
}).sort((a, b) => {
return a.status - b.status == 0 ? b.no - a.no : a.status - b.status;
})
;
});
};
const onCompleteAll = async (todo) => {
console.log('์ ์ฒด์๋ฃ...');
// PUT ์์ฒญ
const data = {
no: -1,
};
const init = {
method: 'PUT',
headers: {
'Content-Type': 'application/json',
},
body: JSON.stringify(data),
};
try {
const response = await fetch('http://localhost:8080/todos', init);
console.log(response);
} catch (error) {
console.log(error);
}
setTodoList((todoList) => {
return todoList.map((item) => {
return { ...item, status: true };
}).sort((a, b) => {
return a.status - b.status == 0 ? b.no - a.no : a.status - b.status;
})
;
});
};
const onRemoveAll = async (no) => {
console.log('remove...');
// DELETE ์์ฒญ
const init = {
method: 'DELETE',
headers: {
'Content-Type': 'application/json',
},
};
try {
const response = await fetch(`http://localhost:8080/todos/-1`, init);
console.log(response);
} catch (error) {
console.log(error);
}
setTodoList([])
};
return (
<div className='container'>
<TodoHeader />
<TodoInput input={input} onChange={onChange} onSubmit={onSubmit} />
<TodoList todoList={todoList} onRemove={onRemove} onToggle={onToggle} />
<TodoFooter onCompleteAll={onCompleteAll} onRemoveAll={onRemoveAll} />
</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 from 'react'
const TodoInput = ( { input, onChange, onSubmit } ) => {
return (
<div>
<form onSubmit={onSubmit} className='form'>
<input placeholder='ํ ์ผ ์
๋ ฅ' value={input} onChange={onChange} className='input' />
<button type='submit' className='btn'>์ถ๊ฐ</button>
</form>
</div>
)
}
export default TodoInput
JavaScript
๋ณต์ฌ
TodoList.jsx
import React from 'react';
import TodoInput from './TodoInput';
import TodoItem from './TodoItem';
const TodoList = ({ todoList, onRemove, onToggle }) => {
return (
<ul className='todoList'>
{todoList.map((todo) => (
<TodoItem
todo={todo}
key={todo.no}
onRemove={onRemove}
ontoggle={onToggle}
/>
))}
</ul>
);
};
export default TodoList;
JavaScript
๋ณต์ฌ
TodoItem.jsx
import React from 'react'
const TodoItem = ( { todo, onRemove, ontoggle} ) => {
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" checked={status} onChange={() => ontoggle(todo)} id={todo.no} />
<label htmlFor={todo.no}></label>
<span>{name}</span>
</div>
<div className="item">
<button className='btn' onClick={() => onRemove(no)}>์ญ์ </button>
</div>
</li>
)
}
export default TodoItem
JavaScript
๋ณต์ฌ
TodoFooter.jsx
import React from 'react'
const TodoFooter = ({ onCompleteAll, onRemoveAll }) => {
return (
<div className='footer'>
<div className="item">
<button className='btn' onClick={onRemoveAll}>์ ์ฒด์ญ์ </button>
</div>
<div className="item">
<button className='btn' onClick={onCompleteAll}>์ ์ฒด์๋ฃ</button>
</div>
</div>
)
}
export default TodoFooter
JavaScript
๋ณต์ฌ
App.css
.container {
border: 2px solid cornflowerblue;
margin: 100px auto;
width: 480px;
min-height: 600px;
padding: 20px;
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;
}
.input {
width: 80%;
height: 30px;
background-color: transparent;
}
.input::placeholder {
color: royalblue;
}
.form .input {
border: none;
border-bottom: 1px solid royalblue;
padding-left: 12px;
font-size: 18px;
}
.form .input {
outline: none;
color: blue;
}
.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;
}
.btn:hover {
background-color: royalblue;
box-shadow: 2px 2px 5px rgba(0,0,0,0.5);
}
.active .btn:hover {
background-color: blue;
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 .item {
display: flex;
align-items: center;
gap: 14px;
}
/* ์ฒดํฌ๋ฐ์ค */
.todoItem input {
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 ~ span {
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 logo from './logo.svg';
import './App.css';
import TodoContainer from './components/TodoContainer';
function App() {
return (
<>
<TodoContainer/>
</>
);
}
export default App;
JavaScript
๋ณต์ฌ