Search
Duplicate

To Do List - FrontEnd (React)

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