Search

To Do List ํ”„๋กœ์ ํŠธ (Front)

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
๋ณต์‚ฌ